Android技術周報190310期 —— onSaveInstanceState到底做了些什么焰檩?

Android技術周報是最近我剛創(chuàng)建的一個文集憔涉,這篇文章也是Android技術周報這個文集的第一篇文章(發(fā)現自己好久沒寫文章了),主要目的是記錄和分享一些工作中遇到的問題析苫,并對其進行深入研究兜叨,對于描述的內容有異議的可以留言,大家一起交流和學習衩侥。今天的內容是從源碼角度了解Activity中的 onSaveInstanceStateonRestoreInstanceState

1. onSaveInstanceState到底做了些什么国旷?

protected void onSaveInstanceState(Bundle outState) {
    // 存儲窗口視圖狀態(tài)
    outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());

    outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
    // 存儲Fragment狀態(tài)
    Parcelable p = mFragments.saveAllState();
    if (p != null) {
        outState.putParcelable(FRAGMENTS_TAG, p);
    }
    if (mAutoFillResetNeeded) {
        outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
        // 存儲Android自動填充的狀態(tài),非重點
        getAutofillManager().onSaveInstanceState(outState);
    }
    // 調用ActivityLifeCallbacks的onSaveInstanceState方法進行狀態(tài)存儲
    getApplication().dispatchActivitySaveInstanceState(this, outState);
}

從源碼上看茫死,不難發(fā)現onSaveInstanceState主要做了以下三個內容:

  1. 存儲窗口視圖狀態(tài)
  2. 存儲Fragment狀態(tài)
  3. 調用ActivityLifeCallbacks的onSaveInstanceState方法進行狀態(tài)存儲

通過調用PhoneWindow的saveHierarchyState方法實際上是調用mContentParent的saveHierarchyState方法:

@Override
public Bundle saveHierarchyState() {
    Bundle outState = new Bundle();
    if (mContentParent == null) {
        return outState;
    }

    SparseArray<Parcelable> states = new SparseArray<Parcelable>();
    mContentParent.saveHierarchyState(states);
    outState.putSparseParcelableArray(VIEWS_TAG, states);
    ....
   return outState;
}

其中mContentParent是Activity的主視圖跪但,本質是一個View對象,在Activity#setContentView()方法里進行初始化操作峦萎,接下來回到View的saveHierarchyState方法:

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

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) {
            // 以當前View的ID為key值存儲起來
            container.put(mID, state);
        }
    }
}

在View的dispatchSaveInstanceState方法里屡久,會將View#onSaveInstanceState()方法返回的Parcelable對象以當前View的ID為key值存儲起來,并返回骨杂,這也意味著如果當前View沒有設置ID涂身,那么將無法進行View的onSaveInstanceState操作

android:id="@+id/tv_save_state"

View的onSaveInstanceState方法默認返回空狀態(tài) BaseSavedState.EMPTY_STATE,可交由子View重寫搓蚪,如TextView#onSaveInstanceState():

@Override
public Parcelable onSaveInstanceState() {
    Parcelable superState = super.onSaveInstanceState();
    // Save state if we are forced to
    final boolean freezesText = getFreezesText();
    boolean hasSelection = false;
    int start = -1;
    int end = -1;
    if (mText != null) {
        start = getSelectionStart();
        end = getSelectionEnd();
        if (start >= 0 || end >= 0) {
            // Or save state if there is a selection
            hasSelection = true;
        }
    }
    if (freezesText || hasSelection) {
        SavedState ss = new SavedState(superState);
        if (freezesText) {
            if (mText instanceof Spanned) {
                final Spannable sp = new SpannableStringBuilder(mText);
                if (mEditor != null) {
                    removeMisspelledSpans(sp);
                    sp.removeSpan(mEditor.mSuggestionRangeSpan);
                }
                ss.text = sp;
            } else {
                ss.text = mText.toString();
            }
        }
        if (hasSelection) {
            // XXX Should also save the current scroll position!
            ss.selStart = start;
            ss.selEnd = end;
        }
        if (isFocused() && start >= 0 && end >= 0) {
            ss.frozenWithFocus = true;
        }
        ss.error = getError();
        if (mEditor != null) {
            ss.editorState = mEditor.saveInstanceState();
        }
        return ss;
    }
    return superState;
}

接下來進入Fragment狀態(tài)的保存方法蛤售,mFragments.saveAllState(),mFragments是一個FragmentController對象妒潭,主要用于提供FragmentManager對象悴能,維護Fragments的生命周期,這里不做過多介紹雳灾,所以最終是調用FragmentManager#saveAllState()方法漠酿,該方法最終會返回一個FragmentManagerState對象:

Parcelable saveAllState() {
    ...
    // 第一步:收集所有處于active狀態(tài)的Fragment
    int N = mActive.size();
    FragmentState[] active = new FragmentState[N];
    boolean haveFragments = false;
    for (int i=0; i<N; i++) {
        Fragment f = mActive.valueAt(i);
        if (f != null) {
            ...
            FragmentState fs = new FragmentState(f);
            active[i] = fs;
            if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
                fs.mSavedFragmentState = saveFragmentBasicState(f);
                ...
            } else {
                fs.mSavedFragmentState = f.mSavedFragmentState;
            }
        }
    }

    // 第二步:收集已經添加的Fragments的Index
    int[] added = null;
    BackStackState[] backStack = null;
    N = mAdded.size();
    if (N > 0) {
        added = new int[N];
        for (int i=0; i<N; i++) {
            added[i] = mAdded.get(i).mIndex;
            ...
        }
    }

    // 第三步:保存回退棧的內容,FragmentManager中的BackStack主要是用來存儲FragmentTransaction谎亩,具體可見下篇文章的講解
    if (mBackStack != null) {
        N = mBackStack.size();
        if (N > 0) {
            backStack = new BackStackState[N];
            for (int i=0; i<N; i++) {
                backStack[i] = new BackStackState(this, mBackStack.get(i));
                if (DEBUG) Log.v(TAG, "saveAllState: adding back stack #" + i
                        + ": " + mBackStack.get(i));
            }
        }
    }

    FragmentManagerState fms = new FragmentManagerState();
    fms.mActive = active;
    fms.mAdded = added;
    fms.mBackStack = backStack;
    fms.mNextFragmentIndex = mNextFragmentIndex;
    if (mPrimaryNav != null) {
        fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex;
    }
    saveNonConfig();
    return fms;
}

對于第一步的方法最終會調用FragmentManager#saveFragmentBasicState()方法炒嘲,此方法主要有以下任務:

Bundle saveFragmentBasicState(Fragment f) {
    Bundle result = null;
    ...
   // 調用Fragment#onSaveInstanceState()方法(可由子類重寫)
    f.performSaveInstanceState(mStateBundle);
    dispatchOnFragmentSaveInstanceState(f, mStateBundle, false);
    ...
    // 保存View的視圖狀態(tài)宇姚,跟上面內容介紹一致
    if (f.mView != null) {
        saveFragmentViewState(f);
    }
    // mSavedViewState為已保存的視圖狀態(tài)
    if (f.mSavedViewState != null) {
        if (result == null) {
            result = new Bundle();
        }
        result.putSparseParcelableArray(
                FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState);
    }
    // 記錄Fragment的顯示屬性
    if (!f.mUserVisibleHint) {
        if (result == null) {
            result = new Bundle();
        }
        // Only add this if it's not the default value
        result.putBoolean(FragmentManagerImpl.USER_VISIBLE_HINT_TAG, f.mUserVisibleHint);
    }
    return result;
}

Fragment的performSaveInstanceState除了保存自身狀態(tài)還會保存子Fragment的狀態(tài),實現如下:

void performSaveInstanceState(Bundle outState) {
    onSaveInstanceState(outState);
    if (mChildFragmentManager != null) {
        Parcelable p = mChildFragmentManager.saveAllState();
        if (p != null) {
            outState.putParcelable(Activity.FRAGMENTS_TAG, p);
        }
    }
}

最后夫凸,看一下Activity#onSaveInstanceState的最后一步:

getApplication().dispatchActivitySaveInstanceState(this, outState);

實際上是遍歷已注冊的ActivityLifecycleCallback浑劳,并調用其onActivitySaveInstanceState()方法:

void dispatchActivitySaveInstanceState(Activity activity, Bundle outState) {
    Object[] callbacks = collectActivityLifecycleCallbacks();
    if (callbacks != null) {
        for (int i=0; i<callbacks.length; i++) {
            ((ActivityLifecycleCallbacks)callbacks[i]).onActivitySaveInstanceState(activity,
                    outState);
        }
    }
}

至此,Activity#onSaveInstanceState的工作已經做完了夭拌,總結起來魔熏,其時序圖如下:


onSaveInstanceState時序圖

講解過程省略了不少代碼,需要做詳細了解的可以進一步查看源碼鸽扁,關于onRestoreInstanceState的方法實現蒜绽,主要是以上方法的一個逆過程,這里不再做過多的敘述桶现,接下來內容主要講onSaveInstanceState與onRestoreInstanceState的調用時機躲雅。

2. onSaveInstanceState與onRestoreInstanceState調用時機

關于onSaveInstanceState調用時機的追蹤,我們先來簡單看一下一個Activity的啟動過程:


Activity的啟動過程.jpg

Activity的啟動過程與Binder進程間通信機制息息相關骡和,對于Binder進程間通信機制以及Activity的啟動過程吏夯,我們在后期的文章里會仔細說明,這里就不在做過多說明即横。Activity的啟動過程主要的步驟可以概括如下:

  1. MainActivity向ActivityManagerService發(fā)送一個啟動SecondActivity的進程間通信請求噪生;
  2. ActivityManagerService保存SecondActivity的相關信息,并向MainActivity發(fā)送一個進入中止狀態(tài)(pause)的進程間通信請求东囚;
  3. MainActivity進入中止狀態(tài)后跺嗽,向ActivityManagerService發(fā)送一個已進入中止狀態(tài)的進程間通信請求;
  4. ActivityManagerService發(fā)現SecondActivity所在進程不存在页藻,則會發(fā)起啟動新進程的請求桨嫁;
  5. 當新進程啟動完畢后,會向SecondActivity發(fā)送一個啟動完成的進程間通信請求份帐;
  6. ActivityManagerService將保存的SecondActivity信息發(fā)送給新創(chuàng)建的進程璃吧,以便其將SecondActivity啟動起來

我們主要看第三步,Activity接收ActivityManagerService的進程間通信請求最終都會由該Activity所在的進程對象ActivityThread里的Handler對象去處理:

// ActivityThread.java
private class H extends Handler {
    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            ...
            case PAUSE_ACTIVITY: {
                // 通知Activity進入中止狀態(tài)
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
                SomeArgs args = (SomeArgs) msg.obj;
                handlePauseActivity((IBinder) args.arg1, false,
                        (args.argi1 & USER_LEAVING) != 0, args.argi2,
                        (args.argi1 & DONT_REPORT) != 0, args.argi3);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            } break;
            ...
        }
}

核心方法為handlePauseActivity:

private void handlePauseActivity(IBinder token, boolean finished,
        boolean userLeaving, int configChanges, boolean dontReport, int seq) {
    ActivityClientRecord r = mActivities.get(token);
    if (DEBUG_ORDER) Slog.d(TAG, "handlePauseActivity " + r + ", seq: " + seq);
    if (!checkAndUpdateLifecycleSeq(seq, r, "pauseActivity")) {
        return;
    }
    if (r != null) {
        ...
        r.activity.mConfigChangeFlags |= configChanges;
        performPauseActivity(token, finished, r.isPreHoneycomb(), "handlePauseActivity");
        ...
    }
}

接著在往下看performPauseActivity:

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

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

    // Next have the activity save its current state and managed dialogs...
    if (!r.activity.mFinished && saveState) {
        callCallActivityOnSaveInstanceState(r);
    }
   ...
}

這時候關鍵方法已經顯而易見了:

private void callCallActivityOnSaveInstanceState(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);
    }
}

public void callActivityOnSaveInstanceState(Activity activity, Bundle outState,
        PersistableBundle outPersistentState) {
    activity.performSaveInstanceState(outState, 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);
}

總結起來废境,onSaveInstanceState會在以下情況下被調用:

  1. 當用戶按下home鍵畜挨;
  2. 長按home鍵切換應用程序;
  3. 按下電源鍵噩凹;
  4. 啟動一個新的Activity巴元;
  5. 屏幕方向切換;
  6. 電話打入等情況發(fā)生

而onRestoreInstanceState只有在activity確實是被系統(tǒng)回收驮宴,重新創(chuàng)建activity的情況下才會被調用逮刨,所以主要分為以下兩種情況:

a. activity沒有被銷毀:

onPause -> onSaveInstanceState -> onStop -> onRestart -> onStart -> onResume

b. activity被銷毀:

onPause -> onSaveInstanceState -> onStop -> onDestroy -> onCreate -> onStart -> onRestoreInstanceState -> onResume

OK,今天的內容就到這里堵泽,下篇文章見 ~

@see https://segmentfault.com/a/1190000018487825

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末修己,一起剝皮案震驚了整個濱河市恢总,隨后出現的幾起案子,更是在濱河造成了極大的恐慌睬愤,老刑警劉巖离熏,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異戴涝,居然都是意外死亡,警方通過查閱死者的電腦和手機钻蔑,發(fā)現死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門啥刻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人咪笑,你說我怎么就攤上這事可帽。” “怎么了窗怒?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵映跟,是天一觀的道長。 經常有香客問我扬虚,道長努隙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任辜昵,我火速辦了婚禮荸镊,結果婚禮上,老公的妹妹穿的比我還像新娘堪置。我一直安慰自己躬存,他們只是感情好,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布舀锨。 她就那樣靜靜地躺著岭洲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坎匿。 梳的紋絲不亂的頭發(fā)上盾剩,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機與錄音替蔬,去河邊找鬼彪腔。 笑死,一個胖子當著我的面吹牛进栽,可吹牛的內容都是我干的德挣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼快毛,長吁一口氣:“原來是場噩夢啊……” “哼格嗅!你這毒婦竟也來了番挺?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤屯掖,失蹤者是張志新(化名)和其女友劉穎玄柏,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體贴铜,經...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡粪摘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了绍坝。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徘意。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖轩褐,靈堂內的尸體忽然破棺而出椎咧,到底是詐尸還是另有隱情,我是刑警寧澤把介,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布勤讽,位于F島的核電站,受9級特大地震影響拗踢,放射性物質發(fā)生泄漏脚牍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一巢墅、第九天 我趴在偏房一處隱蔽的房頂上張望莫矗。 院中可真熱鬧,春花似錦砂缩、人聲如沸作谚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妹懒。三九已至,卻和暖如春双吆,著一層夾襖步出監(jiān)牢的瞬間眨唬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工好乐, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留匾竿,地道東北人。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓蔚万,卻偏偏與公主長得像岭妖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354