Android onSaveInstanceState/onRe

作者: 小鱼人爱编程 | 来源:发表于2021-12-12 20:10 被阅读0次

    前言

    前些天,有位小伙伴兴匆匆地跑过来给我展示一个现象: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 实践与原理系列

    相关文章

      网友评论

        本文标题:Android onSaveInstanceState/onRe

        本文链接:https://www.haomeiwen.com/subject/pluzxrtx.html