ViewModel源码分析(二)

639 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第16天,点击查看活动详情

ViewModel的存储

我们知道当配置发生变化或者屏幕旋转时候,Activity是会执行销毁重建的,通常需要保存数据时传统的方式我们需要在onSaveInstanceState页面销毁的时候保存一次,在onRestoreInstanceState页面重建的时候恢复数据。但是由于Bundle只能保存轻量的数据,所以经常无法满足需求。但是ViewModel的出现刚好解决了这类问题,接下来让我们一起来了解一下ViewModel是如何恢复数据的。

performDestroyActivity

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

Activity.retainNonConfigurationInstances

NonConfigurationInstances retainNonConfigurationInstances() {
		// 子类ComponentActivity来实现
        Object activity = onRetainNonConfigurationInstance();
		...
        NonConfigurationInstances nci = new NonConfigurationInstances();
        // 根据子类我们可以得到viewModelStore就存在当前activity对象中
        nci.activity = activity;
        nci.children = children;
        nci.fragments = fragments;
        nci.loaders = loaders;
        if (mVoiceInteractor != null) {
            mVoiceInteractor.retainInstance();
            nci.voiceInteractor = mVoiceInteractor;
        }
        return nci;
    }

ComponentActivity.onRetainNonConfigurationInstance()

// ComponentActivity
 public final Object onRetainNonConfigurationInstance() {
        ...
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        return nci;
}

屏幕在旋转的时候会调用ComponentActivity中的onRetainNonConfigurationInstance,这里我们可以看到当前activity的viewModelStore是被保存到类型为NonConfigurationInstances的对象当中,根据ViewModel的创建,我们知道ViewModel对象是被保存在ViewModelStore中的。也就是说我们如果要在重建后获取ViewModel对象,我们需要得到这个NonConfigurationInstances对象。

ComponentActivity.getViewModelStore()

 public ViewModelStore getViewModelStore() {
        ...
        ensureViewModelStore();
        return mViewModelStore;
    }
  
void ensureViewModelStore() {
		// mViewModelStore第一次进入activity的时候一定是空的
    	// 那么屏幕旋转不是第一进入,mViewModelStore就不为空
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                mViewModelStore = nc.viewModelStore;
            }
            // 如果nc为空,才会执行ViewModelStore的创建
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }

根据之前的了解,ViewModel创建时会调用getViewModelStore方法,当重建时由于mViewModelStore已经在旋转前就创建了,所以这里会执行getLastNonConfigurationInstance

Activity.getLastNonConfigurationInstance()

 public Object getLastNonConfigurationInstance() {
 		// mLastNonConfigurationInstances为空时即返回空
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }

接下来我们需要找到mLastNonConfigurationInstances对象当前是否为空,它的赋值是在Activity.attach()方法中。那么attach方法又是在什么情况下调用的呢?

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) {
       ...
        // mLastNonConfigurationInstances初始化
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        ...
    }

根据attach方法的实现源码分析,mLastNonConfigurationInstances是由参数列表的lastNonConfigurationInstances参数所赋值而来,那么我们需要知道调用attach的地方是在哪?参数列表中的lastNonConfigurationInstances又是那个值?

ActivityThread.performLaunchActivity()

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
     ...
     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);
     ...
 }    

根据Activity的创建都是由ActivityThread线程进行操作的,那么我们不难猜到attach是在ActivityThread中调用的。根据源码分析我们得到,lastNonConfigurationInstances是通过参数列表的r所提供。那么我们不难得出当屏幕在旋转时mLastNonConfigurationInstances是非空状态。那么getLastNonConfigurationInstance结果就一定是非空,也就是说getViewModelStore得到的ViewModelStore是缓存在NonConfigurationInstances中的,也就是屏幕切换前的由onRetainNonConfigurationInstance方法保存的ViewModelStore,至此我们就可以得出结论viewmodel在屏幕切换前后都为同一个,所以才可以做到数据恢复。

ViewModel的销毁

根据ViewModel的生命周期我们知道,它的销毁和activity的生命周期一样。 viewmodel生命周期.png

ComponentActivity构造

getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                    // 生命周期事件为ON_DESTROY
                if (event == Lifecycle.Event.ON_DESTROY) {
                   	...
                    if (!isChangingConfigurations()) {
                    	//出发ViewModelStore的清除
                        getViewModelStore().clear();
                    }
                }
            }
        });

当activity生命周期结束时,ViewModelStore便会执行clear方法

ViewModelStore.clear()

   private final HashMap<String, ViewModel> mMap = new HashMap<>();

  public final void clear() {
        for (ViewModel vm : mMap.values()) {
        // viewmodel销毁之前需要执行的操作
            vm.clear();
        }
        // viewmodel的销毁
        mMap.clear();
    }

ViewModel的创建我们知道,当ViewModel被创建之后会被putHashMap中,当ViewModelStoreclear方法触达时遍历HashMap进行销毁前操作,最后一次性清除HashMap

ViewModel.clear()

@MainThread
final void clear() {
    mCleared = true;
    if (mBagOfTags != null) {
        synchronized (mBagOfTags) {
            for (Object value : mBagOfTags.values()) {
                // 关闭协程
                closeWithRuntimeException(value);
            }
        }
    }
    onCleared();
}