程優(yōu)先級(jí)機(jī)制詳解:從oom_score_adj到lmkd回收)
做Android開(kāi)發(fā)的這些年進(jìn)程優(yōu)先級(jí)是我和新同事交流時(shí)最常被誤解的一個(gè)概念。很多同學(xué)以為進(jìn)程被殺是“系統(tǒng)心情不好”其實(shí)是Android在內(nèi)存吃緊時(shí)按規(guī)則做的取舍。只要理解了進(jìn)程優(yōu)先級(jí)你不僅能把應(yīng)用做得更穩(wěn)還能在線上出現(xiàn)“進(jìn)程被回收”問(wèn)題時(shí)快速定位原因。1. 為什么Android要搞一套進(jìn)程優(yōu)先級(jí)1.1 內(nèi)存有限系統(tǒng)必須在關(guān)鍵時(shí)候做取舍Android手機(jī)的內(nèi)存不像PC那么寬裕即便是現(xiàn)在12GB、16GB的旗艦機(jī)系統(tǒng)里跑的應(yīng)用數(shù)量也多得遠(yuǎn)超想象。加上Android后臺(tái)默認(rèn)不清理應(yīng)用大部分應(yīng)用都留下了活躍或半活躍的進(jìn)程。如果一個(gè)應(yīng)用占著內(nèi)存不肯釋放而另一個(gè)應(yīng)用又打算啟動(dòng)系統(tǒng)就必須騰出空間。如果沒(méi)有進(jìn)程優(yōu)先級(jí)面臨內(nèi)存不足時(shí)只有兩條路要么直接觸發(fā)Linux內(nèi)核的OOM Killer隨機(jī)找進(jìn)程殺掉要么一直等到系統(tǒng)徹底卡死甚至重啟。這兩種結(jié)果都是災(zāi)難級(jí)的。隨機(jī)殺會(huì)把正在前臺(tái)使用的應(yīng)用到殺掉用戶當(dāng)場(chǎng)就崩潰。等到系統(tǒng)已經(jīng)卡頓再殺整個(gè)手機(jī)已經(jīng)沒(méi)法用了。所以Android必須提前給所有進(jìn)程排隊(duì)打分明確“誰(shuí)該舍、誰(shuí)該留”。1.2 優(yōu)先級(jí)就是“排隊(duì)被殺”的號(hào)碼牌很多資料把進(jìn)程優(yōu)先級(jí)翻譯成“被殺的順序”其實(shí)它是一個(gè)動(dòng)態(tài)變化的評(píng)分體系。系統(tǒng)給每個(gè)進(jìn)程算出一個(gè)分?jǐn)?shù)分?jǐn)?shù)越低越重要越不容易被回收分?jǐn)?shù)越高越次要內(nèi)存一緊就越先被清理。你可以把它想象成租房時(shí)的預(yù)約名單房間不夠時(shí)排在后面的先出去早期看不出來(lái)真到臨界點(diǎn)時(shí)名單順序決定一切。這個(gè)分?jǐn)?shù)在現(xiàn)代Android里一般反映在/proc/pid/oom_score_adj這個(gè)文件里取值通常在 -1000 到 1000 之間。進(jìn)程不會(huì)永遠(yuǎn)保持同一個(gè)分?jǐn)?shù)它會(huì)跟前臺(tái)有沒(méi)有Activity、有沒(méi)有被別的進(jìn)程綁定、有沒(méi)有運(yùn)行前臺(tái)服務(wù)、甚至退出后臺(tái)多久都有關(guān)。系統(tǒng)會(huì)定期重新計(jì)算分?jǐn)?shù)用來(lái)精確控制每輪內(nèi)存回收該動(dòng)哪些進(jìn)程。理解這套機(jī)制是你處理好進(jìn)程生命周期的基礎(chǔ)。2. Android進(jìn)程優(yōu)先級(jí)到底怎么分2.1 官方給出的五檔進(jìn)程分類Android官方把進(jìn)程分為五檔按重要性從高到低依次是前臺(tái)進(jìn)程、可見(jiàn)進(jìn)程、服務(wù)進(jìn)程、后臺(tái)進(jìn)程、空進(jìn)程。前臺(tái)進(jìn)程正在用戶屏幕最頂層運(yùn)行的Activity、正在執(zhí)行生命周期回調(diào)的BroadcastReceiver或者正在執(zhí)行關(guān)鍵回調(diào)的Service用戶能直接感受到它。只要不是系統(tǒng)實(shí)在撐不住這類進(jìn)程通常不應(yīng)該被殺??梢?jiàn)進(jìn)程界面部分可見(jiàn)但沒(méi)有完全處于交互狀態(tài)。比如被一個(gè)半透明彈窗遮住的Activity或者輸入法、動(dòng)態(tài)壁紙這類輔助窗口。用戶還能看到它所以重要性也很高。服務(wù)進(jìn)程通過(guò)startService啟動(dòng)的服務(wù)不在前臺(tái)界面也無(wú)法直接可見(jiàn)但后臺(tái)還在做用戶關(guān)心的事比如播音樂(lè)、下載。系統(tǒng)一般愿意多留一留。后臺(tái)進(jìn)程用戶按了Home或者切走Activity已經(jīng)不可見(jiàn)但進(jìn)程還保留著。這類進(jìn)程隨時(shí)可能被殺。空進(jìn)程進(jìn)程里沒(méi)有Activity、Service等任何運(yùn)行組件只留在內(nèi)存里當(dāng)緩存已經(jīng)成了純粹的資源占用者回收時(shí)優(yōu)先級(jí)最低。2.2 從oom_adj到oom_score_adj數(shù)值越高興越快被回收老版本Android用的是內(nèi)核里比較粗的oom_adj范圍 -16 到 15數(shù)值越小級(jí)別越高。后來(lái)系統(tǒng)改成了更線性的oom_score_adj范圍基本在 -1000 到 1000好處是計(jì)算起來(lái)更平滑也更方便在框架層做微調(diào)。我整理過(guò)一份常見(jiàn)的參考數(shù)值不同手機(jī)廠商和不同Android版本會(huì)有差異但結(jié)構(gòu)基本一致進(jìn)程狀態(tài)典型oom_score_adj系統(tǒng)持久進(jìn)程約 -800正在交互的前臺(tái)應(yīng)用0可見(jiàn)進(jìn)程約 100前臺(tái)服務(wù)帶通知但不在前臺(tái)約 50普通后臺(tái)服務(wù)約 200后臺(tái)Activity約 400空進(jìn)程 / 緩存進(jìn)程約 900 到 1000注意這些數(shù)字不是固定死的。不同ROM會(huì)在系統(tǒng)進(jìn)程、廠商應(yīng)用、白名單應(yīng)用上做額外調(diào)整但“數(shù)值越大越容易被回收”這個(gè)基本方向不變。所以當(dāng)你調(diào)試自己應(yīng)用時(shí)看到oom_score_adj從0變成400就意味著它已經(jīng)從“正在交互”降級(jí)成了“后臺(tái)可回收”的對(duì)象。2.3 優(yōu)先級(jí)不是固定不變的它會(huì)跟著組件狀態(tài)動(dòng)態(tài)變化很多人寫App時(shí)會(huì)有個(gè)誤區(qū)以為一個(gè)Service啟動(dòng)后進(jìn)程就一直是“服務(wù)進(jìn)程”永遠(yuǎn)不會(huì)變。實(shí)際上進(jìn)程優(yōu)先級(jí)由進(jìn)程內(nèi)當(dāng)前所有組件的最高重要性決定而且會(huì)跟隨組件生命周期實(shí)時(shí)變化。舉個(gè)例子你的App里有一個(gè)前臺(tái)Activity正在運(yùn)行此時(shí)進(jìn)程優(yōu)先級(jí)是0。用戶按Home鍵Activity進(jìn)入onStop進(jìn)程變成了“后臺(tái)進(jìn)程”adj可能直接跳到400。如果同一進(jìn)程里剛好有個(gè)服務(wù)在播放音樂(lè)且通過(guò)前臺(tái)服務(wù)方式啟動(dòng)那么進(jìn)程優(yōu)先級(jí)又會(huì)提升到一個(gè)較高的檔位。如果服務(wù)只是普通startService那它只能讓進(jìn)程在“服務(wù)進(jìn)程”和“后臺(tái)進(jìn)程”之間略微掙扎并不能保證不被回收。另外如果另一個(gè)前臺(tái)應(yīng)用綁定了你這個(gè)App的Service比如系統(tǒng)MediaBrowserService被音樂(lè)App連接你的進(jìn)程會(huì)跟著綁定者的優(yōu)先級(jí)走。這就是為什么有時(shí)候你的應(yīng)用明明在后臺(tái)adj卻很漂亮因?yàn)樗罢戳斯狻薄?. 系統(tǒng)是怎么把低優(yōu)先級(jí)進(jìn)程“請(qǐng)走”的3.1 lmkd的誕生從內(nèi)核驅(qū)動(dòng)到守護(hù)進(jìn)程早期Android使用的回收機(jī)制叫l(wèi)owmemorykiller是一個(gè)內(nèi)核對(duì)進(jìn)程的判定直接殺人的模塊。它會(huì)定期檢查可用內(nèi)存剩余量再對(duì)照配置的內(nèi)存閾值和adj閾值一旦剩余內(nèi)存低于某個(gè)水位就把超過(guò)對(duì)應(yīng)adj的進(jìn)程殺掉。低版本上它工作得還行但有個(gè)明顯問(wèn)題它只是簡(jiǎn)單看剩余內(nèi)存不能很好感知“內(nèi)存壓力到底有沒(méi)有在累積”。后來(lái)系統(tǒng)引入了lmkdLow Memory Killer Daemon這是一個(gè)用戶空間的守護(hù)進(jìn)程。它一方面能從內(nèi)核拿到更精細(xì)的內(nèi)存壓力信息比如PSIPressure Stall Information也就是系統(tǒng)在內(nèi)存上發(fā)生阻塞的時(shí)間和程度另一方面可以更靈活地執(zhí)行內(nèi)存回收策略目標(biāo)還是那些adj值較高的進(jìn)程一旦選中就直接發(fā)SIGKILL。現(xiàn)在絕大多數(shù)Android設(shè)備都是用lmkd配合內(nèi)核watermark來(lái)工作的。3.2 一次真實(shí)的內(nèi)存回收過(guò)程我在調(diào)試某款中端設(shè)備時(shí)遇到過(guò)這樣的場(chǎng)景手機(jī)運(yùn)行視頻通話App前臺(tái)后臺(tái)堆了新聞客戶端、購(gòu)物App、游戲、地圖導(dǎo)航等多個(gè)應(yīng)用。隨著通話時(shí)間拉長(zhǎng)系統(tǒng)可用內(nèi)存逐漸下降lmkd開(kāi)始介入。首先被清掉的一定是我進(jìn)程列表里那些adj接近1000的空進(jìn)程它們連Activity都沒(méi)有了回收無(wú)成本。接著是很多后臺(tái)緩存Activity進(jìn)程也就是常見(jiàn)的一堆App雖然看不到界面但進(jìn)程還活著系統(tǒng)殺掉它們時(shí)如果你回去打開(kāi)對(duì)應(yīng)應(yīng)用會(huì)看到重新走啟動(dòng)頁(yè)、重新恢復(fù)數(shù)據(jù)這就是典型的“進(jìn)程被回收了”。然后可能輪到一個(gè)只做普通后臺(tái)任務(wù)、沒(méi)有前臺(tái)服務(wù)也沒(méi)有可見(jiàn)界面的應(yīng)用。如果它還在后臺(tái)掛著一個(gè)Serviceadj大約200多在內(nèi)存進(jìn)一步緊張時(shí)也會(huì)被處理。而正在通話的App因?yàn)閷儆谇芭_(tái)進(jìn)程只要用戶還看著界面lmkd一般不會(huì)動(dòng)它。3.3 進(jìn)程被殺之后系統(tǒng)如何幫你恢復(fù)現(xiàn)場(chǎng)Android進(jìn)程被系統(tǒng)殺掉不是無(wú)痕的。只要進(jìn)程之前是通過(guò)正常棧啟動(dòng)系統(tǒng)會(huì)在用戶重新進(jìn)入應(yīng)用時(shí)嘗試恢復(fù)到被殺前的Activity棧。它會(huì)讀取Activity在onSaveInstanceState里保存的狀態(tài)重建Activity和View再通過(guò)onCreate里的savedInstanceState恢復(fù)臨時(shí)數(shù)據(jù)。但這里必須說(shuō)明這種恢復(fù)不是百分百無(wú)損。如果應(yīng)用在進(jìn)程被殺之前沒(méi)有處理好狀態(tài)保存比如一些內(nèi)存里的集合、請(qǐng)求隊(duì)列或者WebSocket連接恢復(fù)后就會(huì)丟失。所以要保證進(jìn)程被系統(tǒng)回收后依然體驗(yàn)正常重點(diǎn)不是“讓進(jìn)程永遠(yuǎn)不死”而是做好狀態(tài)保存和重建時(shí)的恢復(fù)邏輯。你可以在onSaveInstanceState、onTrimMemory里保存數(shù)據(jù)也可以在Application里維護(hù)一個(gè)可持久化的臨時(shí)狀態(tài)。4. 開(kāi)發(fā)者如何合理影響自己的進(jìn)程優(yōu)先級(jí)4.1 前臺(tái)服務(wù)合法“提權(quán)”最基本的手段如果應(yīng)用需要在后臺(tái)執(zhí)行用戶能明確感知的任務(wù)比如下載大文件、播放音頻或錄制視頻最標(biāo)準(zhǔn)的方式是開(kāi)啟前臺(tái)服務(wù)并調(diào)用startForeground()。前臺(tái)服務(wù)會(huì)持有一條常駐通知讓用戶知道當(dāng)前有任務(wù)在執(zhí)行同時(shí)系統(tǒng)的優(yōu)先級(jí)也會(huì)相應(yīng)提升adj值會(huì)明顯低于普通后臺(tái)服務(wù)。用Kotlin寫一個(gè)典型示例class DownloadService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (Build.VERSION.SDK_INT Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { startForeground( NOTIFICATION_ID, createNotification(), ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC ) } else { startForeground(NOTIFICATION_ID, createNotification()) } // 在這里執(zhí)行下載任務(wù) return START_STICKY } }Android 14開(kāi)始前臺(tái)服務(wù)必須聲明類型并且部分類型要在啟動(dòng)時(shí)滿足系統(tǒng)要求。比如下載任務(wù)需要聲明FOREGROUND_SERVICE_TYPE_DATA_SYNC并且從Android 14某個(gè)版本開(kāi)始某些后臺(tái)啟動(dòng)前臺(tái)服務(wù)的情況會(huì)被系統(tǒng)直接攔截。所以正確做法不是拼手速去繞過(guò)而是在用戶主動(dòng)操作時(shí)啟動(dòng)服務(wù)或用系統(tǒng)允許的調(diào)度方式比如WorkManager滿足條件時(shí)運(yùn)行。4.2 綁定關(guān)系帶來(lái)的優(yōu)先級(jí)提升除了前臺(tái)服務(wù)另一個(gè)重要手段是“綁定”。當(dāng)一個(gè)高優(yōu)先級(jí)進(jìn)程綁定到你應(yīng)用的Service或者調(diào)用你應(yīng)用的ContentProvider時(shí)你的進(jìn)程adj會(huì)被重要的一方帶著往上抬。這個(gè)機(jī)制經(jīng)常被忽略但它非常實(shí)用。比如App里播放器提供一個(gè)媒體播放Service通過(guò)MediaStyle通知綁定到系統(tǒng)MediaSession用戶在前臺(tái)控制播放時(shí)播放Service的進(jìn)程就能跟著保持較高優(yōu)先級(jí)。再比如你的App給主應(yīng)用提供數(shù)據(jù)能力作為一個(gè)獨(dú)立進(jìn)程運(yùn)行ContentProvider主應(yīng)用每次調(diào)用時(shí)你的Provider進(jìn)程都會(huì)因?yàn)橹鲬?yīng)用處于前臺(tái)而臨時(shí)獲得較高優(yōu)先級(jí)。這個(gè)機(jī)制也有代價(jià)如果你綁定的客戶端進(jìn)程本身很弱比如只是一個(gè)后臺(tái)緩存進(jìn)程那你的服務(wù)頂多從“空進(jìn)程”等級(jí)變成“服務(wù)進(jìn)程”并不能保證絕對(duì)安全。所以判斷到底該用前臺(tái)服務(wù)還是綁定需要結(jié)合業(yè)務(wù)場(chǎng)景思考而不是單純?yōu)榱颂岣邇?yōu)先級(jí)硬綁。4.3 千萬(wàn)別做的“?;睢闭`區(qū)我見(jiàn)過(guò)不少開(kāi)發(fā)者為了不讓進(jìn)程死會(huì)做各種“保活”操作在Service里循環(huán)空轉(zhuǎn)、開(kāi)一個(gè)常駐線程跑心跳、監(jiān)聽(tīng)系統(tǒng)廣播把自己拉活、甚至做雙進(jìn)程互相守護(hù)。說(shuō)句實(shí)在話這些方案在早期Android版本上可能還有部分作用放到現(xiàn)在輕則被系統(tǒng)電量和后臺(tái)限制策略直接清理重則被應(yīng)用商店、用戶當(dāng)作流氓應(yīng)用處理。系統(tǒng)真正在意的是“用戶體感”。你現(xiàn)在就算能讓進(jìn)程一直活著但如果應(yīng)用占著內(nèi)存不做任何有意義的事系統(tǒng)耗電增加用戶也會(huì)去系統(tǒng)設(shè)置里管理你的應(yīng)用。合理的做法應(yīng)該是讓任務(wù)變成前臺(tái)服務(wù)、綁定從而提升優(yōu)先級(jí)或者干脆不用常駐進(jìn)程用JobScheduler、WorkManager、推送通道在合適的時(shí)機(jī)喚醒執(zhí)行。進(jìn)程優(yōu)先級(jí)的存在本來(lái)就不是為了讓所有進(jìn)程永遠(yuǎn)活著的。5. 進(jìn)程優(yōu)先級(jí)的調(diào)試與排查實(shí)錄5.1 用adb查看進(jìn)程現(xiàn)任adj排查問(wèn)題第一步是知道自己進(jìn)程當(dāng)前到底處于什么優(yōu)先級(jí)。最簡(jiǎn)單的方式是使用Android自帶的dumpsys命令adb shell dumpsys activity processes | grep -E oomScoreAdj |pid.*com.example.app輸出里會(huì)包含一串類似這樣的信息*APP* UID 10086 PID 12345 com.example.app oomScoreAdj0 lastActivityTime-1h ...看到oomScoreAdj0說(shuō)明你現(xiàn)在是前臺(tái)進(jìn)程如果切后臺(tái)后再跑一次可能會(huì)變成oomScoreAdj400或更高。有些設(shè)備上普通adb shell沒(méi)有權(quán)限讀其他應(yīng)用進(jìn)程但看自己應(yīng)用進(jìn)程通常是沒(méi)問(wèn)題的。如果發(fā)現(xiàn)看不到oomScoreAdj可以用另一個(gè)命令直接看內(nèi)核文件adb shell cat /proc/12345/oom_score_adj這個(gè)方法需要知道進(jìn)程PID適合在已經(jīng)拿到PID后快速確認(rèn)。不過(guò)要注意部分系統(tǒng)為了安全普通shell可能臨時(shí)降權(quán)讀不到其他uid的進(jìn)程節(jié)點(diǎn)。這種情況下優(yōu)先用dumpsys。5.2 復(fù)現(xiàn)一次“被回收”現(xiàn)場(chǎng)想實(shí)測(cè)自己應(yīng)用的進(jìn)程優(yōu)先級(jí)變化可以在開(kāi)發(fā)機(jī)上做一個(gè)簡(jiǎn)單實(shí)驗(yàn)。把應(yīng)用切到后臺(tái)然后讓系統(tǒng)產(chǎn)生內(nèi)存壓力最直接的方式是把其他大型應(yīng)用一個(gè)個(gè)打開(kāi)并切到后臺(tái)直到可用內(nèi)存越來(lái)越少。也可以使用開(kāi)發(fā)者選項(xiàng)里的“不保留活動(dòng)”模擬系統(tǒng)回收Activity。我在測(cè)試某項(xiàng)目時(shí)遇到過(guò)App里有下載任務(wù)用普通Service開(kāi)在后臺(tái)切后臺(tái)后oom_score_adj大約在200左右。我連續(xù)打開(kāi)了相機(jī)、地圖、購(gòu)物和幾個(gè)游戲再回來(lái)看dumpsys時(shí)發(fā)現(xiàn)下載Service所在進(jìn)程的oomScoreAdj已經(jīng)變成1000左右沒(méi)過(guò)多久就被lmkd殺掉了。之后我把這個(gè)Service改成前臺(tái)服務(wù)重新復(fù)現(xiàn)同一場(chǎng)景發(fā)現(xiàn)adj能穩(wěn)定在50附近整個(gè)測(cè)試過(guò)程沒(méi)被殺。這說(shuō)明通過(guò)調(diào)試找到業(yè)務(wù)真正的前臺(tái)化空間比盲目保活有用得多。5.3 在代碼里監(jiān)測(cè)進(jìn)程狀態(tài)有些應(yīng)用需要在進(jìn)程被判死刑前做最后的數(shù)據(jù)保存這可以通過(guò)onTrimMemory(int level)回調(diào)感知。當(dāng)level為TRIM_MEMORY_RUNNING_LOW時(shí)說(shuō)明當(dāng)前還有Activity在前臺(tái)但系統(tǒng)已經(jīng)內(nèi)存緊張當(dāng)level為TRIM_MEMORY_UI_HIDDEN時(shí)說(shuō)明你的應(yīng)用已經(jīng)退到后臺(tái)是時(shí)候釋放不必要的緩存了當(dāng)收到TRIM_MEMORY_BACKGROUND及以上說(shuō)明進(jìn)程已經(jīng)開(kāi)始排隊(duì)等待被系統(tǒng)回收這時(shí)候應(yīng)該保存業(yè)務(wù)關(guān)鍵數(shù)據(jù)。注意onTrimMemory回調(diào)順序和進(jìn)程被殺時(shí)間不完全對(duì)應(yīng)不要指望靠它做“臨死搶救”。它更適合做資源整理比如清掉圖片緩存、關(guān)閉部分網(wǎng)絡(luò)連接讓進(jìn)程在后臺(tái)更“輕”從而降低被殺概率或者至少讓崩潰后的恢復(fù)成本變低。把這些數(shù)據(jù)保存邏輯放在onPause和onStop里比收到回調(diào)再動(dòng)手更可靠。6. 常見(jiàn)問(wèn)題與避坑指南6.1 為什么有Service還是被殺很多人的第一反應(yīng)是我開(kāi)了Service怎么還會(huì)被殺答案在于普通Service的優(yōu)先級(jí)通常只是“服務(wù)進(jìn)程”檔位當(dāng)內(nèi)存壓力大系統(tǒng)會(huì)繼續(xù)提升回收力度這一檔并不能保命。只有前臺(tái)服務(wù)也就是帶通知、調(diào)用過(guò)startForeground()的Service才會(huì)有比較強(qiáng)的生存能力。我之前幫一個(gè)團(tuán)隊(duì)成員排查過(guò)類似問(wèn)題他們做了一個(gè)聯(lián)網(wǎng)狀態(tài)監(jiān)聽(tīng)用startService常駐結(jié)果在后臺(tái)運(yùn)行一段時(shí)間后進(jìn)程消失。dumpsys日志里顯示的oomScoreAdj一度接近后臺(tái)緩存進(jìn)程。后來(lái)我們把監(jiān)聽(tīng)邏輯并入了一個(gè)需要用戶主動(dòng)確認(rèn)的前臺(tái)服務(wù)進(jìn)程才穩(wěn)定下來(lái)。實(shí)際開(kāi)發(fā)中如果業(yè)務(wù)確實(shí)需要后臺(tái)長(zhǎng)期運(yùn)行最好先檢查有沒(méi)有實(shí)際的前臺(tái)場(chǎng)景可以綁定比如用戶在看視頻、在聽(tīng)音頻、在進(jìn)行上傳下載。6.2 前臺(tái)服務(wù)為什么也會(huì)被系統(tǒng)“清掉”不少人也遇到過(guò)前臺(tái)服務(wù)被回收的情況。這個(gè)可能因?yàn)榈谝磺芭_(tái)服務(wù)的類型不符合系統(tǒng)要求例如Android 14里聲明了不支持的啟動(dòng)類型第二用戶主動(dòng)在系統(tǒng)設(shè)置里關(guān)閉了這個(gè)App的通知或后臺(tái)運(yùn)行權(quán)限第三部分國(guó)產(chǎn)ROM有更激進(jìn)的清理策略會(huì)把長(zhǎng)時(shí)間后臺(tái)運(yùn)行的前臺(tái)服務(wù)也殺掉。遇到這類問(wèn)題先不要急著怪系統(tǒng)。你可以先查一下應(yīng)用有沒(méi)有在用戶屏幕解鎖后偷偷后臺(tái)啟動(dòng)服務(wù)如果有那被限制很正常。沒(méi)有的話可以在崩潰日志里看是否有ServiceConnection斷開(kāi)等相關(guān)記錄也可以配合dumpsys看最近的系統(tǒng)進(jìn)程清理日志。關(guān)鍵是做好業(yè)務(wù)降級(jí)如果服務(wù)必須運(yùn)行就通過(guò)系統(tǒng)提供的調(diào)度能力定時(shí)重試如果服務(wù)只是輔助就要接受它可能被殺的事實(shí)保證用戶重新進(jìn)入時(shí)立刻重啟。6.3 進(jìn)程優(yōu)先級(jí)不是保命符最后想強(qiáng)調(diào)一點(diǎn)優(yōu)先級(jí)只是系統(tǒng)在內(nèi)存總量緊張時(shí)評(píng)估“先放棄誰(shuí)”的標(biāo)準(zhǔn)它不能保證你的應(yīng)用永遠(yuǎn)不死。正常使用時(shí)系統(tǒng)不會(huì)殺死前臺(tái)進(jìn)程也不應(yīng)該頻繁殺死前臺(tái)進(jìn)程但當(dāng)某些系統(tǒng)進(jìn)程、安全組件出問(wèn)題時(shí)為了不讓整機(jī)完全死機(jī)系統(tǒng)依然可能強(qiáng)行回收。因此不要給進(jìn)程優(yōu)先級(jí)“鍍金”它是App質(zhì)量的輔助要素不是免死金牌。我在實(shí)際項(xiàng)目中經(jīng)??吹綀F(tuán)隊(duì)為了“不殺進(jìn)程”犧牲啟動(dòng)速度、電量和用戶隱私最后被用戶卸載。反而是那些啟動(dòng)快、數(shù)據(jù)恢復(fù)好、后臺(tái)任務(wù)少的應(yīng)用用戶壓根不會(huì)在意它是不是被回收過(guò)。從這個(gè)角度看進(jìn)程優(yōu)先級(jí)再高也不如把活干完、把狀態(tài)存好、把恢復(fù)做順。我個(gè)人在實(shí)際開(kāi)發(fā)里的習(xí)慣是凡是后臺(tái)任務(wù)先想清楚“是否真的有前臺(tái)感知”沒(méi)有就交給系統(tǒng)的Job機(jī)制去調(diào)度有就正正經(jīng)經(jīng)做成前臺(tái)服務(wù)。調(diào)試時(shí)用dumpsys觀察幾個(gè)關(guān)鍵節(jié)點(diǎn)并專門做一次“后臺(tái)空轉(zhuǎn)測(cè)試”模擬內(nèi)存緊張看看申請(qǐng)結(jié)果如何。這個(gè)辦法幫我提前發(fā)現(xiàn)過(guò)不少數(shù)據(jù)丟失和崩潰問(wèn)題也避免了很多無(wú)意義的“?;罡脑臁?。你也可以從今天開(kāi)始把自己App的各個(gè)進(jìn)程adj全掃一遍很可能會(huì)有意外發(fā)現(xiàn)。