前文鏈接:是時(shí)候使用SaveState了
要使前文介紹的5.0新機(jī)制生效,應(yīng)用需要設(shè)計(jì)為多Task結(jié)構(gòu),而且要處理好頁(yè)面的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)建頁(yè)面的時(shí)間戳決定
如果我們不處理SaveState,則在恢復(fù)ActivityTwo時(shí),列表中每一項(xiàng)的后綴會(huì)發(fā)生變化,如果處理SaveState,則能保證返回時(shí)頁(yè)面和創(chuàng)建時(shí)一樣。在代碼中通過(guò)修改ActivityTwo#ENABLE_SAVE_STATE可以切換兩種狀態(tài)。
創(chuàng)建時(shí)的頁(yè)面:


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

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

SaveState的處理應(yīng)該包括4個(gè)部分
- 保存數(shù)據(jù)
- 恢復(fù)數(shù)據(jù)
- 處理View
- 處理Fragment
第一:保存數(shù)據(jù)
這一步相對(duì)簡(jiǎn)單,只要把頁(yè)面中的數(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中傳入頁(yè)面id,再通過(guò)網(wǎng)絡(luò)接口獲取頁(yè)面詳情。這時(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ò)請(qǐng)求應(yīng)放初始化流程中,而類似mListView.setAdapter的View更新邏輯應(yīng)該在網(wǎng)絡(luò)請(qǐng)求的回調(diào)中處理。
第三:處理View
上面的例子中,除了ListView的內(nèi)容,我們還應(yīng)注意到ListView的位置。不管我們是否處理SaveState,ListView都會(huì)恢復(fù)到離開(kāi)時(shí)的位置。這是因?yàn)長(zhǎng)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來(lái)保存View的狀態(tài),以id為key
- 沒(méi)有id的View是不會(huì)被保存狀態(tài)的
- 如果id重復(fù),則View的狀態(tài)會(huì)被覆蓋
- 自定義的View,要注意處理View的Save和Restore
- 如果View的恢復(fù)有特殊處理邏輯,需要充分考慮View更新的時(shí)機(jī),注意onCreate、onRestoreInstanceState和網(wǎng)絡(luò)請(qǐng)求回調(diào)的時(shí)序問(wèn)題。
第三:處理Fragment
關(guān)于Fragment的處理,和Activity的處理基本一致。只需要注意一個(gè)特殊的地方
很多項(xiàng)目都會(huì)只在Manifest中聲明一個(gè)FragmentContainerActivity的模式,各個(gè)頁(yè)面通過(guò)Fragment實(shí)現(xiàn)。然后啟動(dòng)FragmentContainerActivity,通過(guò)Extra傳遞要展現(xiàn)的Fragment類名和Argument。FragmentContainerActivity#onCreate中創(chuàng)建并添加Fragment。
由于Activity的恢復(fù)機(jī)制會(huì)自動(dòng)重建Fragment,所以在恢復(fù)時(shí)不要再重復(fù)創(chuàng)建添加Fragment。當(dāng)然也不要新創(chuàng)建,并通過(guò)replace替換老的,這樣會(huì)使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)用開(kāi)發(fā)中根本用不到。而且在5.0之后,又引入了android:documentLaunchMode讓它變得更加復(fù)雜了。
關(guān)于Task,還需要另一篇專題來(lái)討論。這里只舉兩類多Task的例子。
使用singleInstance
給一些特定的頁(yè)面設(shè)置singleInstance,可使他們處于單獨(dú)的Task中。這類頁(yè)面一般和其他頁(yè)面沒(méi)有很強(qiáng)的邏輯關(guān)系,同時(shí)又是消耗資源的大戶。
適用場(chǎng)景:
- 視頻播放或錄制頁(yè)(視頻的編碼解碼,視頻上的絢麗彈幕和禮物等)
- 應(yīng)用介紹頁(yè)(包含很多大圖和動(dòng)畫)
- 應(yīng)用內(nèi)用于現(xiàn)實(shí)外部網(wǎng)頁(yè)的單WebView頁(yè)
- ViewPager實(shí)現(xiàn)的大圖圖集頁(yè)
使用taskAffinity
給一組完成某一功能的Activity設(shè)置相同的taskAffinity,就可使用FLAG_ACTIVITY_NEW_TASK啟動(dòng)新Task。使用taskAffinity啟動(dòng)的新Task一般都包括多個(gè)Activity,而且和別的參數(shù)相互影響,請(qǐng)謹(jǐn)慎使用。
適用場(chǎng)景:
- 注冊(cè)、登陸、找回密碼等頁(yè)面(這類頁(yè)面一般占用的資源并不多,不一定要設(shè)計(jì)在獨(dú)立的Task中)
- 自定義的選擇相冊(cè),照相機(jī),圖片預(yù)覽,圖片裁剪等頁(yè)面
關(guān)于多Task的補(bǔ)充
由于不同的Task之間不能通過(guò)StartActivityForResult傳遞結(jié)果,可能需要EventBus或其他機(jī)制在Task之間傳遞信息。
默認(rèn)的每個(gè)Task都會(huì)出現(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通過(guò)一個(gè)列表mHistory來(lái)管理所有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銷毀
- 處理的問(wèn)題:一個(gè)進(jìn)程內(nèi)部,前后臺(tái)Task的資源協(xié)調(diào)
- 觸發(fā)時(shí)機(jī):進(jìn)程使用的內(nèi)存接近上限時(shí)(根據(jù)機(jī)型不同,大約在64M~256M之間)
再看進(jìn)程被殺
- 處理的問(wèn)題:系統(tǒng)控制中,多進(jìn)程之間的資源協(xié)調(diào)
- 觸發(fā)時(shí)機(jī):整個(gè)系統(tǒng)使用的內(nèi)存接近機(jī)器配置的內(nèi)存上限時(shí)
我們以一個(gè)簡(jiǎn)化的例子討論兩者的關(guān)系。假設(shè):
- 單進(jìn)程最大可使用內(nèi)存為100M,進(jìn)程使用內(nèi)存超過(guò)90M時(shí)會(huì)觸發(fā)后臺(tái)Task銷毀。
- 系統(tǒng)總可用內(nèi)存為200M,系統(tǒng)使用內(nèi)存超過(guò)190M時(shí)會(huì)觸發(fā)后臺(tái)進(jìn)程被殺。
- 系統(tǒng)中運(yùn)行著3個(gè)進(jìn)程,他們?cè)谌齻€(gè)Task中的分布和內(nèi)存使用如下
- Task1處于前臺(tái)運(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,所以會(huì)導(dǎo)致P1 T2中的Activity被銷毀。
如果T1 P2部分消耗的內(nèi)存由20M上升到50M,會(huì)導(dǎo)致系統(tǒng)總內(nèi)存消耗達(dá)到190M。此時(shí)三個(gè)Process中,P1和P2和前臺(tái)Task關(guān)聯(lián),優(yōu)先級(jí)較高,所以系統(tǒng)會(huì)殺掉P3。
這個(gè)例子,只是對(duì)兩者關(guān)系的一個(gè)簡(jiǎn)要說(shuō)明。系統(tǒng)對(duì)進(jìn)程的實(shí)際處理方式要復(fù)雜得多!
- 原文鏈接
- 我的主頁(yè):www.huangyifei.info
- 微信訂閱號(hào):tech_galaxy