
背景介紹
作為一個Android開發(fā)者,肯定會遇到這樣的一種情況,用戶在玩著你開發(fā)的app時,突然有微信來消息了,切換到了微信,然后還在微信逗留看視頻啊,聊天啊,刷朋友圈啊等等的,你所開發(fā)的app就出于后臺了,這個時候就很容易出現(xiàn)手機內(nèi)存不足,app被內(nèi)存回收干掉的情況了,等用戶終于聊完天,刷完朋友圈,回來app的時候,就會進行app的自我恢復(fù)了,如果開發(fā)者處理不好,就會出現(xiàn)崩潰的情況了,而且肯定會出現(xiàn)返回的時候一瞬間白屏,然后再顯示出來,這樣的用戶體驗非常的不好。那我們應(yīng)該怎樣去解決這樣的狀況呢?扯了那么多,我們的文章就正式開始啦!
開始
首先要介紹下Android中activity的四種啟動模式(就當作復(fù)習(xí)一下舊知識吧,資料來源于網(wǎng)絡(luò)總結(jié)):
Standard:是默認的也是標準的Task模式,在沒有其他因素的影響下,使用此模式的Activity,會構(gòu)造一個Activity的實例,加入到調(diào)用者的Task棧中去,對于使用頻度一般開銷一般什么都一般的Activity而言,standard模式無疑是最合適的,因為它邏輯簡單條理清晰,所以是默認的選擇。
singleTop:基本上于standard一致,僅在請求的Activity正好位于棧頂時,有所區(qū)別。此時,配置成singleTop的Activity,不再會構(gòu)造新的實例加入到Task棧中,而是將新來的Intent發(fā)送到棧頂Activity中,棧頂?shù)腁ctivity可以通過重載onNewIntent來處理新的Intent(當然,也可以無視...)。這個模式,降低了位于棧頂時的一些重復(fù)開銷,更避免了一些奇異的行為(想象一下,如果在棧頂連續(xù)幾個都是同樣的Activity,再一級級退出的時候,這是怎么樣的用戶體驗...),很適合一些會有更新的列表Activity展示。一個活生生的實例是,在Android默認提供的應(yīng)用中,瀏覽器(Browser)的書簽Activity(BrowserBookmarkPage),就用的是singleTop。
singleTask:配置了這個屬性的activity,最多僅有一個實例存在,而且,它在根的task中,在之后的被殺死重啟的過程中我們會利用到這個配置,也就是我們的主界面MainActivity。
singleInstance:跟上面的singleTask基本上是一樣的,但是,singleInstance的Activity,是它所在棧中僅有的一個Activity,如果涉及到的其他Activity,都移交到其他Task中進行,在實際開發(fā)中這個是用得比較少的。
這個是activity的生命周期:

咱們就不多介紹這個生命周期了,相信都熟悉不過了,有想了解的自行Google或者百度吧。
重點
接下來是我們的重點:程序如果在后臺被殺死之后,我們怎么去處理?是立刻恢復(fù)還是重新啟動?哪個方法更適合我們?
首先,我們得知道,為什么程序會在后臺被干掉的?我們又沒有手動關(guān)閉程序。
app在后臺被強殺,是在內(nèi)存不足的情況下被強制釋放了,也有一些惡心的rom會強制殺掉那些后臺進程以釋放緩存以提高所謂的用戶體驗。(注:當你的代碼寫得混亂、冗余,而且非常消耗內(nèi)存的時候,那你的app在后臺運行時將會比較容易被系統(tǒng)給干掉的,所以從現(xiàn)在開始要約束自己要養(yǎng)成良好的編碼習(xí)慣和注意內(nèi)存泄漏的問題)
我們都覺得android rom很惡心,但同時還是用些更惡心的手法去繞開這些瓶頸。亂,是因為在最上層沒有一個很好的約束,這也是開源的弊端。anyway。我們還是得想破腦袋來解決這些問題,否則飯碗就沒了。
我們現(xiàn)在來重現(xiàn)這個熟悉的一幕:
假設(shè):App A -> B -> C
在C activity中點Home鍵后臺運行,打開ddms,選中該App進程,強殺。
然后從“最近打開的應(yīng)用”中選中該App,回到的界面是C activity,假設(shè)App中沒有靜態(tài)變量,這個時候是不會crash的,點擊返回到B,這個時候也只是短暫白屏后顯示B界面。但如果B中有引用靜態(tài)變量,并想要獲取靜態(tài)變量中的某個值時,就NullPointer了。
以上復(fù)現(xiàn)的流程就幾個點,我們展開說下:
當應(yīng)用被強殺,整個App進程都是被殺掉了,所有變量全都被清空了。包括Application實例。更別提那些靜態(tài)變量了。
雖然變量被清空了,但Android給了一些補救措施。activity棧沒有被清空,也就是說A -> B -> C這個棧還保存了,只是ABC這幾個activity實例沒有了。所以回到App時,顯示的還是C頁面。另外當activity被強殺時,系統(tǒng)會調(diào)用onSaveInstance去讓你保存一些變量,但我個人覺得面對海量的靜態(tài)變量,這個根本不夠用。返回到B會白屏,是因為B要重繪,重走onCreate流程,渲染上需要點時間,所以會白屏了。
大概是以上這些點。如果App中沒有靜態(tài)變量的引用,那就不用出現(xiàn)NullPointer這個crash,也就不需要解決。一旦你有靜態(tài)變量,或者有些Application的全局變量,那就很危險了。比如登錄狀態(tài),user profile等等。這些值都是空了。
肯定會有人說,這沒關(guān)系啊,所有的靜態(tài)變量都改到單例去不就好了嗎?然后附加上一些持久化cache,空了再取緩存就ok了嘛。嗯,這肯定也是一個辦法,但是這樣的束手束腳對開發(fā)來說也是痛苦,至少需要多50%的編碼時間才能全部cover。另外,還有那么多幫你挖坑的隊友,難省心啊。
既然App都被強殺了,干嘛不重新走第一次啟動的流程呢,別讓App回到D而是啟動A,這樣所有的變量都是按正常的流程去初始化,也就不會空指針了,對吧?有人說這方案用戶體驗一點都不好呀。但哪有十全十美的事呢,是重走流程好,還是一點一個NullPointer好?好好去溝通,相信產(chǎn)品也不會為難你的。當然你也可以拿來舉例,iOS在最近打開的應(yīng)用里殺了某個App,重新點擊那個App,還是會重走流程的啊。
如果你說用戶已經(jīng)打開了C界面,所以重新打開的是是恢復(fù)到C界面,這樣的用戶體驗會更好啊,如果你是這樣認為的,那你很多時間都是在防止恢復(fù)的時候不讓你的app crash了,與其這樣,還不如讓整個app重新走整個流程呢,這樣更省時間,而且這樣也不用擔心隨時都會崩潰的情況,難道這樣的用戶體驗不會更好嗎?
那且想想如何讓它不回到C而是重走流程呢?也就是說中斷C的初始化而回到A,并且按back鍵,不會回到C,B??紤]一下。
我們先實例化這個場景吧。
A為App的啟動頁
B為首頁
C為二級頁面
把首頁launchMode設(shè)置為singleTask,具體為什么上面介紹activity的啟動模式的時候已經(jīng)介紹了singleTask的作用了。
在BaseActivity中onCreate中判斷App是否被強殺,強殺就不往下走,直接重走App流程。
首頁起一個承接或者中轉(zhuǎn)的作用,所有跨級跳轉(zhuǎn)都需要通過首頁來完成。
再給個提示,以上場景的解決方案也可以用于解決其它相關(guān)問題:
在任意頁面退出App
在任意頁面返回到首頁
其實最重要的知識點就是launchMode
具體實現(xiàn)
AppStatusConstant
public static final intSTATUS_FORCE_KILLED = -1;//應(yīng)用放在后臺被強殺了
public static final intSTATUS_NORMAL = 2; //APP正常態(tài)//intent到MainActivity區(qū)分跳轉(zhuǎn)目的
public static finalStringKEY_HOME_ACTION = "key_home_action";//返回到主頁面
public static final intACTION_BACK_TO_HOME = 0;//默認值
public static final intACTION_RESTART_APP = 1;//被強殺
AppStatusManager
public intappStatus= AppStatusConstant.STATUS_FORCE_KILLED; //APP狀態(tài)初始值為沒啟動不在前臺狀態(tài)
public staticAppStatusManagerappStatusManager;
privateAppStatusManager() {
}
public staticAppStatusManagergetInstance() {
if(appStatusManager==null{
appStatusManager=newAppStatusManager();
}
returnappStatusManager;
}
public intgetAppStatus() {
returnappStatus;
}
public voidsetAppStatus(intappStatus) {
this.appStatus= appStatus;
}
BaseActivity(大致內(nèi)容)
switch(AppStatusManager.getInstance().getAppStatus()) {
caseAppStatusConstant.STATUS_FORCE_KILLED:
restartApp();
break;
caseAppStatusConstant.STATUS_NORMAL:
setUpViewAndData();
break;
}
protected abstract voidsetUpViewAndData();
protected voidrestartApp() {
Intent intent =newIntent(this,MainActivity.class);
intent.putExtra(AppStatusConstant.KEY_HOME_ACTION,AppStatusConstant.ACTION_RESTART_APP);
startActivity(intent);
}
每一個繼承于父activity的都不要在oncreat中實現(xiàn)界面初始化和數(shù)據(jù)的初始化,因為如果被殺死之后,回來會走一次正常的生命流程的。
StartPageActivity配置(在oncreat()方法配置,并且在super()前):
AppStatusManager.getInstance().setAppStatus(AppStatusConstant.STATUS_NORMAL);//進入應(yīng)用初始化設(shè)置成未登錄狀態(tài)
MainActivity(配置了singleTask的主界面)
@Override
protected voidrestartApp() {
Toast.makeText(getApplicationContext(),"應(yīng)用被回收重啟",Toast.LENGTH_LONG).show();
startActivity(newIntent(this,StartPageActivity.class));
finish();
}
@Override
protected voidonNewIntent(Intent intent) {
super.onNewIntent(intent);
intaction = intent.getIntExtra(AppStatusConstant.KEY_HOME_ACTION,AppStatusConstant.ACTION_BACK_TO_HOME);
switch(action) {
caseAppStatusConstant.ACTION_RESTART_APP:
restartApp();
break;
}
}
當應(yīng)用打開的時候,啟動StartPageActivity,然后設(shè)置app的status為normal狀態(tài),記住,一定要設(shè)置,因為默認的是被殺死的狀態(tài)的。
當應(yīng)用被殺死之后,所有數(shù)據(jù)都會被回收,所以之前設(shè)置的app status也會置于默認狀態(tài),即殺死狀態(tài),所以再次打開app的時候,status為殺死狀態(tài),就會走重啟的流程,這里為什么要先跳轉(zhuǎn)到MainActivity呢?就是因為MainActivity配置為了Sing了Task,當跳轉(zhuǎn)到這個界面時,MainActivity就會置于Activity Task的最上層,其他的Activity將會被默認銷毀掉,利用這種技巧去銷毀其他的Activity,最后才是重新啟動StartPageActivity。整個流程就是這樣了。
大致的實現(xiàn)就如上所述了,我所倡導(dǎo)的宗旨就是花最少的時間,寫最好的代碼,實現(xiàn)最好的體驗!之前也參考過很多網(wǎng)上大神們的實現(xiàn)方式,但是我覺得以上實現(xiàn)的應(yīng)該是比較完整的一種了。
此文思路借鑒于stay4it