扒一扒场景恢复的代码

今天扒一扒场景恢复的代码;

流程顺序

大致看下调用方式,没有按照严格的时序图方式写!


调用顺序

ActivityThread

-> ActivityThread.callActivityOnSaveInstanceState(ActivityClientRecord r)
此方法有三个调用:分别对应三个调用分支

  • 分支一: Honeycomb之前(Api<11(Android3.0.x)),调用在pausing之前
  • 分支二: P之前(api<28(Android9.0))被调用在onstop之前,
  • 分之三: P开始在onstop之后
    private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
        r.state = new Bundle();
        r.state.setAllowFds(false);
        if (r.isPersistable()) {//是否走持久化方法
            r.persistentState = new PersistableBundle();
            mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state,
                    r.persistentState);
        } else {
            mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
        }
    }

if (r.isPersistable())注意这里方法,这里决定是否走持久化方法
需在Manifest中的activity设置属性:

android:persistableMode="persistAcrossReboots"

这可不是我说的,我看这位小伙子的,我没论证persistableMode与Activity的持久化

->
调用了Instrumentation#callActivityOnSaveInstanceState(Activity activity, Bundle outState,
PersistableBundle outPersistentState)

 activity.performSaveInstanceState(outState, outPersistentState);

->

Activity#performSaveInstanceState(Bundle outState, PersistableBundle outPersistentState)

final void performSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
        onSaveInstanceState(outState, outPersistentState);
        saveManagedDialogs(outState);
        storeHasCurrentPermissionRequest(outState);
        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState +
                ", " + outPersistentState);
    }

这里执行了Activity的onSaveInstanceState(outState, outPersistentState)方法.

public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
        onSaveInstanceState(outState);
    }

注意这里直接调用了默认的onSaveInstanceState()一个参数的,而持久化参数的使用,需要我们在Activity里面重载这个方法,然后我们把咱们要保存的数据穿到这个对象里面就好了.

Activity#onSaveInstanceState(Bundle outState)

    protected void onSaveInstanceState(Bundle outState) {
        //这里获取整个window的需要保存的数据
        outState.putBundle(WINDOW_HIERARCHY_TAG,mWindow.saveHierarchyState());
        outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
        Parcelable p = mFragments.saveAllState();//这里是保存Fragment的数据
        if (p != null) {
            outState.putParcelable(FRAGMENTS_TAG, p);
        }
        if (mAutoFillResetNeeded) {
            outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
            getAutofillManager().onSaveInstanceState(outState);
        }
        getApplication().dispatchActivitySaveInstanceState(this, outState);//这里是给ActivityLifecycleCallbacks#onActivitySaveInstanceState的回调
    }

这里就是默认的实现方法,保存了系统的一些对象,然后你重载这个就可以保存自己的了.

我们去验证一下View数据的保存;

  outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());

PhoneWindow实现了Window,所以去PhoneWindow去找saveHierarchyState();

  public Bundle saveHierarchyState() {
        Bundle outState = new Bundle();
        if (mContentParent == null) {
            return outState;
        }
        //主要看着里
        SparseArray<Parcelable> states = new SparseArray<Parcelable>();//这个证明所有的View都存放在同一个集合里
        mContentParent.saveHierarchyState(states);//开始ViewGroup的遍历赋值
        outState.putSparseParcelableArray(VIEWS_TAG, states);//这是放到了Bundle里

        // Save the focused view ID.//需要获取焦点的id
        final View focusedView = mContentParent.findFocus();
        if (focusedView != null && focusedView.getId() != View.NO_ID) {
            outState.putInt(FOCUSED_ID_TAG, focusedView.getId());
        }

        // save the panels
        SparseArray<Parcelable> panelStates = new SparseArray<Parcelable>();
        savePanelState(panelStates);
        if (panelStates.size() > 0) {
            outState.putSparseParcelableArray(PANELS_TAG, panelStates);
        }

        if (mDecorContentParent != null) {
            SparseArray<Parcelable> actionBarStates = new SparseArray<Parcelable>();
            mDecorContentParent.saveToolbarHierarchyState(actionBarStates);
            outState.putSparseParcelableArray(ACTION_BAR_TAG, actionBarStates);
        }

        return outState;
    }

继续看mContentParent.saveHierarchyState(states);干了些什么?
找了一下ViewGroup没找到,那么就去View里面找,找到了
View#saveHierarchyState

 public void saveHierarchyState(SparseArray<Parcelable> container) {
        dispatchSaveInstanceState(container);
    }

然后去找 ViewGroup#dispatchSaveInstanceState

 @Override
    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
        super.dispatchSaveInstanceState(container);
        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            View c = children[i];
            if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) {
                c.dispatchSaveInstanceState(container);
            }
        }
    }

上面就是递归调用了

下面就是基线条件了;

View#dispatchSaveInstanceState()

    protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
        if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) {
            mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
            Parcelable state = onSaveInstanceState();
            if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) {
                throw new IllegalStateException(
                        "Derived class did not call super.onSaveInstanceState()");
            }
            if (state != null) {
                // Log.i("View", "Freezing #" + Integer.toHexString(mID)
                // + ": " + state);
                container.put(mID, state);
            }
        }
    }

最后我们拿到了所有有id的并且需要存储数据View的数据;也就是id不能重复,重复了数据就会被最后一个id覆盖。
敲重点:

通过递归dispatchSaveInstanceState,来调用View的onSaveInstanceState(),最终拿到所有数据,值得注意的是View的id不能重复,重复最后的id会覆盖前一个id,所以在Include布局的时候,自定义ViewGroup的时候都会出现重复id的问题,例如下面这个问题LottieAnimationView场景恢复-导致的底部按钮显示相同

看到这里,我突然想起来,没看到Bundle的存储呀,往上面找了找,就看到从Activity的值被赋值到了callActivityOnSaveInstanceState(ActivityClientRecord r)里,我们再看下这段代码
Activity#callActivityOnSaveInstanceState

 private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
        r.state = new Bundle();//在这里new了出来,下面的都是赋值逻辑
        r.state.setAllowFds(false);
        if (r.isPersistable()) {
            r.persistentState = new PersistableBundle();
            mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state,
                    r.persistentState);
        } else {
            mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
        }
    }

那我们再看看ActivityClientRecord,先看看这个ActivityClientRecord是从哪来的,
ActivityThread#performPauseActivity

  private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, String reason,
            PendingTransactionActions pendingActions) {
        // Pre-Honeycomb apps always save their state before pausing
        final boolean shouldSaveState = !r.activity.mFinished && r.isPreHoneycomb();
        if (shouldSaveState) {
        //这个是咱们一直在看的那个save方法
            callActivityOnSaveInstanceState(r);
        }

这里没找到,我们再往上找
ActivityThread#handlePauseActivity

 @Override
    public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
            int configChanges, PendingTransactionActions pendingActions, String reason) {
            //就在这
        ActivityClientRecord r = mActivities.get(token);
        if (r != null) {
            //***
            r.activity.mConfigChangeFlags |= configChanges;
            //这个使我们刚才跟的方法
            performPauseActivity(r, finished, reason, pendingActions);
            //....
        }
    }

好的我们知道了怎么获得ActivityClientRecord的了

妙啊!原来每个Activity的数据都给放在这个集合里了

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

在哪被put呢?

ActivityThread#performLaunchActivity

    /**  Core implementation of activity launch. */
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            //...
           mActivities.put(r.token, r);
           //...
        return activity;
    }

晓得了在哪put了,那哪里创建的ActivityClientRecord呢?
ActivityThread#startActivityNow

 public final Activity startActivityNow(Activity parent, String id,
        Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
        Activity.NonConfigurationInstances lastNonConfigurationInstances) {
        ActivityClientRecord r = new ActivityClientRecord();
            r.token = token;
            r.ident = 0;
            r.intent = intent;
            r.state = state;
            r.parent = parent;
            r.embeddedID = id;
            r.activityInfo = activityInfo;
            r.lastNonConfigurationInstances = lastNonConfigurationInstances;
        //...
        return performLaunchActivity(r, null /* customIntent */);
    }

舒服了,找到了handleLaunchActivity!我觉得我们可以到此收手了,因为中间要经过Activity启动流程,因为不是咱们说的重点,我们不看这个,如果想看,可以看下这个Android 7.0 startActivity()源码解析以及对几个问题的思考

这里可以看到每个Activity创建的时候会创建一个ActivityClientRecord用来保存数据,并且存在集合里面。

好了,敲黑板!重点来了

我们可以知道ActivityClientRecord是记录Activity的一些属性的。当页面销毁的时候,由于把信息存到了全局的 final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();所以当会销毁Activity重建的时候自然可以从此集合里再次获取数据!

恢复的代码和保存的代码调用几乎一致,所以就不写了!

此处应有签名
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 225,208评论 6 524
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,502评论 3 405
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 172,496评论 0 370
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 61,176评论 1 302
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,185评论 6 401
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,630评论 1 316
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,992评论 3 431
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,973评论 0 280
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,510评论 1 325
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,546评论 3 347
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,659评论 1 355
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,250评论 5 351
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,990评论 3 340
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,421评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,569评论 1 277
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,238评论 3 382
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,732评论 2 366