PS:原文首发于微信公众号:躬行之(jzman-blog)
前面学习了 LiveData 和 Lifecycle 架构组件的使用:
ViewModel 具有生命周期意识,会自动存储和管理 UI 相关的数据,即使设备配置发生变化后数据还会存在,我们就不需要在 onSaveInstanceState 保存数据,在 onCreate 中恢复数据了,使用 ViewModel 这部分工作就不需要我们做了,很好地将视图与逻辑分离开来。
从 OnCreate 获取到 ViewModel 之后,它会一直存在,直到该 ViewModel 绑定的 View 彻底 onDestory。
本次创建项目是升级 Android Studio 为 3.2.1,所以直接将项目中的依赖包替换成 androidx 下面的对应包,主要配置如下:
//gradle插件
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
}
// ViewModel and LiveData版本
def lifecycle_version = "2.0.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
//gradle-wrapper.properties文件
distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
创建 ViewModel 如下:
/**
*
* 如果需要使用Context可以选择继承AndroidViewModel
* Powered by jzman.
* Created on 2018/12/13 0013.
*/
public class MViewModel extends ViewModel {
private MutableLiveData<List<Article>> data;
public LiveData<List<Article>> getData(){
if (data == null){
data = new MutableLiveData<>();
data.postValue(DataUtil.getData());
}
return data;
}
}
如果需要使用 Context 可以选择继承 AndroidViewModel,这里继承 ViewModel 就可以了,然后,在 Activity 中使用就可以了,具体如下:
MViewModel mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
mViewModel.getData().observe(this, new Observer<List<Article>>() {
@Override
public void onChanged(List<Article> articles) {
for (Article article : articles) {
Log.i(TAG,article.getDesc());
}
}
});
来看一看调用过程,从 ViewModelProviders 开始,ViewModelProviders 主要提供四个静态方法获取对应的 ViewModelProvider,四个静态方法如下:
public static ViewModelProvider of(@NonNull Fragment fragment)
public static ViewModelProvider of(@NonNull FragmentActivity activity)
public static ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory)
public static ViewModelProvider of(@NonNull FragmentActivity activity, @Nullable Factory factory)
以第二个方法为例,其实现如下:
@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
@Nullable Factory factory) {
Application application = checkApplication(activity);
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return new ViewModelProvider(activity.getViewModelStore(), factory);
}
可以使用用默认的 AndroidViewModelFactory,也可以自定义 Factory,直接调用上面任意一个方法创建 ViewModelProvider 即可:
那就来看一下 ViewModelProvider,ViewModelProvider 中两个关键属性:
private final Factory mFactory;
private final ViewModelStore mViewModelStore;
当创建完 ViewModelProvider 的时候,mFactory 和 mViewModelStore 已经被初始化了,然后是 get() 方法,源码如下:
@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
//获取类名称,在获取内部类名称时与getName有所区分
//getCanonicalName-->xx.TestClass.InnerClass
//getName-->xx.TestClass$InnerClass
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
然后调用带参数 key 的 get 方法如下:
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
ViewModel viewModel = mViewModelStore.get(key);
if (modelClass.isInstance(viewModel)) {
//noinspection unchecked
return (T) viewModel;
} else {
//noinspection StatementWithEmptyBody
if (viewModel != null) {
// TODO: log a warning.
}
}
//创建ViewModel
viewModel = mFactory.create(modelClass);
//从mViewModelStore中根据key获取对应的ViewModel返回
mViewModelStore.put(key, viewModel);
//noinspection unchecked
return (T) viewModel;
}
此时,ViewModel 就创建好了,那 VIewModel 是如何被创建的呢,mFactory 的具体实现这里是默认的 AndroidViewModelFactory,其创建时通过反射获取构造方法创建的,关键代码如下:
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
//判断AndroidViewModel是不是modelClass的父类或接口
if (AndroidViewModel.class.isAssignableFrom(modelClass)) {
//...
//反射创建ViewModel并返回
return modelClass.getConstructor(Application.class).newInstance(mApplication);
}
return super.create(modelClass);
}
具体的 ViewModel 对象创建完成之后,就可以随意调用具体的 ViewModel 中的方法了,前面跟源码的时候会遇到各种封装类,如 ViewModelStore、ViewModelStoreOwner、AndroidViewModelFactory 等,下文中将会介绍。
ViewModelStore 主要是用来保存当设备配置发生变化的时候保存 ViewModel 的状态,如当前界面被重新创建或者销毁等,对应的新的 ViewModelStore 应该和旧的 ViewModelStore 一样保存对应 ViewModel 的所有信息,只有调用了对应的 clear() 方法才会通知这个 ViewModel 不在使用,其对应的 ViewModelStore 也不会存储相关信息了。
该类实际上使用 HashMap 存储相应的 ViewModel,非常简单:
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
}
这是一个接口,定义了一个方法 getViewModelStore() 用来获取对应 ViewModel 的 ViewModelStore , 同样调用了 ViewModelStoreOwner 的 clear() 方法,则获取不到对应的 ViewModelStore 了,源码如下:
public interface ViewModelStoreOwner {
/**
* Returns owned {@link ViewModelStore}
*
* @return a {@code ViewModelStore}
*/
@NonNull
ViewModelStore getViewModelStore();
}
当然,具体的肯定是实现类了,实际上像 FragmentActivity 、Fragment 等都间接或直接实现了这个接口,这一点和 LifecycleOwner 一样,源码参考如下:
public ViewModelStore getViewModelStore() {
if (getApplication() == null) {
throw new IllegalStateException("Your activity is not yet attached to the "
+ "Application instance. You can't request ViewModel before onCreate call.");
}
if (mViewModelStore == null) {
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
// Restore the ViewModelStore from NonConfigurationInstances
mViewModelStore = nc.viewModelStore;
}
if (mViewModelStore == null) {
mViewModelStore = new ViewModelStore();
}
}
return mViewModelStore;
}
@Override
public ViewModelStore getViewModelStore() {
if (mFragmentManager == null) {
throw new IllegalStateException("Can't access ViewModels from detached fragment");
}
return mFragmentManager.getViewModelStore(this);
}
其保存 ViewModelStore 过程是在 Activty 或 Fragment 的上层实现中完成,对于认识 ViewModelStoreOwner 这个接口到这里就 OK 了。
Fragment 之间的通信以前是使用接口通过宿主 Activity 转发来实现的,现在可以使用同一 ViewModel 完成两个 Fragment 之间的通信,记住一点,使用 ViewModel 进行两个 Fragment 之间通信的时候,创建 ViewModel 使用其宿主 Activity 来创建,实现过程如下,首先创建一个 ViewModel 如下:
/**
* Powered by jzman.
* Created on 2018/12/14 0014.
*/
public class FViewModel extends ViewModel {
private MutableLiveData<String> mSelect = new MutableLiveData<>();
public void selectItem(String item) {
mSelect.postValue(item);
}
public LiveData<String> getSelect() {
return mSelect;
}
}
然后,创建 LeftFragment 如下:
public class LeftFragment extends Fragment {
private FViewModel mViewModel;
private FragmentTitleBinding titleBinding;
public LeftFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_title, container, false);
titleBinding = DataBindingUtil.bind(view);
mViewModel = ViewModelProviders.of(getActivity()).get(FViewModel.class);
RvAdapter adapter = new RvAdapter(getActivity(), new RvAdapter.OnRecycleItemClickListener() {
@Override
public void onRecycleItemClick(String info) {
mViewModel.selectItem(info);
}
});
titleBinding.rvData.setLayoutManager(new LinearLayoutManager(getActivity()));
titleBinding.rvData.setAdapter(adapter);
return view;
}
}
LeftFragment 布局文件就一个 RecycleView,其 Item 的布局文件如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="itemData"
type="String"/>
<variable
name="onItemClick"
type="com.manu.archsamples.fragment.RvAdapter.OnRecycleItemClickListener"/>
data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:onClick="@{() -> onItemClick.onRecycleItemClick(itemData)}">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{itemData}"
android:padding="10dp"/>
LinearLayout>
layout>
RecyclerView 的 Adapter 如下:
public class RvAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mContext;
private List<String> mData;
private OnRecycleItemClickListener mOnRecycleItemClickListener;
public RvAdapter(Context mContext,OnRecycleItemClickListener itemClickListener) {
this.mContext = mContext;
mData = DataUtil.getDataList();
mOnRecycleItemClickListener = itemClickListener;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(mContext).inflate(R.layout.recycle_item,null);
view.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
));
return new MViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
MViewHolder mHolder = (MViewHolder) holder;
mHolder.bind(mData.get(position),mOnRecycleItemClickListener);
}
@Override
public int getItemCount() {
return mData.size();
}
private static class MViewHolder extends RecyclerView.ViewHolder{
RecycleItemBinding itemBinding;
MViewHolder(@NonNull View itemView) {
super(itemView);
itemBinding = DataBindingUtil.bind(itemView);
}
void bind(String info, OnRecycleItemClickListener itemClickListener){
itemBinding.setItemData(info);
itemBinding.setOnItemClick(itemClickListener);
}
}
public interface OnRecycleItemClickListener {
void onRecycleItemClick(String info);
}
}
然后,创建 RightFragment 如下:
public class RightFragment extends Fragment {
private static final String TAG = RightFragment.class.getName();
private FragmentContentBinding contentBinding;
private FViewModel mViewModel;
public RightFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_content, container, false);
contentBinding = DataBindingUtil.bind(view);
mViewModel = ViewModelProviders.of(getActivity()).get(FViewModel.class);
mViewModel.getSelect().observe(this, new Observer<String>() {
@Override
public void onChanged(String s) {
//接收LeftFragment Item 点击事件的值
contentBinding.setData(s);
}
});
return view;
}
}
RightFragment 的布局文件如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="data"
type="String"/>
data>
<FrameLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragment.LeftFragment">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:layout_marginStart="12dp"
android:layout_marginTop="10dp"
android:text="@{data,default=def}"/>
FrameLayout>
layout>
实现方式比较简单,没什么多说的,使用 ViewModel 之后,宿主 Activity 就非常清爽,只负责 Fragment 的切换就可以了,测试效果如下:
使用 ViewModel 的优势如下: