應(yīng)用在后臺(tái)運(yùn)行時(shí)很容易被強(qiáng)殺,這很正常,但是回到前臺(tái)時(shí),很容易出現(xiàn)空指針的情況。怎么解決這樣的問(wèn)題,且看看Stay的見(jiàn)解。
我們先跳出來(lái)看看android的app運(yùn)行原理。
app在后臺(tái)被強(qiáng)殺,是在內(nèi)存不足的情況下被強(qiáng)制釋放了,也有一些惡心的rom會(huì)強(qiáng)制殺掉那些后臺(tái)進(jìn)程以釋放緩存以提高所謂的用戶體驗(yàn)。
我們都覺(jué)得android rom很惡心,但同時(shí)還是用些更惡心的手法去繞開(kāi)這些瓶頸。亂,是因?yàn)樵谧钌蠈記](méi)有一個(gè)很好的約束,這也是開(kāi)源的弊端。anyway。我們還是得想破腦袋來(lái)解決這些問(wèn)題,否則飯碗就沒(méi)了。
我們先來(lái)重現(xiàn)這個(gè)bug:
假設(shè): App A -> B -> C -> D
在D activity中點(diǎn)Home鍵后臺(tái)運(yùn)行,打開(kāi)ddms,選中該App進(jìn)程,強(qiáng)殺。
然后從“最近打開(kāi)的應(yīng)用”中選中該App,回到的界面是D activity,假設(shè)App中沒(méi)有靜態(tài)變量,這個(gè)時(shí)候是不會(huì)crash的,點(diǎn)擊返回到C,這個(gè)時(shí)候也只是短暫黑屏后顯示C界面。但如果C中有引用靜態(tài)變量,并想要獲取靜態(tài)變量中的某個(gè)值時(shí),就NullPointer了。
以上復(fù)現(xiàn)的流程就幾個(gè)點(diǎn),我們展開(kāi)說(shuō)下:
- 當(dāng)應(yīng)用被強(qiáng)殺,整個(gè)App進(jìn)程都是被殺掉了,所有變量全都被清空了。包括Application實(shí)例。更別提那些靜態(tài)變量了。
- 雖然變量被清空了,但Android給了一些補(bǔ)救措施。activity棧沒(méi)有被清空,也就是說(shuō)A -> B -> C -> D這個(gè)棧還保存了,只是ABCD這幾個(gè)activity實(shí)例沒(méi)有了。所以回到App時(shí),顯示的還是D頁(yè)面
- 另外當(dāng)activity被強(qiáng)殺時(shí),系統(tǒng)會(huì)調(diào)用onSaveInstance去讓你保存一些變量,但我個(gè)人覺(jué)得面對(duì)海量的靜態(tài)變量,這個(gè)根本不夠用。
- 返回到C會(huì)黑屏,是因?yàn)镃要重繪,重走onCreate流程,渲染上需要點(diǎn)時(shí)間,所以會(huì)黑屏。
大概是以上這些點(diǎn)。如果App中沒(méi)有靜態(tài)變量的引用,那就不用出現(xiàn)NullPointer這個(gè)crash,也就不需要解決。一旦你有靜態(tài)變量,或者有些Application的全局變量,那就很危險(xiǎn)了。比如登錄狀態(tài),user profile等等。這些值都是空了。
肯定會(huì)有人說(shuō),這沒(méi)關(guān)系啊,所有的靜態(tài)變量都改到單例去不就好了嗎?然后附加上一些持久化cache,空了再取緩存就ok了嘛。嗯,這肯定也是一個(gè)辦法,但是這樣的束手束腳對(duì)開(kāi)發(fā)來(lái)說(shuō)也是痛苦,至少需要多30%的編碼時(shí)間才能全部cover。另外,還有那么多幫你挖坑的隊(duì)友,難省心啊。
既然App都被強(qiáng)殺了,干嘛不重新走第一次啟動(dòng)的流程呢,別讓App回到D而是啟動(dòng)A,這樣所有的變量都是按正常的流程去初始化,也就不會(huì)空指針了,對(duì)吧?有人說(shuō)這方案用戶體驗(yàn)一點(diǎn)都不好呀。但哪有十全十美的事呢,是重走流程好,還是一點(diǎn)一個(gè)NullPointer好?好好去溝通,相信產(chǎn)品也不會(huì)為難你的。當(dāng)然你也可以拿iOS來(lái)舉例,iOS在最近打開(kāi)的應(yīng)用里殺了某個(gè)App,重新點(diǎn)擊那個(gè)App,還是會(huì)重走流程的啊。
如果你接受我的這個(gè)解決方案,那且想想如何讓它不回到D而是重走流程呢?也就是說(shuō)中斷D的初始化而回到A,并且按back鍵,不會(huì)回到D,C,B??紤]一下。
我們先實(shí)例化這個(gè)場(chǎng)景吧。
A 為App的啟動(dòng)頁(yè)
B 為登錄頁(yè)
C 為首頁(yè)
D 為二級(jí)頁(yè)面
簡(jiǎn)單說(shuō)下解決方案,剩下的自己思考。
- 把首頁(yè)launchMode設(shè)置為singleTask,具體為什么我就不說(shuō)了,自己google。
- 在BaseActivity中onCreate中判斷App是否被強(qiáng)殺,強(qiáng)殺就不往下走,直接重走App流程。
- 首頁(yè)起一個(gè)承接或者中轉(zhuǎn)的作用,所有跨級(jí)跳轉(zhuǎn)都需要通過(guò)首頁(yè)來(lái)完成。
再給個(gè)提示,以上場(chǎng)景的解決方案也可以用于解決其它相關(guān)問(wèn)題:
- 在任意頁(yè)面退出App
- 在任意頁(yè)面返回到首頁(yè)
- 在任意頁(yè)面注銷或者token失效回到登錄頁(yè)
其實(shí)最重要的知識(shí)點(diǎn)就是launchMode,很多人面試的時(shí)候都能背出來(lái),甚至是原理。但真正會(huì)合理應(yīng)用它們的少之又少。有的時(shí)候,技術(shù)的優(yōu)劣體現(xiàn)于此。生搬硬套肯定是站不到最高點(diǎn)的。
題外話:
當(dāng)我第一次碰到這種問(wèn)題的時(shí)候就在想,為啥Android非得這么來(lái)實(shí)現(xiàn),既然都已經(jīng)把應(yīng)用強(qiáng)殺了,為什么還把棧信息保存下來(lái)了。既然把棧信息保存下來(lái),為什么不把整個(gè)App變量都cache到硬盤(pán)上呢。這樣還能節(jié)省ram,每個(gè)當(dāng)前運(yùn)行的App分到的最大內(nèi)存也不用再加限制了啊。這樣的話Bitmap的OOM也很難發(fā)生了。多好。好吧,iOS的內(nèi)存管理貌似就是這樣的機(jī)制(我這是白話文,勿較真)
有很多bug都是系統(tǒng)級(jí)的限制,雖說(shuō)沒(méi)有解決不了的技術(shù),但是偏要鉆牛角尖偏要用自以為的方式去解決問(wèn)題,那么就是坑自己,并且也坑了隊(duì)友。
做技術(shù)越久,越能感受到,難做的不是技術(shù),而是業(yè)務(wù)。如何理解業(yè)務(wù),以及背后的需求本質(zhì),是開(kāi)發(fā)中最最重要的事情。與其盲目的拿需求就開(kāi)始寫(xiě)代碼,不如花上些時(shí)間去理解需求。弄清前因后果,想好封裝與擴(kuò)展。這些是對(duì)思維邏輯的鍛煉,并且也是技術(shù)提升最快的方式。
參考閱讀:
Activity生命周期的妙用