ViewModel 唯一性原理解析、生命周期管理解析

1. 是什么?

ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。

2. 有什么作用?
  • 管理UI数据;
  • 发生横竖屏切换时仍能保持唯一,避免重复请求数据;
  • Activity - Fragment 间通信 (ViewModel + LiveData);
3. 为什么可以管理UI数据?
  • ViewModel + LiveData + ViewBinding,支持在ViewModel中请求数据并做逻辑处理,之后通过LiveData将数据更新通知给ViewBinding,ViewBinding收到数据刷新消息之后刷新UI。
4. 为什么可以用作Aty-Fgm间通信?

通过ViewModelProvider指定一个Activity构建出来的ViewModel,在Activity生命周期内是唯一的(横竖屏切换后也能保持唯一)

  • Activity 会实现 ViewModelStoreOwner 的 getViewModelStore 返回 ViewModelStore,ViewModelStore内部有HashMap用于保存在Activity作用域下构建的ViewModel;
  • 通过ViewModelProvider获取ViewModel,会先向Activity获取ViewModelStore,然后通过ViewModel的类名作为key从HashMap容器中检查是否存在该ViewModel对象,有就直接拿出来用,没有就通过工厂新建;
  • 因此,通过ViewModelProvider获得的Activity的ViewModel,在作用域生命周期内是唯一的(包括横竖屏切换)
5. 为什么横竖屏切换后仍然保持同一对象?

ViewModel 类是一种屏幕级状态容器,即使发生横竖屏切换导致Activity重建,ViewModel都能保持唯一,为什么?猜测是Framework层针对Activity作用域下的全体ViewModel进行了缓存,那是逐个将ViewModel缓存吗?显然不是,通过缓存作用域的ViewModelStore是更好的选择。
下面就来研究怎么个缓存法:

5.1 研究 ViewModelProvider获取ViewModel流程
  • ViewModelProvider获取ViewModel方式
mainViewModel = 
ViewModelProvider(MainActivity.this).get(MainViewModel::class.java)
  • ViewModelProvider 构造函数
public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
        this(owner.getViewModelStore(), ...);
}

可见,MainActivity是实现了ViewModelStoreOwner的,并且向ViewModelProvider提供了自己的ViewModelStore,为什么要这样做?因为MainActivity就是一个作用域,为这个作用域构建出来的ViewModel随MainActivity生命周期销毁而释放才是合理的。
但是MainActivity本身是没有实现ViewModelStoreOwner的,那就应该是父类实现了,我们定位到FragmentActivity后查看ViewModelStoreOwner的方法实现:getViewModelStore

  • FragmentActivity.getViewModelStore 源码
// 摘自androidx.fragment.app.FragmentActivity implements ViewModelStoreOwner
public ViewModelStore getViewModelStore() {
    // 一、mViewModelStore 不为空就直接返回
    if (mViewModelStore == null) {
        NonConfigurationInstances nc = (NonConfigurationInstances) getLastNonConfigurationInstance();
        // 二、尝试通过 getLastNonConfigurationInstance 获取系统保存的 ViewModelStore
        if (nc != null) { // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        // 三、获取不到系统保存的,就直接new
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

看过代码后有个疑问,为什么ViewModelStore不直接new,而是要先经过getLastNonConfigurationInstance()判断?

  • 猜测:是不是就是这个getLastNonConfigurationInstance()缓存着ViewModel的容器ViewModelStore?怀着疑问查看getLastNonConfigurationInstance()方法
5.2 研究 getLastNonConfigurationInstance 流程

疑问:

  1. 为什么先要通过getLastNonConfigurationInstance获取ViewModelStore?
  2. 获取的ViewModelStore又是什么时候保存下来的?
  • getLastNonConfigurationInstance 源码
// 摘自 android.app.Activity
public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.activity : null;
}

结论:ViewModelStore保存在activity中
疑问:activity是什么?在哪里赋值的?

  • mLastNonConfigurationInstances.activity 在哪里赋值

经过对activity赋值的代码跟踪可以发现,activity赋值是在Activity.retainNonConfigurationInstances()中的

// 摘自 android.app.Activity
NonConfigurationInstances retainNonConfigurationInstances() {
    // !!! -> retainNonConfigurationInstances是在哪里调用的?
    // !!! -> onRetainNonConfigurationInstance又返回了什么?
    Object activity = onRetainNonConfigurationInstance();
    ...
    // 注意,这是 Activity.NonConfigurationInstances
    NonConfigurationInstances nci = new NonConfigurationInstances(); 
    nci.activity = activity; // 这里就是设置activity的地方
    ...
    return nci;
}

疑问:

  1. retainNonConfigurationInstances又是哪里调用的?返回值又是给谁的?
  2. 想知道activity是什么,还得搞清楚onRetainNonConfigurationInstance返回了什么。
  • onRetainNonConfigurationInstance返回了什么?
// 摘自 android.app.Activity
public Object onRetainNonConfigurationInstance() {
    // Activity中的onRetainNonConfigurationInstance返回了null,所以去子类查找重载方法
    return null;
}

不同Activity实现类,基于onRetainNonConfigurationInstance可能会有自己不同的重载方式,这里可以通过FragmentActivity查看

  • FragmentActivity.onRetainNonConfigurationInstance 源码
@Override
public final Object onRetainNonConfigurationInstance() {
    Object custom = onRetainCustomNonConfigurationInstance();
    FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
    ...
    // 注意,这是 FragmentActivity.NonConfigurationInstances
    // 与上面的Activity.NonConfigurationInstances看起来像,但不是一个东西
    NonConfigurationInstances nci = new NonConfigurationInstances();
    ...
    // 哇呜,这里把 mViewModelStore 封装起来了
    nci.viewModelStore = mViewModelStore; 
    ...
    return nci;
}

可见,上面提到的mLastNonConfigurationInstances.activity的这个activity其实就是ViewModelStore和其他一些信息的封装类NonConfigurationInstances,只是不同Activity的实现类不一样,具体NonConfigurationInstances就会保存不一样的值,这也是为什么onRetainNonConfigurationInstance返回一个Object的原因
疑问

  1. activity怎么赋值的咱们知道了,那还有个重要的疑问,retainNonConfigurationInstances又是什么时候触发导致ViewModelStore被封装返回的?返回值又是给谁用的?
  • ActivityThread.performDestroyActivity

经过代码跟踪发现,retainNonConfigurationInstances在ActivityThread.performDestroyActivity被调用

// 摘自 android.app.ActivityThread
void performDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
    ...
    // 如果 getNonConfigInstance == true,就会将 retainNonConfigurationInstances
    // 的结果保存到 ActivityClientRecord 中保存起来
    // 设计横竖屏切换时,getNonConfigInstance == true
    if (getNonConfigInstance) {
        try {
            r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
        } catch (Exception e) {
            if (!mInstrumentation.onException(r.activity, e)) {
                throw new RuntimeException("Unable to retain activity "
                        + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
            }
        }
    }
    ...
}

经过上述分析我们得知,在页面销毁时会经过经过判断getNonConfigInstance是否为true,来决定是否调用Activity.retainNonConfigurationInstances将ViewModelStore及其他一些信息的封装类放入ActivityClientRecord。
疑问:

  1. getNonConfigInstance什么时候为true?
  2. Activity.mLastNonConfigurationInstances数据来源是什么?它与上面保存起来的ActivityClientRecord.lastNonConfigurationInstances是个什么关系?
5.3 研究 Activity.mLastNonConfigurationInstances数据来源

经过代码跟踪发现,Activity.mLastNonConfigurationInstances默认为null,在Activity.attach处赋值为方法参数lastNonConfigurationInstances,然后在Activity.performResume时重置为null

  • Activity.attach
final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
	...
	mLastNonConfigurationInstances = lastNonConfigurationInstances;
	...
}

Activity.attach在哪里被调用?

  • ActivityThread.performLaunchActivity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    // 反射构建activity
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    ...
    // 给activity绑定数据
    activity.attach(appContext, this, getInstrumentation(), r.token,
        r.ident, app, r.intent, r.activityInfo, title, r.parent,
        r.embeddedID, r.lastNonConfigurationInstances, config,
        r.referrer, r.voiceInteractor, window, r.configCallback,
        r.assistToken, r.shareableActivityToken);
    ...
    // 开始生命周期onCreate
    mInstrumentation.callActivityOnCreate(activity, r.state);
}

结论:

  1. 通过ViewModelProvider获取ViewModel时,会先询问ViewModelStoreOwner通过getViewModelStore获取ViewModelStore。
  2. FragmentActivity.getViewModelStore()内部会先向Activity.mLastNonConfigurationInstances.activity询问是否缓存了ViewModelStore,如果有就直接返回使用,内部的ViewModel信息也因此得到复用。
  3. Activity在关闭流程的源码ActivityThread.performDestroyActivity中会通过判断方法参数getNonConfigInstance是否为true,来将ViewModel及一些其他信息封装起来给到Activity对应的ActivityClientRecord缓存起来。

但是,任然有疑问

  1. getNonConfigInstance什么时候为true?
  2. 都说横竖屏切换后,ViewModel保持唯一,现在看来是因为ViewModelStore被ActivityClientRecord缓存起来了,但是,ActivityClientRecord中缓存的ViewModelStore是怎么流向Activity.mLastNonConfigurationInstances.activity中的呢?
5.4 研究横竖屏切换下通过ActivityClientRecord缓存数据ViewModelStore的详情

注意:ViewModelStore会被封装成不同Activity实现类的NonConfigurationInstances,然后继续封装成Activity.NonConfigurationInstances给到ActivityClientRecord.lastNonConfigurationInstances保存,所以此处只描述Activity.NonConfigurationInstances的横竖屏切换缓存流程即可

  • 先来关注下横竖屏切换时,Activity的生命周期
    为什么先onDestroy再onCreate

为什么是先onDestroy再onCreate?
我们都知道,横竖屏切换时,Framework层会执行ActivityThread.handleRelaunchActivity

  • ActivityThread.handleRelaunchActivity
public void handleRelaunchActivity(ActivityClientRecord tmp, PendingTransactionActions pendingActions) {
    ...
    ActivityClientRecord r = mActivities.get(tmp.token);
    handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
        pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
}

这里发现,获取了一个ActivityClientRecord,然后传入给handleRelaunchActivityInner

  • ActivityThread.handleRelaunchActivityInner
private void handleRelaunchActivityInner(ActivityClientRecord r, ...) {
    ...
    // 先执行Activity的 Destroy
    // getNonConfigInstance就在这里设置成true
    handleDestroyActivity(r, false, configChanges, true, reason); 
    ...
    // 再执行Activity的 Create
    handleLaunchActivity(r, pendingActions, customIntent);                   
}

注意

  1. handleDestroyActivity先于handleLaunchActivity执行,这就是为什么onDestroy先于onCreate执行原因
  2. 传入handleDestroyActivity的ActivityClientRecord和传入handleLaunchActivity的是同一个
  • ActivityThread.handleDestroyActivity
public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
    ....
    performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);                            
}

void performDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
    ...
    if (getNonConfigInstance) {
        ...
        r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
        ...
    }
    ...
}

构造了一个包含ViewModelStore的Activity.NonConfigurationInstances给到ActivityClientRecord

  • ActivityThread.handleLaunchActivity
public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {
   ...
   final Activity a = performLaunchActivity(r, customIntent);
   ...
}

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    ...
    activity.attach(appContext, this, getInstrumentation(), r.token,
        r.ident, app, r.intent, r.activityInfo, title, r.parent,
        r.embeddedID, r.lastNonConfigurationInstances, config,
        r.referrer, r.voiceInteractor, window, r.configCallback,
        r.assistToken, r.shareableActivityToken);
    ...
    mInstrumentation.callActivityOnCreate(activity, r.state)
    ...
}

从ActivityClientRecord中将Activity.NonConfigurationInstances拿出来通过Activity.attach重新set回Activity.mLastNonConfigurationInstances中

好啦,至此,ViewModelStore缓存与获取缓存形成闭环,撒花!!!

5.5. 总结

重点注意:

  1. ActivityThread.handleRelaunchActivityInner -> ActivityClientRecord -> handleDestroyActivity -> handleLaunchActivity
  2. handleDestroyActivity -> Activity.retainNonConfigurationInstances()
  3. handleLaunchActivity -> Activity.attach
  4. ViewModelProvider -> getViewModelStore -> getLastNonConfigurationInstance
  1. 横竖屏切换时,会执行ActivityThread.handleRelaunchActivityInner,其中获取ActivityClientRecord后先后执行handleDestroyActivity和handleLaunchActivity,切都将ActivityClientRecord作为参数传入二者
  2. 执行handleDestroyActivity时会通过判断getNonConfigInstance来决定是否调用Activity.retainNonConfigurationInstances()返回需要缓存的信息给给到ActivityClientRecord,其缓存信息内部就有Activity作用域下的ViewModelStore及其他信息
  3. 紧接着执行handleLaunchActivity,执行时会将ActivityClientRecord中缓存的信息通过Activity.attach传如并保存到Activity.mLastNonConfigurationInstances中保存起来
  4. 通过ViewModelProvider获取ViewModel时,会先向目标作用域的Activity通过getViewModelStore获取ViewModelStore,getViewModelStore会先通过Activity.getLastNonConfigurationInstance判断是否有缓存的ViewModelStore存在,如果存在就直接使用
  5. 经过上面的流程,保证Activity生命周期内,即使经过横竖屏切换,也能保证ViewModelStore的唯一性,从而保证ViewModel的唯一性
6. 生命周期管理

在这里插入图片描述

6.1 正常构建

通过ViewModelProvider构建ViewModel时,作用域会构建一个ViewModelStore,然后再通过工厂将对应ViewModel构建出来

6.2 横竖屏切换
  1. 旧Activity会将ViewModelStore打包缓存到ActivityClientRecord中
  2. 新Activity重建时,会从对应ActivityClientRecord中将打包的数据拿出来通过Activity.attach回set给新Activity
  3. 当通过ViewModelProvider获取ViewModel时,会先检查Activity中是否已经有进过上个流程回set进来的ViewModelStore,有的话就直接拿来用,里面的ViewModel自然也能得到复用
6.3 用户back键退出

ViewModel通过Activity内注册的LifecycleObserver进行生命周期管理

  • onDestroy执行时,如果不是横竖屏下,就直接释放ViewModel,然后清空ViewModelStore中HashMap
// ComponentActivity
public ComponentActivity() {
getLifecycle().addObserver(new LifecycleEventObserver() {
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            // Clear out the available context
            mContextAwareHelper.clearAvailableContext();
            // And clear the ViewModelStore
            if (!isChangingConfigurations()) {
                getViewModelStore().clear();
            }
        }
    }
});
}

// ViewModelStore
public final void clear() {
    for (ViewModel vm : mMap.values()) {
        vm.clear();// ViewModel释放
    }
    mMap.clear(); // ViewModel容器清空
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值