Activity的内部实例保存-onSaveInstanceState

本文详细探讨了Android中Activity的onSaveInstanceState机制,分析了何时系统会调用此方法以及如何保存和恢复实例状态。文章指出,通过onSaveInstanceState可以在Activity销毁时保存数据,并在恢复时恢复,避免了实例变量丢失的问题,特别讨论了不同场景下系统调用此方法的情况,并结合源码解析了保存状态的条件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

在android 中,acticity是一个常用的组件,但这个组件又是那么的脆弱,由于android的LMK(Low Memory Killer)机制 和ConfigurationChange(例如屏幕旋转等配置变化)都会使其当前的Activity销毁。然而在一些开发场景中需要在Activity中存储一些实例变量,并在Activity销毁了也能保持实例变量不会被销毁,等待Activity恢复后继续使用实例变量,但实际上那些实例变量同样也会随Activity销毁而销毁。(如果没有销毁只能说明内存泄漏了,当前Activity的引用被其他对象持有)

这很显然不是我们想要的结果,有人说可以使用单例或者在Application添加实例变量;(我以前就是这么干的,漂亮 ^ - ^)

首先这样确实能满足要求,但这些实例什么时候回收呢?

比较讲究的A同学可能会在业务逻辑上清空变量(例如在用户主动退出Activity),不讲究的B同学就不管回收问题,反正能正常使用,为什么要回收( *︾▽︾)

B同学这样看起来好像没有问题,但如果这种场景比较多,存储的实例变量很多并且还有一些大胖子变量(例如Bitmap),此时OOM已经准备和你招手了,随时崩掉你的应用。
A同学做法一般没有问题,为什么说一般,因为每个Activity的逻辑都要清空实例变量,如果露掉一两个或者因为自己笔误导致没有执行清空实例变量的逻辑,这样和B同学没太大区别,当然可以自己定义一个BaseActivity 统一处理清空实例变量,恩应该没有问题了,但很繁琐,需要自己映射每个activity对应的实例变量。

所以Android为了应对这类场景也有相应的解决方案,这里也是我特此引出介绍的onSaveInstanceState

onSaveInstanceState

先不看资料和注释,字面意思理解:在保存一个实例状态。
在查看相关注释和介绍大概也是这样理解的,会在Activity被系统杀死的时候触发此方法调用,可以在onCreate(Bundle)或onRestoreInstanceState(Bundle)中还原该状态(此方法填充的Bundle将传递给这两个方法)。

那么具体哪些场景会调用此方法呢?
我随便搜索了一下大同小异,我不做过多介绍

1、当用户按下HOME键时
这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity
A是否会被销毁,故系统会调用onSaveInstanceState,让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则
2、长按HOME键,选择运行其他的程序时。
3、按下电源按键(关闭屏幕显示)时。
4、从activity A中启动一个新的activity时。
5、屏幕方向切换时,例如从竖屏切换到横屏时
在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState一定会被执行

以上是从现象上和根据官方注释说明总结的几种情况,具体代码是如何运行的呢?
通过查看android4.4.3源码在这里上一张图
onSaveInstanceState
在执行onSaveInstanceState时的堆栈信息如下
ActivityThread.handleStopActivity
ActivityThread.performStopActivityInner
Instrumentation.callActivityOnSaveInstanceState
Activity.performSaveInstanceState
Activity.onSaveInstanceState
这里着重看callActivityOnSaveInstanceState 这个方法,因为调用此方法就代表会调用Activity的onSaveInstanceState方法
此方法调用在ActivityThread类有三处
1. performPauseActivity
此方法是用于pause 指定的activity

final Bundle performPauseActivity(ActivityClientRecord r, boolean finished,
            boolean saveState)
省略...
            if (!r.activity.mFinished && saveState) {
                state = new Bundle();
                state.setAllowFds(false);
                mInstrumentation.callActivityOnSaveInstanceState(r.activity, state);
                r.state = state;
            }

这里有两个关键的变量r.activity.mFinished 和saveState
mFinished 通过上下文可以先理解为Activity准备结束销毁,后面统一解释
saveState 判断是否需要保存状态
连起来理解:要满足执行保存数据必须在activity未销毁的时候并且允许保存状态。
先看saveState这个参数代码来源

    final Bundle performPauseActivity(IBinder token, boolean finished,
            boolean saveState) {
        ActivityClientRecord r = mActivities.get(token);
        return r != null ? performPauseActivity(r, finished, saveState) : null;
    }

1.1分支 performPauseActivity 在handlePauseActivity内执行

    private void handlePauseActivity(IBinder token, boolean finished,
            boolean userLeaving, int configChanges) {
省略...
            performPauseActivity(token, finished, r.isPreHoneycomb());

1.2分支 performPauseActivity 在handleRelaunchActivity内执行

    private void handleRelaunchActivity(ActivityClientRecord tmp) {
省略...
        // Need to ensure state is saved.
        if (!r.paused) {
            performPauseActivity(r.token, false, r.isPreHoneycomb());
        }

两个执行分支都是通过r.isPreHoneycomb()来判断saveState是否需要保存状态
r.isPreHoneycomb()内部是干什么的下面查看源码,是内部类ActivityClientRecord的一个方法

        public boolean isPreHoneycomb() {
            if (activity != null) {
                return activity.getApplicationInfo().targetSdkVersion
                        < android.os.Build.VERSION_CODES.HONEYCOMB;
            }
            return false;
        }

主要是判断当前Activity的应用sdk版本是否小于android.os.Build.VERSION_CODES.HONEYCOMB,如果小于则为true,否则为false
查看定义 是api 11, 是Android 3.0. 我们使用的是android 4.4.3 api 19,肯定是大于的,所以isPreHoneycomb 一定是false。
由此可得出saveState也是false,那么不管r.activity.mFinished是什么,都不会执行保存状态的方法。

2. handleRelaunchActivity
此方法用于重启actvity

    private void handleRelaunchActivity(ActivityClientRecord tmp) {
省略...
        if (r.state == null && !r.stopped && !r.isPreHoneycomb()) {
            r.state = new Bundle();
            r.state.setAllowFds(false);
            mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
        }

这里同样也有一个r.isPreHoneycomb()方法调用,同样也一定是false
另外两个变量
r.state 是保存状态时最后传递给Activity的Bundle参数,在performPauseActivity 和performStopActivityInner方法 中,如果要执行callActivityOnSaveInstanceState,必然会紧接着执行r.state = state; 此时state就不为空了
r.stopped 是指acticity是否stop的

连起来理解必须要state为空(没有在pause 和stop时执行保存状态callActivityOnSaveInstanceState),并且是activity处于非stop的生命周期才会执行

3. performStopActivityInner
此方法用stop activty

    private void performStopActivityInner(ActivityClientRecord r,
            StopInfo info, boolean keepShown, boolean saveState) {
省略...
            if (!r.activity.mFinished && saveState) {
                if (r.state == null) {
                    state = new Bundle();
                    state.setAllowFds(false);
                    mInstrumentation.callActivityOnSaveInstanceState(r.activity, state);
                    r.state = state;
                } else {
                    state = r.state;
                }
            }

和performPauseActivity方法相似,必须判断非r.activity.mFinished 和是保存状态
还是先看这里的saveState的参数来源

分支1 handleWindowVisibility

    private void handleWindowVisibility(IBinder token, boolean show) {
省略...
        if (!show && !r.stopped) {
            performStopActivityInner(r, null, show, false);

saveState 直接传递false不做保存

分支2 handleStopActivity

    private void handleStopActivity(IBinder token, boolean show, int configChanges) {
省略...
        performStopActivityInner(r, info, show, true);

这里saveState直接设置为true,那么是否能保存状态完全取决于r.activity.mFinished这个变量了,在这里我们看看这个变量的代码变化位置
mFinished这个变量是Activity的成员变量

finish 方法
用于需要用户主动关闭Activity调用

    public void finish() {
        if (mParent == null) {
            int resultCode;
            Intent resultData;
            synchronized (this) {
                resultCode = mResultCode;
                resultData = mResultData;
            }
            if (false) Log.v(TAG, "Finishing self: token=" + mToken);
            try {
                if (resultData != null) {
                    resultData.prepareToLeaveProcess();
                }
                if (ActivityManagerNative.getDefault()
                    .finishActivity(mToken, resultCode, resultData)) {
                    mFinished = true;
                }
            } catch (RemoteException e) {
                // Empty
            }
        } else {
            mParent.finishFromChild(this);
        }
    }

performPauseActivity方法
是通过参数finished来判断是否需要finished

    final Bundle performPauseActivity(ActivityClientRecord r, boolean finished,
            boolean saveState) {
省略...
        if (finished) {
            r.activity.mFinished = true;
        }

finished这个参数最终来源在AMS,由系统告知是否finished
performDestroyActivity方法
也是通过参数finished来判断是否需要finished

    private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance) {
        ActivityClientRecord r = mActivities.get(token);
        Class<? extends Activity> activityClass = null;
        if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
        if (r != null) {
            activityClass = r.activity.getClass();
            r.activity.mConfigChangeFlags |= configChanges;
            if (finishing) {
                r.activity.mFinished = true;
            }

同样是finished这个参数最终来源在AMS,由系统告知是否finished

分析现有的信息:
1 保存状态callActivityOnSaveInstanceState分别在performPauseActivity, performStopActivityInner 和handleRelaunchActivity 调用
2 但实际由于isPreHoneycomb这个方法因为android版本原因为false,所以performPauseActivity不会执行执行
3 performStopActivityInner 有两个分支,handleWindowVisibility不会执行,handleStopActivity可能执行保存状态,需要判断actvity是否被finished掉。
4 handleRelaunchActivity 可能执行保存状态,需要判断是否被保存过,和是否在非stop状态

总结会出现保存状态的条件:
1 handleStopActivity 如果执行保存,一定是在activity被finished掉之前
2 handleRelaunchActivity一般用于配置更改或在其他机制触发重启activity ,如果需要执行保存状态,一定之前没有被保存过,并且在stop之前。

handleStopActivity 和handleRelaunchActivity都会有AMS通知执行,至于通知触发的场景至少有activity 停止和重启的流程。
具体深入需要了解AMS内部activity的管理源码才能有更详细的解释,后续整理清楚了在补充这块内容。

最后一个问题Bundle类型的state最后保存在哪里?
这个在上面的图中已经描述过了,这里我们贴出具体代码看看

    private void handleStopActivity(IBinder token, boolean show, int configChanges) {
        ActivityClientRecord r = mActivities.get(token);
        r.activity.mConfigChangeFlags |= configChanges;

        StopInfo info = new StopInfo();
        performStopActivityInner(r, info, show, true);

        if (localLOGV) Slog.v(
            TAG, "Finishing stop of " + r + ": show=" + show
            + " win=" + r.window);

        updateVisibility(r, show);

        // Make sure any pending writes are now committed.
        if (!r.isPreHoneycomb()) {
            QueuedWork.waitToFinish();
        }

        // Schedule the call to tell the activity manager we have
        // stopped.  We don't do this immediately, because we want to
        // have a chance for any other pending work (in particular memory
        // trim requests) to complete before you tell the activity
        // manager to proceed and allow us to go fully into the background.
        info.activity = r;
        info.state = r.state;
        mH.post(info);
    }

在handleStopActivity这个方法中,会创建一个StopInfo的info对象,让后等stop的一系列动作完成后最后会执行这几句话,把activity记录和state保存在info对象,然后通过post发给对应的handler处理
info.activity = r;
info.state = r.state;
mH.post(info);
再来看看StopInfo

    private static class StopInfo implements Runnable {
        ActivityClientRecord activity;
        Bundle state;
        Bitmap thumbnail;
        CharSequence description;

        @Override public void run() {
            // Tell activity manager we have been stopped.
            try {
                if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Reporting activity stopped: " + activity);
                ActivityManagerNative.getDefault().activityStopped(
                    activity.token, state, thumbnail, description);
            } catch (RemoteException ex) {
            }
        }
    }

StopInfo是一个Runnable的实现类,post后执行这个类的run方法,里面很简单的一句话, 发送方法activityStopped给AMS,包括把state也发送给AMS保存。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月夜持剑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值