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

3,439 阅读9分钟

最近一直在用Google出那套android-architecture框架,感觉挺好用的,尤其喜欢Room。然后在用ViewModel的时候发现一个很有意思的现象,ViewModel不会随着onDestory的销毁而重建,搜了好多回答,都说到了Fragment.setRetainInstance(boolean},但本质原因说的都云里雾里的(因为现在版本Google已经完全移除了HolderFragment和静态变量的实现)。下面带大家刨根问底的具体弄清楚这到底是为什么。

    @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来判断。

    object.hashCode();
    System.identityHashCode(object);

所以,我们把代码做简单的改造下

    @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生命周期更长的对象持有了。

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

Activity的onRetainNonConfigurationInstance

Activity有个可以复写的方法叫:onRetainNonConfigurationInstance

    /**
     * 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)等等一大堆。那光说保存,那怎么拿到这个对象呢?是下面这个方法。

    /**
     * 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方法里。

    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}要准确的多)。

刨根问底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完美解决实例持有的问题。 如果还有什么疑问,欢迎评论留言。