前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android Jetpack 组件之 ViewModel 详解

Android Jetpack 组件之 ViewModel 详解

作者头像
子晋
发布2022-01-18 19:13:06
1.2K0
发布2022-01-18 19:13:06
举报
文章被收录于专栏:子晋城

ViewModel 具有生命周期意识,会自动存储和管理 UI 相关的数据,即使设备配置发生变化后数据还会存在,我们就不需要在 onSaveInstanceState 保存数据,在 onCreate 中恢复数据了,使用 ViewModel 这部分工作就不需要我们做了,很好地将视图与逻辑分离开来。

  1. ViewModel生命周期
  2. ViewModel的源码分析
  3. 什么是ViewModelStore
  4. 什么是ViewModelStoreOwner
  5. 如何简化Fragment之间的通信

ViewModel 的生命周期

从 OnCreate 获取到 ViewModel 之后,它会一直存在,直到该 ViewModel 绑定的 View 彻底 onDestory。

ViewModel 的源码分析

本次创建项目是升级 Android Studio 为 3.2.1,所以直接将项目中的依赖包替换成 androidx 下面的对应包,主要配置如下:

代码语言:javascript
复制
// 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 如下:

代码语言:javascript
复制
/**  
 * 如果需要使用Context可以选择继承 AndroidViewModel  
 */
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 中使用就可以了,具体如下:

代码语言:javascript
复制
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,四个静态方法如下:

代码语言:javascript
复制
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)

以第二个方法为例,其实现如下:

代码语言:javascript
复制
@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 中两个关键属性:

代码语言:javascript
复制
private final Factory mFactory;
private final ViewModelStore mViewModelStore;

当创建完 ViewModelProvider 的时候,mFactory 和 mViewModelStore 已经被初始化了,然后是 get() 方法,源码如下:

代码语言:javascript
复制
@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 方法如下:

代码语言:javascript
复制
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,其创建时通过反射获取构造方法创建的,关键代码如下:

代码语言:javascript
复制
@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

ViewModelStore 主要是用来保存当设备配置发生变化的时候保存 ViewModel 的状态,如当前界面被重新创建或者销毁等,对应的新的 ViewModelStore 应该和旧的 ViewModelStore 一样保存对应 ViewModel 的所有信息,只有调用了对应的 clear() 方法才会通知这个 ViewModel 不在使用,其对应的 ViewModelStore 也不会存储相关信息了。

该类实际上使用 HashMap 存储相应的 ViewModel,非常简单:

代码语言:javascript
复制
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();
    }
}

什么是 ViewModelStoreOwner

这是一个接口,定义了一个方法 getViewModelStore() 用来获取对应 ViewModel 的 ViewModelStore , 同样调用了 ViewModelStoreOwner 的 clear() 方法,则获取不到对应的 ViewModelStore 了,源码如下:

代码语言:javascript
复制
public interface ViewModelStoreOwner {
    /**
     * Returns owned {@link ViewModelStore}
     *
     * @return a {@code ViewModelStore}
     */
    @NonNull
    ViewModelStore getViewModelStore();
}

当然,具体的肯定是实现类了,实际上像 FragmentActivity 、Fragment 等都间接或直接实现了这个接口,这一点和 LifecycleOwner 一样,源码参考如下:

  • Activity 间接实现: ```java 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; }
代码语言:javascript
复制

- Fragment 直接实现:
```java
@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 之间的通信

Fragment 之间的通信以前是使用接口通过宿主 Activity 转发来实现的,现在可以使用同一 ViewModel 完成两个 Fragment 之间的通信,记住一点,使用 ViewModel 进行两个 Fragment 之间通信的时候,创建 ViewModel 使用其宿主 Activity 来创建,实现过程如下,首先创建一个 ViewModel 如下:

代码语言:javascript
复制
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 如下:

代码语言:javascript
复制
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 的布局文件如下:

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?><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 如下:

代码语言:javascript
复制
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 如下:

代码语言:javascript
复制
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 的布局文件如下:

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?><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 的优势如下:

  1. Activity 不在介入子 Fragment 之间的通信了,职责更单一。
  2. Fragment 之间除了使用同一个 ViewModel 的实例,其他互不相同,任何一个 Fragment 都可单独工作。
  3. 每个 Fragment 都有自己的生命周期,可以随意替换和移除都不会互相影响另一个 Fragment 的正常工作。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-09-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • ViewModel 的生命周期
  • ViewModel 的源码分析
  • 什么是 ViewModelStore
  • 什么是 ViewModelStoreOwner
  • 如何简化 Fragment 之间的通信
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档