Android onSaveInstanceState/onRestoreInstanceState 原来要这么理解

1,877

前言

系列文章:

Android Activity 与View 的互动思考
Android Activity 生命周期详解及监听
Android onSaveInstanceState/onRestoreInstanceState 原来要这么理解
Android Fragment 要你何用?
Android Fragment 要你何用?2.0版本
Android Activity/View/Window/Dialog/Fragment 深层次关联(白话解析)

前些天,有位小伙伴兴匆匆地跑过来给我展示一个现象:Activity 里有个EditText,点击该EditText 输入一些文字。此时,转动手机方向,Activity 变成横屏了,而EditText 上的文字依然保留。
问我:为啥EditText上文字能够恢复?
我说:你Activity 配置了横竖屏切换时不重建Activity。
他立马给我展示了:Activity 重建的日志。
我说:系统会在重建Activity 的时候恢复整个ViewTree吧。
他又给我展示了:ImageView 横竖屏时没有恢复之前的图像。
我:...
不服输的我开始了默默地研究,于是有了这篇总结以解心中困惑。
通过本篇文章,你将了解到:

1、onSaveInstanceState/onRestoreInstanceState 作用。
2、onSaveInstanceState/onRestoreInstanceState 原理分析
3、onSaveInstanceState/onRestoreInstanceState 触发场景。
4、onSaveInstanceState/onRestoreInstanceState 为啥不能存放大数据?
5、与Jetpack ViewModel 区别。

1、onSaveInstanceState/onRestoreInstanceState 作用

EditText/ImageView 横竖屏地表现

tt0.top-423136.gif

可以看出,从竖屏到横屏再恢复到竖屏,EditText 内容没有变化。而从竖屏到横屏时,ImageView 内容已经丢失了。
都是系统控件,咱们也没有进行其它的额外区别处理,为啥表现不一致呢?
View.java 里有两个方法:

#View.java
    protected Parcelable onSaveInstanceState() {...}

    protected void onRestoreInstanceState(Parcelable state){...}

官方注释上写的比较清楚了:

1、onSaveInstanceState 是个钩子方法,View.java 的子类可以重写该方法,在方法里面存储一些子类的内部状态,用以下次重建时恢复。
2、onRestoreInstanceState 也是个钩子方法,用以恢复在onSaveInstanceState 里保存的状态。

既然是View的方法,分别查看EditText 与ImageView 对它们的重写情况:

#TextView.java
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        ...
        if (freezesText || hasSelection) {
            SavedState ss = new SavedState(superState);

            if (freezesText) {
                if (mText instanceof Spanned) {
                    final Spannable sp = new SpannableStringBuilder(mText);
                    ...
                    ss.text = sp;
                } else {
                    //将TextView 内容存储在SavedState里
                    ss.text = mText.toString();
                }
            }
            ...
            return ss;
        }

        //返回存储的对象
        return superState;
    }

    public void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }

        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());

        if (ss.text != null) {
            //取出TextView 内容,并设置
            setText(ss.text);
        }
        ...
    }

由此可见,TextView 重写这俩方法,先是在onSaveInstanceState 里存储文本内容,再在onRestoreInstanceState 里恢复文本内容。
而通过查看ImageView 发现它并没有重写这俩方法,当然就不能恢复了。其实这也比较容易理解,毕竟对于ImageView,Bitmap 是它的内容,暂存这个Bitmap 很耗内存。

需要注意的是:想要onSaveInstanceState 被调用,则需要给该控件设置id。因为系统是根据View id将状态存储在SparseArray 里

Activity 横竖屏的处理

现在的问题是:谁调用了View 的onSaveInstanceState/onRestoreInstanceState ? 在前一篇分析过Activity 和View的关系:Android Activity 与View 的互动思考
可知,Activity 通过Window 控制View,我们子类继承自EditText,并重写 onSaveInstanceState/onRestoreInstanceState,然后在横竖屏切换时查看这俩方法的调用栈:

image.png

第一个红色框表示EditText子类里的方法(onSaveInstanceState),而第二个红框表示Activity 子类里重写的方法(onSaveInstanceState)。
由此可知,当横竖屏切换时调用了Activity.onSaveInstanceState(xx) 方法。

#Activity.java
    protected void onSaveInstanceState(@NonNull Bundle outState) {
        //saveHierarchyState 调用整个ViewTree 的onSaveInstanceState 方法
        outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
        ...
        //告知生命周期回调方法状态已保存
        dispatchActivitySaveInstanceState(outState);
    }

同样的对于onRestoreInstanceState:

#Activity.java
    protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
        if (mWindow != null) {
            Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
            if (windowState != null) {
                //恢复整个ViewTree 状态
                mWindow.restoreHierarchyState(windowState);
            }
        }
    }

当横竖屏切换时,会调用到Activity onSaveInstanceState/onRestoreInstanceState 方法,进而会调用整个ViewTree onSaveInstanceState/onRestoreInstanceState 方法来保存与恢复必要的状态。

Activity 数据保存与恢复

Activity 的onSaveInstanceState/onRestoreInstanceState 方法 除了触发View 的状态保存与恢复外,还可以将Activity 用到的一些重要的数据保存下来,待下次Activity 重建时恢复。
重写两者:

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("say", "hello world");
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        String restore = savedInstanceState.getString("say");
        Log.d("fish", restore);
    }

此时我们注意到onSaveInstanceState 的入参是Bundle 类型,往outState 写入数据,在onRestoreInstanceState 将数据取出,outState/savedInstanceState 必然不为空。

总结 onSaveInstanceState/onRestoreInstanceState 作用

1、保存与恢复View 的状态。
2、保存与恢复Activity 自定义数据。

2、onSaveInstanceState/onRestoreInstanceState 原理分析。

onSaveInstanceState 调用时机

之前 Android Activity 生命周期详解及监听 有详细分析了Activity 各个阶段的调用情况,现在结合生命周期来分析onSaveInstanceState(xx)在生命周期中的哪个阶段被调用的。
调用栈如下:

image.png

看看上图标黄色的方法,这方法很眼熟,在Activity 生命周期中分析过,它是Activity.onStop()方法的调用者:

#ActivityThread.java
    private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) {
        // Before P onSaveInstanceState was called before onStop, starting with P it's
        // called after. Before Honeycomb state was always saved before onPause.
        //这句话翻译过来:
        //如果目标设备是Android 9之前,那么onSaveInstanceState 在onStop 之前调用
        //如果在Android 9 之后,那么onSaveInstanceState 在onStop 之后调用
        //Honeycomb 指的是Android 3.0 现在基本可以忽略了。
        //r.activity.mFinished 表示Activity 是否即将被销毁
        final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null
                && !r.isPreHoneycomb();
        final boolean isPreP = r.isPreP();
        //Android p 之前先于onStop 之前执行
        if (shouldSaveState && isPreP) {
            callActivityOnSaveInstanceState(r);
        }
        try {
            //最终执行到Activity.onStop()方法
            r.activity.performStop(r.mPreserveWindow, reason);
        } catch (SuperNotCalledException e) {
            ...
        }
        //标记Stop状态
        r.setState(ON_STOP);

        if (shouldSaveState && !isPreP) {
            //调用onSave 保存
            callActivityOnSaveInstanceState(r);
        }
    }

以上注释比较详细了,小结一下:

1、在Android 9之前,onSaveInstanceState 在onStop 之前调用(至于在onPause 之前还是之后调用,这个时机不确定);在Android 9(包含)之后,onSaveInstanceState 在onStop 之后调用。
2、如果Activity 即将被销毁,则onSaveInstanceState 不会被调用。

对于第二句的理解,举个简单例子:

Activity 在前台时,此时按Home键回到桌面,会执行onSaveInstanceState;若是按back键/主动finish,此时虽然会执行到onStop,但是不会执行onSaveInstanceState。

onRestoreInstanceState 调用时机

现在已经弄清楚onSaveInstanceState 调用时机,接着来分析 onRestoreInstanceState 什么时候执行。
调用栈如下:

image.png

黄色部分的方法也很眼熟,它是Activity.onStart()方法的调用者:

    public void handleStartActivity(ActivityClientRecord r,
                                    PendingTransactionActions pendingActions) {
        final Activity activity = r.activity;
        ...
        //最终执行到Activity.onStart()
        activity.performStart("handleStartActivity");
        r.setState(ON_START);
        ...
        if (pendingActions.shouldRestoreInstanceState()) {
            if (r.isPersistable()) {
                //从持久化存储里恢复数据
                if (r.state != null || r.persistentState != null) {
                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                            r.persistentState);
                }
            } else if (r.state != null) {
                //从内存里恢复数据
                mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
            }
        }
        ...
    }

小结:

1、onRestoreInstanceState 在onStart()方法之后执行。
2、pendingActions.shouldRestoreInstanceState() 返回值是执行 onRestoreInstanceState()方法的关键,它是在哪赋值的呢?接下来会分析。
3、r.state 不能为空,毕竟没数据无法恢复。

通过以上分析,结合Activity生命周期,onSaveInstanceState /onRestoreInstanceState 调用时机如下:

image.png

onRestoreInstanceState 与onCreate 参数差异

onCreate参数也是Bundle类型,实际上这个参数就是onSaveInstanceState里保存的Bundle,这个Bundle分别传递给了onCreate和onRestoreInstanceState,而onCreate里的Bundle可能为空(新建非重建的情况下),onRestoreInstanceState 里的Bundle必然不为空。
官方注释也说了在onRestoreInstanceState里处理数据的恢复更灵活。

3、onSaveInstanceState/onRestoreInstanceState 触发场景

横竖屏触发的场景

在前面的分析中,与Activity 生命周期关联可能会让人有种印象:
onSaveInstanceState 调用之后onRestoreInstanceState 就会被调用。
而事实并非如此,举个简单例子:
Activity 处在前台时,此时退回到桌面,onSaveInstanceState 会被执行。而后再让Activity 回到前台,onStart()方法执行后,发现onRestoreInstanceState 并没有被调用。

也就是说onSaveInstanceState/onRestoreInstanceState 的调用不一定是成对出现的。

还记得在分析onRestoreInstanceState 遗留了个问题: pendingActions.shouldRestoreInstanceState() 返回值如何确定的 ?
在横竖屏切换时,onRestoreInstanceState 被调用了,说明 pendingActions.shouldRestoreInstanceState() 在横竖屏切换时返回了true,接着来看看其来龙去脉:

#PendingTransactionActions.java
    //判断是否需要执行onRestoreInstanceState 方法
    public boolean shouldRestoreInstanceState() {
        return mRestoreInstanceState;
    }

    //设置标记
    public void setRestoreInstanceState(boolean restoreInstanceState) {
        mRestoreInstanceState = restoreInstanceState;
    }

只需要找到setRestoreInstanceState()在何处调用即可。
直接说结论:

ActivityThread.handleLaunchActivity() 里设置了setRestoreInstanceState(true)

而handleLaunchActivity()在两种情况下被调用:

image.png

横竖屏时属于重建 Activity,因此onRestoreInstanceState 能被调用。
而从后台返回到前台,并没有新建Activity也没有重建Activity,因此onRestoreInstanceState 不会被调用。
又引申出另一个问题:为啥新建Activity 时onRestoreInstanceState 没被调用?
答案:因为新建Activity 时,ActivityClientRecord 是全新的对象,它所持有的Bundle state 对象为空,因此不会调用到onRestoreInstanceState。

其它配置项更改的场景

除了横竖屏切换时会重建Activity,还有以下配置项更改会重建Activity:

image.png

当然,还有一些不常涉及的配置项,比如所在地区更改等。

重建Activity 的细节

image.png

当需要重建Activity 时,AMS 发出指令,会执行到 ActivityThread.handleRelaunchActivity()方法。

#ActivityThread.java
    public void handleRelaunchActivity(ActivityClientRecord tmp,
                                       PendingTransactionActions pendingActions) {
        ...
        //从Map 里获取缓存的ActivityClientRecord
        ActivityClientRecord r = mActivities.get(tmp.token);
        ...
        //将ActivityClientRecord 传递下去
        handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
        ...
    }

mActivities 定义如下:

final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();

以IBinder 为key,存储ActivityClientRecord。
当新建Activity 时,存入ActivityClientRecord,当销毁Activity 时,移除 ActivityClientRecord。

再来分析handleRelaunchActivityInner():

#ActivityThread.java
    private void handleRelaunchActivityInner(...) {
        ...
        if (!r.paused) {
            //最终执行到onPause
            performPauseActivity(r, false, reason, null /* pendingActions */);
        }
        if (!r.stopped) {
            //最终执行到onStop
            callActivityOnStop(r, true /* saveState */, reason);
        }
        //最终执行到onDestroy
        handleDestroyActivity(r.token, false, configChanges, true, reason);
        //创建新的Activity 实例
        handleLaunchActivity(r, pendingActions, customIntent);
    }

通过分析Activity 重建的细节,有以下结论:

1、Activity 重建过程中,先将原来的Activity 进行销毁(从onResume--onStop-->onDestroy 的生命周期)。
2、虽然是不同的Activity 对象,但重建时使用的ActivityClientRecord 却是相同的,而ActivityClientRecord 最终是被ActivityThread 持有,它是全局的。这也是 onSaveInstanceState/onRestoreInstanceState 能够存储与恢复数据的本质原因。

当然也可以通过配置告诉系统在配置项变更时不重建Activity:

<activity android:name=".viewmodel.ViewModelActivity" android:configChanges="orientation|screenSize"></activity>

比如以上配置,当横竖屏切换时,不会重建Activity,而配置项的变更会通过 Activity.onConfigurationChanged()方法回调。

4、onSaveInstanceState/onRestoreInstanceState 为啥不能存放大数据?

onSaveInstanceState/onRestoreInstanceState 的参数都是Bundle 类型,思考一下为什么需要定义为Bundle类型呢?
Android IPC 精讲系列 中有提到过,Android 进程间通信方式大多时候使用的是Binder,而要想自定义数据能够通过Binder传输则需要实现Parcelable 接口,Bundle 实现了Parcelable 接口。

由此我们推测,onSaveInstanceState/onRestoreInstanceState 可能涉及到进程间通信,才会用Bundle 来修饰形参。但之前说的ActivityClientRecord是存储在当前进程的啊,貌似和其它进程没有关联呢?
要分析这个问题,实际上只需要在onSaveInstanceState 存储一个比较大的数据,看看报错时的堆栈。

    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putString("say", "hello world");
        //存储2M 数据
        outState.putByteArray("big", new byte[1024*1024*2]);
    }

保存2M 的数据,通常来说这是超出了Binder的限制,当调用onSaveInstanceState 时会有报错信息:

image.png

果然还是crash了。
找到 PendingTransactionActions ,它实现了Runnable 接口,在其run方法里:

#PendingTransactionActions.java
    public void run() {
        try {
            //提交给ActivityTaskManagerService 处理,属于进程间通信
            //mState 即是onSaveInstanceState 保存的数据
            ActivityTaskManager.getService().activityStopped(
                    mActivity.token, mState, mPersistentState, mDescription);
        } catch (RemoteException ex) {
            ...
        }
    }

而在ActivityThread.java 里有个方法:

    public void reportStop(PendingTransactionActions pendingActions) {
        mH.post(pendingActions.getStopInfo());
    }

该方法用于告知系统,咱们的Activity 已经变为Stop状态了,最终会执行到PendingTransactionActions.run()方法。
小结一下:

onSaveInstanceState 存储的数据,在onStop执行后,当前进程需要将Stop状态传递给ATM(ActivityTaskManagerService 运行在system_server进程),因为跨进程传递(Binder)有大小限制,因此onSaveInstanceState 不能传递大量数据。

5、与Jetpack ViewModel 区别

onSaveInstanceState 与 ViewModel 都是将数据放在ActivityClientRecord 的不同字段里。

image.png

1、onSaveInstanceState 用Bundle存储数据便于跨进程传递,而ViewModel 是Object存储数据,不需要跨进程,因此它没有大小限制。
2、onSaveInstanceState 在onStop 之后调用,比较频繁。而ViewModel 存储数据是onDestroy 之后。
3、onSaveInstanceState 可以选择是否持久化数据到文件里(该功能由ATM 实现,存储到xml里),而ViewModel 没有这功能。

更多的区别后续分析 ViewModel 时会提到。 测试地址

本文基于Android 10.0。

您若喜欢,请点赞、关注,您的鼓励是我前进的动力

持续更新中,和我一起步步为营系统、深入学习Android

1、Android各种Context的前世今生
2、Android DecorView 必知必会
3、Window/WindowManager 不可不知之事
4、View Measure/Layout/Draw 真明白了
5、Android事件分发全套服务
6、Android invalidate/postInvalidate/requestLayout 彻底厘清
7、Android Window 如何确定大小/onMeasure()多次执行原因
8、Android事件驱动Handler-Message-Looper解析
9、Android 键盘一招搞定
10、Android 各种坐标彻底明了
11、Android Activity/Window/View 的background
12、Android Activity创建到View的显示过
13、Android IPC 系列
14、Android 存储系列
15、Java 并发系列不再疑惑
16、Java 线程池系列
17、Android Jetpack 前置基础系列
18、Android Jetpack 易学易懂系列