刨根问底,为何屏幕旋转后Activity重建了,但ViewModel不会重建。

最近一直在用Google出那套android-architecture框架,感受挺好用的,尤为喜欢Room。而后在用ViewModel的时候发现一个颇有意思的现象,ViewModel不会随着onDestory的销毁而重建,搜了好多回答,都说到了Fragment.setRetainInstance(boolean},但本质缘由说的都云里雾里的(由于如今版本Google已经彻底移除了HolderFragment和静态变量的实现)。下面带你们刨根问底的具体弄清楚这究竟是为何。java

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ViewModelProvider provider = ViewModelProviders.of(this);
        mViewModel = provider.get(SimpleViewModel.class);
    }
复制代码

利用上面的代码能够很是简单的建立你须要的ViewModel,好比这里的SimpleViewModel。 咱们判断一个Java对象是否有变化咱们能够简单的经过hashCode来判断。固然,若是目标对象复写了hashCode,咱们能够经过System.identityHashCode来判断。android

object.hashCode();
    System.identityHashCode(object);
复制代码

因此,咱们把代码作简单的改造下git

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "activity: " + this.hashCode());
        ViewModelProvider provider = ViewModelProviders.of(this);
        Log.d(TAG, "provider: " + provider.hashCode());
        mViewModel = provider.get(SimpleViewModel.class);
        Log.d(TAG, "mViewModel: " + mViewModel.hashCode());
    }
复制代码

利用上面代码咱们能够很轻松的观察这些实例是否发生了变化。上面代码写好了,旋转一下屏幕,实际观察下旋转先后打印的日志(注意不要配置旋转时候不重建Activity)。很天然的,咱们发现Activity重建了,并且provider也重建了,可是很是神奇的是mViewModel竟然没有重建。 添加onDestroy的日志发现生命周期彻底正常。之前我写过一篇关于Activity的Activity源码分析里面详细说明了Activity在哪里建立的,以及为何onDestroy后Activity实例为何能被回收。这里顺着前面的思路,咱们猜下为何mViewModel没有被从新建立? mViewModel确定被比前一个被销毁的Activity生命周期更长的对象持有了。github

  • 那什么对象比Activity生命周期长呢?
  • 静态变量?
  • Application?

Activity的onRetainNonConfigurationInstance

Activity有个能够复写的方法叫:onRetainNonConfigurationInstancebash

/** * Called by the system, as part of destroying an * activity due to a configuration change, when it is known that a new * instance will immediately be created for the new configuration. You * can return any object you like here, including the activity instance * itself, which can later be retrieved by calling * {@link #getLastNonConfigurationInstance()} in the new activity * instance. * * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB} * or later, consider instead using a {@link Fragment} with * {@link Fragment#setRetainInstance(boolean) * Fragment.setRetainInstance(boolean}.</em> * * <p>This function is called purely as an optimization, and you must * not rely on it being called. When it is called, a number of guarantees * will be made to help optimize configuration switching: * <ul> * <li> The function will be called between {@link #onStop} and * {@link #onDestroy}. * <li> A new instance of the activity will <em>always</em> be immediately * created after this one's {@link #onDestroy()} is called. In particular, * <em>no</em> messages will be dispatched during this time (when the returned * object does not have an activity to be associated with). * <li> The object you return here will <em>always</em> be available from * the {@link #getLastNonConfigurationInstance()} method of the following * activity instance as described there. * </ul> * * <p>These guarantees are designed so that an activity can use this API * to propagate extensive state from the old to new activity instance, from * loaded bitmaps, to network connections, to evenly actively running * threads. Note that you should <em>not</em> propagate any data that * may change based on the configuration, including any data loaded from * resources such as strings, layouts, or drawables. * * <p>The guarantee of no message handling during the switch to the next * activity simplifies use with active objects. For example if your retained * state is an {@link android.os.AsyncTask} you are guaranteed that its * call back functions (like {@link android.os.AsyncTask#onPostExecute}) will * not be called from the call here until you execute the next instance's * {@link #onCreate(Bundle)}. (Note however that there is of course no such * guarantee for {@link android.os.AsyncTask#doInBackground} since that is * running in a separate thread.) * * <p><strong>Note:</strong> For most cases you should use the {@link Fragment} API * {@link Fragment#setRetainInstance(boolean)} instead; this is also * available on older platforms through the Android support libraries. * * @return any Object holding the desired state to propagate to the * next activity instance */
    public Object onRetainNonConfigurationInstance() {
        return null;
    }
复制代码

注释乌拉乌拉说了特别多,简单说就是这个方法若是返回了一个对象,那么这个对象会被保存起来。同时还说了一些兼容问题,Fragment须要本身调用setRetainInstance(boolean)等等一大堆。那光说保存,那怎么拿到这个对象呢?是下面这个方法。app

/** * Retrieve the non-configuration instance data that was previously * returned by {@link #onRetainNonConfigurationInstance()}. This will * be available from the initial {@link #onCreate} and * {@link #onStart} calls to the new instance, allowing you to extract * any useful dynamic state from the previous instance. * * <p>Note that the data you retrieve here should <em>only</em> be used * as an optimization for handling configuration changes. You should always * be able to handle getting a null pointer back, and an activity must * still be able to restore itself to its previous state (through the * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this * function returns null. * * <p><strong>Note:</strong> For most cases you should use the {@link Fragment} API * {@link Fragment#setRetainInstance(boolean)} instead; this is also * available on older platforms through the Android support libraries. * * @return the object previously returned by {@link #onRetainNonConfigurationInstance()} */
    @Nullable
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null
                ? mLastNonConfigurationInstances.activity : null;
    }
复制代码

呜呜啦啦一大堆。简单说就是能够经过这个方法拿到以前保存的实例。同时注意这个实例只能在onCreate onStart之间拿,onResume或者这以后就拿不到了。你们能够写一个最简单的Activity复写上面的方法尝试下效果。固然若是你继承的不是Activity而是AppCompatActivity,你会发现这个方法不能复写,由于这个方法被FragmentActivity标记为final了禁止你复写。框架

/** * Retain all appropriate fragment state. You can NOT * override this yourself! Use {@link #onRetainCustomNonConfigurationInstance()} * if you want to retain your own state. */
    @Override
    public final Object onRetainNonConfigurationInstance() {
        Object custom = onRetainCustomNonConfigurationInstance();

        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();

        if (fragments == null && mViewModelStore == null && custom == null) {
            return null;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
    }
复制代码

这里Google为了保证ViewModel相关功能的正常禁止你手动复写这个方法,由于你本身复写后返回新的对象会破坏ViewModel的保存实例的功能,但Google也给了咱们曲线救国的方法,复写onRetainCustomNonConfigurationInstance,固然与之对应的,拿实例时候就须要调用getLastCustomNonConfigurationInstance了。但愿你们都能本身尝试下,看看这样修改后,旋转屏幕,是否能在onCreate时候拿到以前保存的对象,同时仍是同一个实例。 而后这是保存,那ViewModel实际怎么恢复的呢?在FragmentActivity类的onCreate方法里。ide

static final class NonConfigurationInstances {
        Object custom;
        ViewModelStore viewModelStore;
        FragmentManagerNonConfig fragments;
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.attachHost(null /*parent*/);

        super.onCreate(savedInstanceState);

        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null && nc.viewModelStore != null && mViewModelStore == null) {
            mViewModelStore = nc.viewModelStore;
        }
        ......
    }
复制代码

这里Google经过getLastNonConfigurationInstance拿到以前保存的ViewModelStore,间接的把ViewModel的实例恢复了,并且没有重建。另外注意到里面的FragmentManagerNonConfig fragments;了吗?这个就是用来保持Fragment不重建的。 看到这里若是不关心背后更详细的实现,其实就差很少了,这已经很准确(比你搜到的其余单说Fragment.setRetainInstance(boolean}要准确的多)。oop

刨根问底onRetainNonConfigurationInstance背后发生了什么?

上面其实已经从API层面上解释的很清楚了,为啥ViewModel能恢复。但仍是不知足,看过以前Activity源码分析的都知道Activity的建立和销毁,那这里就有个疑问了,ViewModel没有被销毁是由于ViewModelStore,ViewModelStore没有被销毁是由于onRetainNonConfigurationInstance,那ViewModelStore从onRetainNonConfigurationInstance返回后,跑哪里去了,他到底被什么东西持有了致使下次Activity重建的时候还能从新还给Activity? 怎么找呢?咱们先看下onRetainNonConfigurationInstance回调放生时候主线程的方法堆栈。源码分析

at com.aesean.SimpleActivity.onRetainCustomNonConfigurationInstance(LoginActivity.java:191)
	  at androidx.fragment.app.FragmentActivity.onRetainNonConfigurationInstance(FragmentActivity.java:569)
	  at android.app.Activity.retainNonConfigurationInstances(Activity.java:2423)
	  at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:4469)
	  at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4515)
	  at android.app.ActivityThread.handleRelaunchActivityInner(ActivityThread.java:4799)
	  at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4732)
	  at android.app.servertransaction.ActivityRelaunchItem.execute(ActivityRelaunchItem.java:69)
	  at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
	  at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
	  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
	  at android.os.Handler.dispatchMessage(Handler.java:106)
	  at android.os.Looper.loop(Looper.java:193)
	  at android.app.ActivityThread.main(ActivityThread.java:6718)
	  at java.lang.reflect.Method.invoke(Method.java:-1)
	  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
	  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
复制代码

这里咱们很清楚的看到这个方法回调是ActivityThread.performDestroyActivity的时候被调用的,咱们看下ActivityThread(这个类源码在AndroidSdk里有,双击Shift输入ActivityThread能够找到这个类,若是找不到能够先打开Activity的源码,而后直接Ctrl+F查找ActivityThread,找到后直接Ctrl+B打开ActivityThread)

/** Core implementation of activity destroy call. */
    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        ...
            if (getNonConfigInstance) {
                try {
                    r.lastNonConfigurationInstances
                            = r.activity.retainNonConfigurationInstances();
                } catch (Exception e) {
            ...
            mInstrumentation.callActivityOnDestroy(r.activity);
            ...
        ...
        mActivities.remove(token);
        ...
        return r;
    }
复制代码

我已经把主要代码贴出来了,能够清楚的看到在Activity的onDestroy被调用以前回调了retainNonConfigurationInstances,这时候把咱们返回的实例对象赋值给了r.lastNonConfigurationInstances,r是ActivityClientRecord,他直接持有Activity等Activity相关的几乎全部信息。看到这里咱们知道r间接持有了ViewModel,因此只要r能在下次新Activity建立后回传给新Activity,那么咱们在onCreate里就能够拿到同一个ViewModel实例了。看前面的方法堆栈, -> performDestroyActivity -> handleDestroyActivity -> handleRelaunchActivityInner -> handleRelaunchActivity performDestroyActivity执行后会向下一层层返回。咱们详细看下handleRelaunchActivityInner

private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges, List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents, PendingTransactionActions pendingActions, boolean startsNotResumed, Configuration overrideConfig, String reason) {
        ...
        handleDestroyActivity(r.token, false, configChanges, true, reason);
        ...
        handleLaunchActivity(r, pendingActions, customIntent);
        ...
复制代码

这里handleRelaunchActivityInner把r.token传给了handleDestroyActivity又继续传给了performDestroyActivity

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        ...
    }
复制代码

ActivityClientRecord r = mActivities.get(token); 这一行拿到的r其实和handleRelaunchActivityInner里的r是同一个。因此handleLaunchActivity这里的r并非新建的,而是刚刚保存了咱们的lastNonConfigurationInstances实例的r,并且这个r又传了新建的Activity。

@Override
    public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {
        ...
        final Activity a = performLaunchActivity(r, customIntent);
        ...
    }
复制代码
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);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }
                r.lastNonConfigurationInstances = null;
       ...
    }
复制代码

这里把r.lastNonConfigurationInstances传给了activity的attach方法,同时r清除了lastNonConfigurationInstances的引用。

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) {
        ...
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
        ...
   }
复制代码

这里mLastNonConfigurationInstances实际拿到了lastNonConfigurationInstances,此时只有新Activity经过mLastNonConfigurationInstances持有了lastNonConfigurationInstances。那前面为何说只能在onCreate和onStart拿到ViewModelStore呢?

final void performResume(boolean followedByPause, String reason) {
        ...
        mLastNonConfigurationInstances = null;
        ...
   }
复制代码

Activity的performResume里把mLastNonConfigurationInstances重置为了null。

总结

好了到这里算是完全从源码层面一层层刨根问底,完全把这个事情讲清楚了。简单总结下,之因此旋转屏幕ViewModel没有重建是由于屏幕旋转的时候发生了Activity的RELAUNCH,在RELAUNCH的时候ActivityClientRecord会被复用,同时ViewModelStore会被保存在ActivityClientRecord里,当新Activity被LAUNCH的时候复用的ActivityClientRecord把持有的ViewModelStore传给了新Activity,而后FragmentActivity拿到了以前保存的ViewModelStore实例,而后Activity重建完成,但ViewModel并无重建。再回到文章开头,

  • 那什么对象比Activity生命周期长呢? RELAUNCH时候的ActivityClientRecord
  • 静态变量? 显然保存ViewModel并无用到静态变量
  • Application? 显然保存ViewModel也没有用到Application

另外,为何你搜到的不少文章都告诉你ViewModel实例不重建是由于Fragment.setRetainInstance(boolean}呢?主要是由于早期ViewModel实现仍是经过HolderFragment和静态变量实现的,但如今已经彻底移除了HolderFragment。很显然如今的实现要比HolderFragment好的多,如今的版本彻底没有使用任何静态变量,利用了Api1就已经出现的onRetainCustomNonConfigurationInstance完美解决实例持有的问题。 若是还有什么疑问,欢迎评论留言。

相关文章
相关标签/搜索