ViewModel的定义: ViewModel旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel本质上是视图(View)与数据(Model)之间的桥梁,想想以前的MVC模式,视图和数据都会写在Activity/Fragment中,导致Activity/Fragment过重,后续难以维护,而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,在介绍Jetpack系列文章Lifecycle时已经提过一次,这里再写一下:
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"复制代码
先看运行效果图:
页面中有两个Fragment,左侧为列表,右侧为详情,当点击左侧某一个Item时,右侧会展示相应的数据,即两个Fragment可以通过ViewModel进行通信;同时可以看到,当屏幕发生旋转的时候,右侧详情页的数据并没有丢失,而是直接进行了展示。
//ViewModelActivity.ktclass 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:
//左侧列表Fragmentclass 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
}
}
}
复制代码
//右侧详情页Fragmentclass 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.ktclass 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的写法有两个好处:
屏幕切换时保存数据
Fragment之间共享数据:
我们在获取ViewModel实例时,并不是直接new出来的,而是通过ViewModelProvider.get()获取的,顾名思义,ViewModelProvider意为ViewModel提供者,那么先来看它的构造方法:
//ViewModelProvider.javapublic 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构造函数中的几个参数:
private final ViewModelStore mViewModelStore;private static final String DEFAULT_KEY = "androidx.lifecycle.ViewModelProvider.DefaultKey";@MainThreadpublic <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);
}@MainThreadpublic <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:
//接口Factorypublic interface Factory {
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}//默认Factory的实现类NewInstanceFactorypublic 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的存取,流程如下:
上面聊了ViewModel的存取,有一个重要的点没有说到,既然ViewModel的生命周期比Activity长,而ViewModel又是通过ViewModelStore存取的,那么ViewModelStore又是如何存取的呢?在上面的流程图中我们知道ViewModelStore是通过ViewModelStoreOwner提供的:
//接口ViewModelStoreOwner.javapublic interface ViewModelStoreOwner { ViewModelStore getViewModelStore();
}
复制代码
ViewModelStoreOwner中接口方法getViewModelStore()返回的既是ViewModelStore。我们上面例子获取ViewModel时是
ViewModelProvider(activity).get(ShareViewModel::class.java),其中的activity其实就是ViewModelStoreOwner,也就是Activity中实现了这个接口:
//ComponentActivity.javapublic 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.javapublic 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.javafinal ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
Activity.NonConfigurationInstances lastNonConfigurationInstances;//1、将NonConfigurationInstances存储到ActivityClientRecordActivityClientRecord 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中获取NonConfigurationInstancesprivate Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
activity.attach(...., r.lastNonConfigurationInstances,....);
}
复制代码
ViewModelStore的存取都是间接在
ActivityThread中进行并保存在
ActivityClientRecord中。在
Activity配置变化时,
ViewModelStore可以在
Activity销毁时得以保存并在重建时重新从
lastNonConfigurationInstances中获取,又因为
ViewModelStore提供了
ViewModel,所以
ViewModel也可以在
Activity配置变化时得以保存,这也是为什么
ViewModel的生命周期比
Activity生命周期长的原因了。
作者:
小马快跑
链接:
https://juejin.cn/post/7018003137692696606
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
更多Android技术分享可以关注@我,也可以加入QQ群号:Android进阶学习群:345659112,一起学习交流。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69983917/viewspace-2795781/ ,如需转载,请注明出处,否则将追究法律责任。