怎么處理SaveState

前文鏈接:是時(shí)候使用SaveState了

要使前文介紹的5.0新機(jī)制生效表锻,應(yīng)用需要設(shè)計(jì)為多Task結(jié)構(gòu)恕齐,而且要處理好頁面的SaveState相關(guān)邏輯。

這里先討論SaveState的相關(guān)邏輯瞬逊,再介紹怎么設(shè)計(jì)應(yīng)用的Task結(jié)構(gòu)显歧。

處理SaveState的四個(gè)方面

在之前演示代碼的基礎(chǔ)上,我們稍做改動(dòng):代碼鏈接

  1. ActivityTwo包含一個(gè)文字列表
  2. 文字列表中每一項(xiàng)的前綴是由啟動(dòng)的Intent的Extra決定
  3. 文字列表中每一項(xiàng)的后綴是由創(chuàng)建頁面的時(shí)間戳決定

如果我們不處理SaveState确镊,則在恢復(fù)ActivityTwo時(shí)士骤,列表中每一項(xiàng)的后綴會發(fā)生變化,如果處理SaveState蕾域,則能保證返回時(shí)頁面和創(chuàng)建時(shí)一樣拷肌。在代碼中通過修改ActivityTwo#ENABLE_SAVE_STATE可以切換兩種狀態(tài)。

創(chuàng)建時(shí)的頁面:

create.png
創(chuàng)建時(shí)的頁面
創(chuàng)建時(shí)的頁面

不處理SaveState恢復(fù)后的頁面

restore.png

處理SaveState恢復(fù)后的頁面

create.png

SaveState的處理應(yīng)該包括4個(gè)部分

  1. 保存數(shù)據(jù)
  2. 恢復(fù)數(shù)據(jù)
  3. 處理View
  4. 處理Fragment

第一:保存數(shù)據(jù)

這一步相對簡單,只要把頁面中的數(shù)據(jù)變量保存到outState中

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (ENABLE_SAVE_STATE) {
            outState.putString(KEY_PREFIX, mPrefix);
            outState.putString(KEY_SUFFIX, mSuffix);
        }
    }

實(shí)際項(xiàng)目中巨缘,可能由Intent中傳入頁面id厢绝,再通過網(wǎng)絡(luò)接口獲取頁面詳情。這時(shí)带猴,也需要將網(wǎng)絡(luò)返回的數(shù)據(jù)也保存在outState中昔汉。

第二:恢復(fù)數(shù)據(jù)

恢復(fù)數(shù)據(jù)時(shí),需要考慮onCreate的正常處理邏輯

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_two);
        mListView = (ListView) findViewById(R.id.lv);

        if (ENABLE_SAVE_STATE && savedInstanceState != null) {
            // 恢復(fù)流程
            mPrefix = savedInstanceState.getString(KEY_PREFIX);
            mSuffix = savedInstanceState.getString(KEY_SUFFIX);
            mListView.setAdapter(new TwoAdapter(mPrefix, mSuffix));
        } else {
            // 初始化流程
            mPrefix = getIntent().getStringExtra(KEY_PREFIX);
            mSuffix = String.valueOf(SystemClock.uptimeMillis());
            mListView.setAdapter(new TwoAdapter(mPrefix, mSuffix));
        }
    }

實(shí)際項(xiàng)目中拴清,初始化網(wǎng)絡(luò)請求應(yīng)放初始化流程中靶病,而類似mListView.setAdapter的View更新邏輯應(yīng)該在網(wǎng)絡(luò)請求的回調(diào)中處理。

第三:處理View

上面的例子中口予,除了ListView的內(nèi)容娄周,我們還應(yīng)注意到ListView的位置。不管我們是否處理SaveState沪停,ListView都會恢復(fù)到離開時(shí)的位置煤辨。這是因?yàn)長istView的基類AbsListView實(shí)現(xiàn)了Save和Restore,下面是節(jié)選的一小段源碼木张。

// AbsListView

    @Override
    public Parcelable onSaveInstanceState() {
        ......

        Parcelable superState = super.onSaveInstanceState();

        SavedState ss = new SavedState(superState);

        if (mPendingSync != null) {
            // Just keep what we last restored.
            ss.selectedId = mPendingSync.selectedId;
            ss.firstId = mPendingSync.firstId;
            ss.viewTop = mPendingSync.viewTop;
            ss.position = mPendingSync.position;
            ss.height = mPendingSync.height;
            ss.filter = mPendingSync.filter;
            ss.inActionMode = mPendingSync.inActionMode;
            ss.checkedItemCount = mPendingSync.checkedItemCount;
            ss.checkState = mPendingSync.checkState;
            ss.checkIdState = mPendingSync.checkIdState;
            return ss;
        }
        .......
     }

除了AbsListView众辨,還有很多View實(shí)現(xiàn)了Save和Restore的機(jī)制,包括ViewPager當(dāng)前的位置舷礼,EditText和TextView中的文本等鹃彻。

關(guān)于View的處理主要注意以下幾點(diǎn):

  • 系統(tǒng)以一個(gè)Map來保存View的狀態(tài),以id為key
  • 沒有id的View是不會被保存狀態(tài)的
  • 如果id重復(fù)妻献,則View的狀態(tài)會被覆蓋
  • 自定義的View蛛株,要注意處理View的Save和Restore
  • 如果View的恢復(fù)有特殊處理邏輯,需要充分考慮View更新的時(shí)機(jī)育拨,注意onCreate谨履、onRestoreInstanceState和網(wǎng)絡(luò)請求回調(diào)的時(shí)序問題知举。

第三:處理Fragment

關(guān)于Fragment的處理总寒,和Activity的處理基本一致。只需要注意一個(gè)特殊的地方

很多項(xiàng)目都會只在Manifest中聲明一個(gè)FragmentContainerActivity的模式良哲,各個(gè)頁面通過Fragment實(shí)現(xiàn)锹引。然后啟動(dòng)FragmentContainerActivity矗钟,通過Extra傳遞要展現(xiàn)的Fragment類名和Argument。FragmentContainerActivity#onCreate中創(chuàng)建并添加Fragment嫌变。

由于Activity的恢復(fù)機(jī)制會自動(dòng)重建Fragment,所以在恢復(fù)時(shí)不要再重復(fù)創(chuàng)建添加Fragment躬它。當(dāng)然也不要新創(chuàng)建腾啥,并通過replace替換老的,這樣會使Fragment的恢復(fù)機(jī)制失效。

這部的例子演示倘待,可使用如下步驟復(fù)現(xiàn):

  1. 修改ActivityThree#ENABLE_SAVE_STATE進(jìn)行切換
  2. 啟動(dòng)Three
  3. 點(diǎn)擊Three的文字可返回到One
  4. 在One中消耗內(nèi)存疮跑,直到logcat中出現(xiàn)ActivityThree#onDestroy
  5. 再次啟動(dòng)Three

合理區(qū)分Task

Android關(guān)于Task的定義十分復(fù)雜,而且很多特性在普通應(yīng)用開發(fā)中根本用不到凸舵。而且在5.0之后祖娘,又引入了android:documentLaunchMode讓它變得更加復(fù)雜了。

關(guān)于Task啊奄,還需要另一篇專題來討論渐苏。這里只舉兩類多Task的例子。

使用singleInstance

給一些特定的頁面設(shè)置singleInstance菇夸,可使他們處于單獨(dú)的Task中琼富。這類頁面一般和其他頁面沒有很強(qiáng)的邏輯關(guān)系,同時(shí)又是消耗資源的大戶庄新。

適用場景:

  • 視頻播放或錄制頁(視頻的編碼解碼鞠眉,視頻上的絢麗彈幕和禮物等)
  • 應(yīng)用介紹頁(包含很多大圖和動(dòng)畫)
  • 應(yīng)用內(nèi)用于現(xiàn)實(shí)外部網(wǎng)頁的單WebView頁
  • ViewPager實(shí)現(xiàn)的大圖圖集頁

使用taskAffinity

給一組完成某一功能的Activity設(shè)置相同的taskAffinity,就可使用FLAG_ACTIVITY_NEW_TASK啟動(dòng)新Task择诈。使用taskAffinity啟動(dòng)的新Task一般都包括多個(gè)Activity械蹋,而且和別的參數(shù)相互影響,請謹(jǐn)慎使用羞芍。

適用場景:

  • 注冊朝蜘、登陸、找回密碼等頁面(這類頁面一般占用的資源并不多涩金,不一定要設(shè)計(jì)在獨(dú)立的Task中)
  • 自定義的選擇相冊谱醇,照相機(jī),圖片預(yù)覽步做,圖片裁剪等頁面

關(guān)于多Task的補(bǔ)充

由于不同的Task之間不能通過StartActivityForResult傳遞結(jié)果副渴,可能需要EventBus或其他機(jī)制在Task之間傳遞信息。

默認(rèn)的每個(gè)Task都會出現(xiàn)在最近應(yīng)用中全度。上述的這些情況中煮剧,都可使用android:excludeFromRecents避免這些Task在最近應(yīng)用中出現(xiàn)

后面單獨(dú)補(bǔ)充了一節(jié)進(jìn)程被殺的介紹,因?yàn)樗苋菀缀?strong>Task中Activity銷毀混淆

Task中Activity銷毀 vs 進(jìn)程被殺

我們先看下再ActivityManagerService中進(jìn)程Process和Task的關(guān)系

Task&Process.gif
  1. ActivityManagerService通過一個(gè)列表mHistory來管理所有ActivityRecord
  2. 相同TaskRecord中的ActivityRecord在列表中處于連續(xù)位置
  3. 同一個(gè)TaskRecord中的ActivityRecord可能處于不同的ProcessRecord

由于以下兩個(gè)因素将鸵,使得很難找到Task和進(jìn)程之間關(guān)聯(lián)的清晰線索勉盅。

  • 同一Task中的Activity可能屬于不同進(jìn)程
  • 進(jìn)程中不僅有Activity,還有Service和BroadcastReceiver

先看Task中Activity銷毀

  • 處理的問題:一個(gè)進(jìn)程內(nèi)部顶掉,前后臺Task的資源協(xié)調(diào)
  • 觸發(fā)時(shí)機(jī):進(jìn)程使用的內(nèi)存接近上限時(shí)(根據(jù)機(jī)型不同草娜,大約在64M~256M之間)

再看進(jìn)程被殺

  • 處理的問題:系統(tǒng)控制中,多進(jìn)程之間的資源協(xié)調(diào)
  • 觸發(fā)時(shí)機(jī):整個(gè)系統(tǒng)使用的內(nèi)存接近機(jī)器配置的內(nèi)存上限時(shí)

我們以一個(gè)簡化的例子討論兩者的關(guān)系痒筒。假設(shè):

  • 單進(jìn)程最大可使用內(nèi)存為100M宰闰,進(jìn)程使用內(nèi)存超過90M時(shí)會觸發(fā)后臺Task銷毀茬贵。
  • 系統(tǒng)總可用內(nèi)存為200M,系統(tǒng)使用內(nèi)存超過190M時(shí)會觸發(fā)后臺進(jìn)程被殺移袍。
  • 系統(tǒng)中運(yùn)行著3個(gè)進(jìn)程解藻,他們在三個(gè)Task中的分布和內(nèi)存使用如下
  • Task1處于前臺運(yùn)行
Momory Usage Process1 Process2 Process 3
Task1 60M 20M -
Task2 20M 20M -
Task3 - - 40M

如果T1 P1部分消耗的內(nèi)存由60M上升到75M,由于P1的總內(nèi)存消耗達(dá)到95M葡盗,所以會導(dǎo)致P1 T2中的Activity被銷毀螟左。

如果T1 P2部分消耗的內(nèi)存由20M上升到50M,會導(dǎo)致系統(tǒng)總內(nèi)存消耗達(dá)到190M觅够。此時(shí)三個(gè)Process中胶背,P1和P2和前臺Task關(guān)聯(lián),優(yōu)先級較高蔚约,所以系統(tǒng)會殺掉P3奄妨。

這個(gè)例子,只是對兩者關(guān)系的一個(gè)簡要說明苹祟。系統(tǒng)對進(jìn)程的實(shí)際處理方式要復(fù)雜得多砸抛!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市树枫,隨后出現(xiàn)的幾起案子直焙,更是在濱河造成了極大的恐慌,老刑警劉巖砂轻,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奔誓,死亡現(xiàn)場離奇詭異,居然都是意外死亡搔涝,警方通過查閱死者的電腦和手機(jī)厨喂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來庄呈,“玉大人蜕煌,你說我怎么就攤上這事∥芰簦” “怎么了斜纪?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長文兑。 經(jīng)常有香客問我盒刚,道長,這世上最難降的妖魔是什么绿贞? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任因块,我火速辦了婚禮,結(jié)果婚禮上樟蠕,老公的妹妹穿的比我還像新娘贮聂。我一直安慰自己靠柑,他們只是感情好寨辩,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布吓懈。 她就那樣靜靜地躺著,像睡著了一般靡狞。 火紅的嫁衣襯著肌膚如雪耻警。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天甸怕,我揣著相機(jī)與錄音甘穿,去河邊找鬼。 笑死梢杭,一個(gè)胖子當(dāng)著我的面吹牛温兼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播武契,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼募判,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了咒唆?” 一聲冷哼從身側(cè)響起届垫,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎全释,沒想到半個(gè)月后装处,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡浸船,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年妄迁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片李命。...
    茶點(diǎn)故事閱讀 40,742評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡登淘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出项戴,到底是詐尸還是另有隱情形帮,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布周叮,位于F島的核電站辩撑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏仿耽。R本人自食惡果不足惜合冀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望项贺。 院中可真熱鬧君躺,春花似錦峭判、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至俺泣,卻和暖如春疗认,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背伏钠。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工横漏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人熟掂。 一個(gè)月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓缎浇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親赴肚。 傳聞我的和親對象是個(gè)殘疾皇子素跺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容