前文鏈接:是時(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):代碼鏈接
- ActivityTwo包含一個(gè)文字列表
- 文字列表中每一項(xiàng)的前綴是由啟動(dòng)的Intent的Extra決定
- 文字列表中每一項(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í)的頁面:

不處理SaveState恢復(fù)后的頁面
處理SaveState恢復(fù)后的頁面
SaveState的處理應(yīng)該包括4個(gè)部分
- 保存數(shù)據(jù)
- 恢復(fù)數(shù)據(jù)
- 處理View
- 處理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):
- 修改
ActivityThree#ENABLE_SAVE_STATE
進(jìn)行切換 - 啟動(dòng)Three
- 點(diǎn)擊Three的文字可返回到One
- 在One中消耗內(nèi)存疮跑,直到logcat中出現(xiàn)
ActivityThree#onDestroy
- 再次啟動(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)系
-
ActivityManagerService
通過一個(gè)列表mHistory
來管理所有ActivityRecord
- 相同
TaskRecord
中的ActivityRecord
在列表中處于連續(xù)位置 - 同一個(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ù)雜得多砸抛!
- 原文鏈接
- 我的主頁:www.huangyifei.info
- 微信訂閱號:tech_galaxy