Android Jetpack系列之ViewModel
ViewModel介绍
ViewModel的定义:ViewModel旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel本质上是视图(View)与数据(Model)之间的桥梁,想想以前的MVC模式,视图和数据都会写在Activity/Fragment中,导致Activity/Fragment过重,后续难以维护,而ViewModel将视图和数据进行了分离解耦,为视图层提供数据。
ViewModel的特点:
ViewModel生命周期比Activity长
数据可在屏幕发生旋转等配置更改后继续留存。下面是ViewModel生命周期图:
可以看到即使是发生屏幕旋转,旋转之后拿到的ViewModel跟之前的是同一个实例,即发生屏幕旋转时,ViewModel并不会消失重建;而如果Activity是正常finish(),ViewModel则会调用onClear()销毁。
ViewModel中不能持有Activity引用
不要将Activity传入ViewModel中,因为ViewModel的生命周期比Activity长,所以如果ViewModel持有了Activity引用,很容易造成内存泄漏。如果想在ViewModel中使用Application,可以使用ViewModel的子类AndroidViewModel,其在构造方法中需要传入了Application实例:
public class AndroidViewModel extends ViewModel { private Application mApplication; public AndroidViewModel(@NonNull Application application) { mApplication = application; } public <T extends Application> T getApplication() { return (T) mApplication; } }复制代码
ViewModel使用举例
引入ViewModel,在介绍Jetpack系列文章Lifecycle时已经提过一次,这里再写一下:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"复制代码
先看运行效果图:
页面中有两个Fragment,左侧为列表,右侧为详情,当点击左侧某一个Item时,右侧会展示相应的数据,即两个Fragment可以通过ViewModel进行通信;同时可以看到,当屏幕发生旋转的时候,右侧详情页的数据并没有丢失,而是直接进行了展示。
//ViewModelActivity.kt class ViewModelActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.e(JConsts.VIEW_MODEL, "onCreate") setContentView(R.layout.activity_view_model) } }复制代码
其中的XML文件:
//activity_view_model.xml <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".viewmodel.ViewModelActivity"> <fragment android:id="@+id/fragment_item" android:name="com.example.jetpackstudy.viewmodel.ItemFragment" android:layout_width="150dp" android:layout_height="match_parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/fragment_detail" app:layout_constraintTop_toTopOf="parent" /> <fragment android:id="@+id/fragment_detail" android:name="com.example.jetpackstudy.viewmodel.DetailFragment" android:layout_width="0dp" android:layout_height="match_parent" app:layout_constraintLeft_toRightOf="@+id/fragment_item" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>复制代码
直接将Fragment以布局方式写入我们的Activity中,继续看两个Fragment:
//左侧列表Fragment class ItemFragment : Fragment() { lateinit var mRvView: RecyclerView //Fragment之间通过传入同一个Activity来共享ViewModel private val mShareModel by lazy { ViewModelProvider(activity!!).get(ShareViewModel::class.java) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = LayoutInflater.from(activity) .inflate(R.layout.layout_fragment_item, container, false) mRvView = view.findViewById(R.id.rv_view) mRvView.layoutManager = LinearLayoutManager(activity) mRvView.adapter = MyAdapter(mShareModel) return view } //构造RecyclerView的Adapter class MyAdapter(private val sViewModel: ShareViewModel) : RecyclerView.Adapter<MyAdapter.MyViewHolder>() { class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val mTvText: TextView = itemView.findViewById(R.id.tv_text) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val itemView = LayoutInflater.from(parent.context) .inflate(R.layout.item_fragment_left, parent, false) return MyViewHolder(itemView) } override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val itemStr = "item pos:$position" holder.mTvText.text = itemStr //点击发送数据 holder.itemView.setOnClickListener { sViewModel.clickItem(itemStr) } } override fun getItemCount(): Int { return 50 } } }复制代码
//右侧详情页Fragment class DetailFragment : Fragment() { lateinit var mTvDetail: TextView //Fragment之间通过传入同一个Activity来共享ViewModel private val mShareModel by lazy { ViewModelProvider(activity!!).get(ShareViewModel::class.java) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return LayoutInflater.from(context) .inflate(R.layout.layout_fragment_detail, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { mTvDetail = view.findViewById(R.id.tv_detail) //注册Observer并监听数据变化 mShareModel.itemLiveData.observe(activity!!, { itemStr -> mTvDetail.text = itemStr }) } }复制代码
最后贴一下我们的ViewModel:
//ShareViewModel.kt class ShareViewModel : ViewModel() { val itemLiveData: MutableLiveData<String> by lazy { MutableLiveData<String>() } //点击左侧Fragment中的Item发送数据 fun clickItem(infoStr: String) { itemLiveData.value = infoStr } }复制代码
这里再强调一下,两个Fragment中ViewModelProvider(activity).get()传入的是同一个Activity,从而得到的ViewModel是同一个实例,进而可以进行互相通信。
上面使用ViewModel的写法有两个好处:
屏幕切换时保存数据
屏幕发生变化时,不需要重新请求数据,直接从ViewModel中再次拿数据即可。
Fragment之间共享数据:
Activity 不需要执行任何操作,也不需要对此通信有任何了解。
除了 SharedViewModel 约定之外,Fragment 不需要相互了解。如果其中一个 Fragment 消失,另一个 Fragment 将继续照常工作。
每个 Fragment 都有自己的生命周期,而不受另一个 Fragment 的生命周期的影响。如果一个 Fragment 替换另一个 Fragment,界面将继续工作而没有任何问题。
ViewModel与onSaveInstance(Bundle)对比
ViewModel是将数据存到内存中,而onSaveInstance()是通过Bundle将序列化数据存在磁盘中
ViewModel可以存储任何形式的数据,且大小不限制(不超过App分配的内存即可),onSaveInstance()中只能存储可序列化的数据,且大小一般不超过1M(IPC通信数据限制)
源码解析
ViewModel的存取
我们在获取ViewModel实例时,并不是直接new出来的,而是通过ViewModelProvider.get()获取的,顾名思义,ViewModelProvider意为ViewModel提供者,那么先来看它的构造方法:
//ViewModelProvider.java public ViewModelProvider(@NonNull ViewModelStoreOwner owner) { this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory() : NewInstanceFactory.getInstance()); } public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) { this(owner.getViewModelStore(), factory); } public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) { mFactory = factory; mViewModelStore = store; }复制代码
ViewModelProvider构造函数中的几个参数:
ViewModelStoreOwner:ViewModel存储器拥有者,用来提供ViewModelStore
ViewModelStore:ViewModel存储器,用来存储ViewModel
Factory:创建ViewModel的工厂
private final ViewModelStore mViewModelStore; private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey"; @MainThread public <T extends ViewModel> T get(@NonNull Class<T> modelClass) { String canonicalName = modelClass.getCanonicalName(); if (canonicalName == null) { throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels"); } //首先构造了一个key,直接调用下面的get(key,modelClass) return get(DEFAULT_KEY + ":" + canonicalName, modelClass); } @MainThread public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) { //尝试从ViewModelStore中获取ViewModel ViewModel viewModel = mViewModelStore.get(key); if (modelClass.isInstance(viewModel)) { if (mFactory instanceof OnRequeryFactory) { ((OnRequeryFactory) mFactory).onRequery(viewModel); } //viewModel不为空直接返回该实例 return (T) viewModel; } else { //noinspection StatementWithEmptyBody if (viewModel != null) { // TODO: log a warning. } } if (mFactory instanceof KeyedFactory) { viewModel = ((KeyedFactory) mFactory).create(key, modelClass); } else { //viewModel为空,通过Factory创建 viewModel = mFactory.create(modelClass); } //将ViewModel保存到ViewModelStore中 mViewModelStore.put(key, viewModel); return (T) viewModel; }复制代码
逻辑很简单,首先尝试通过ViewModelStore.get(key)获取ViewModel,如果不为空直接返回该实例;如果为空,通过Factory.create创建ViewModel并保存到ViewModelStore中。先来看Factory是如何创建ViewModel的,ViewModelProvider构造函数中,如果没有传入Factory,那么会使用NewInstanceFactory:
//接口Factory public interface Factory { <T extends ViewModel> T create(@NonNull Class<T> modelClass); } //默认Factory的实现类NewInstanceFactory public static class NewInstanceFactory implements Factory { private static NewInstanceFactory sInstance; //获取单例 static NewInstanceFactory getInstance() { if (sInstance == null) { sInstance = new NewInstanceFactory(); } return sInstance; } @Override public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { try { //反射创建 return modelClass.newInstance(); } catch (InstantiationException e) { throw new RuntimeException("Cannot create an instance of " + modelClass, e); } catch (IllegalAccessException e) { throw new RuntimeException("Cannot create an instance of " + modelClass, e); } } }复制代码
可以看到NewInstanceFactory的实现很简单,直接通过传入的class反射创建实例,泛型中限制必须是ViewModel的子类,所以最终创建的是ViewModel实例。
看完Factory,接着来看ViewModelStore:
public class ViewModelStore { private final HashMap<String, ViewModel> mMap = new HashMap<>(); final void put(String key, ViewModel viewModel) { ViewModel oldViewModel = mMap.put(key, viewModel); //如果oldViewModel不为空,调用oldViewModel的onCleared释放资源 if (oldViewModel != null) { oldViewModel.onCleared(); } } final ViewModel get(String key) { return mMap.get(key); } Set<String> keys() { return new HashSet<>(mMap.keySet()); } //遍历存储的ViewModel并调用其clear()方法,然后清除所有ViewModel public final void clear() { for (ViewModel vm : mMap.values()) { vm.clear(); } mMap.clear(); } }复制代码
ViewModelStore类也很简单,内部就是通过Map进行存储ViewModel的。到这里,我们基本看完了ViewModel的存取,流程如下:
ViewModelStore的存取
上面聊了ViewModel的存取,有一个重要的点没有说到,既然ViewModel的生命周期比Activity长,而ViewModel又是通过ViewModelStore存取的,那么ViewModelStore又是如何存取的呢?在上面的流程图中我们知道ViewModelStore是通过ViewModelStoreOwner提供的:
//接口ViewModelStoreOwner.java public interface ViewModelStoreOwner { ViewModelStore getViewModelStore(); }复制代码
ViewModelStoreOwner中接口方法getViewModelStore()返回的既是ViewModelStore。我们上面例子获取ViewModel时是ViewModelProvider(activity).get(ShareViewModel::class.java)
,其中的activity其实就是ViewModelStoreOwner,也就是Activity中实现了这个接口:
//ComponentActivity.java public class ComponentActivity extends androidx.core.app.ComponentActivity implements LifecycleOwner, ViewModelStoreOwner,.... { @Override public ViewModelStore getViewModelStore() { //Activity还未关联到Application时,会抛异常,此时不能使用ViewModel if (getApplication() == null) { throw new IllegalStateException("Your activity is not yet attached to the " + "Application instance. You can't request ViewModel before onCreate call."); } ensureViewModelStore(); return mViewModelStore; } void ensureViewModelStore() { if (mViewModelStore == null) { NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null) { //从NonConfigurationInstances中恢复ViewModelStore mViewModelStore = nc.viewModelStore; } if (mViewModelStore == null) { mViewModelStore = new ViewModelStore(); } } } @Override //覆写了Activity的onRetainNonConfigurationInstance方法,在Activity#retainNonConfigurationInstances()方法中被调用,即配置发生变化时调用。 public final Object onRetainNonConfigurationInstance() { Object custom = onRetainCustomNonConfigurationInstance(); ViewModelStore viewModelStore = mViewModelStore; if (viewModelStore == null) { //尝试从之前的存储中获取NonConfigurationInstance NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance(); if (nc != null) { //从NonConfigurationInstances中恢复ViewModelStore viewModelStore = nc.viewModelStore; } } if (viewModelStore == null && custom == null) { return null; } //如果viewModelStore不为空,当配置变化时将ViewModelStore保存到NonConfigurationInstances中 NonConfigurationInstances nci = new NonConfigurationInstances(); nci.custom = custom; nci.viewModelStore = viewModelStore; return nci; } //内部类NonConfigurationInstances static final class NonConfigurationInstances { Object custom; ViewModelStore viewModelStore; } }复制代码
NonConfigurationInstances是ComponentActivity的静态内部类,里面包含了ViewModelStore。getViewModelStore()内部首先尝试通过getLastNonConfigurationInstance()来获取NonConfigurationInstances,不为空直接能拿到对应的ViewModelStore;否则直接new一个新的ViewModelStore
跟进去getLastNonConfigurationInstance()这个方法:
//Activity.java public class Activity extends ContextThemeWrapper { NonConfigurationInstances mLastNonConfigurationInstances; public Object getLastNonConfigurationInstance() { return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.activity : null; } NonConfigurationInstances retainNonConfigurationInstances() { //onRetainNonConfigurationInstance实现在子类ComponentActivity中实现 Object activity = onRetainNonConfigurationInstance(); ....... if (activity == null && children == null && fragments == null && loaders == null && mVoiceInteractor == null) { return null; } NonConfigurationInstances nci = new NonConfigurationInstances(); nci.activity = activity; ...... return nci; } final void attach(Context context, ......., NonConfigurationInstances lastNonConfigurationInstances) { mLastNonConfigurationInstances = lastNonConfigurationInstances; } static final class NonConfigurationInstances { Object activity; ...... } }复制代码
可以看到getLastNonConfigurationInstance()中是通过mLastNonConfigurationInstances是否为空来判断的,搜索一下该变量赋值的地方,就找到了Activity#attach()方法。我们知道Activity#attach()是在创建Activity的时候调用的,顺藤摸瓜就可以找到了ActivityThread:
//ActivityThread.java final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); Activity.NonConfigurationInstances lastNonConfigurationInstances; //1、将NonConfigurationInstances存储到ActivityClientRecord ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) { ActivityClientRecord r = mActivities.get(token); Class<? extends Activity> activityClass = null; if (r != null) { ...... if (getNonConfigInstance) { try { //retainNonConfigurationInstances r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances(); } catch (Exception e) { ...... } } } synchronized (mResourcesManager) { mActivities.remove(token); } return r; } //2、从ActivityClientRecord中获取NonConfigurationInstances private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { ...... activity.attach(...., r.lastNonConfigurationInstances,....); }复制代码
在执行performDestroyActivity()的时候,会调用Activity#retainNonConfigurationInstances()方法将生成的NonConfigurationInstances赋值给lastNonConfigurationInstances。
在performLaunchActivity()中又会通过Activity#attach()将lastNonConfigurationInstances赋值给Activity.mLastNonConfigurationInstances,进而取到ViewModelStore。
ViewModelStore
的存取都是间接在ActivityThread
中进行并保存在ActivityClientRecord
中。在Activity
配置变化时,ViewModelStore
可以在Activity
销毁时得以保存并在重建时重新从lastNonConfigurationInstances
中获取,又因为ViewModelStore
提供了ViewModel
,所以ViewModel
也可以在Activity
配置变化时得以保存,这也是为什么ViewModel
的生命周期比Activity
生命周期长的原因了。
作者:_小马快跑_
链接:https://juejin.cn/post/7018003137692696606