紅色部分是容易被回收的進(jìn)程扶檐,屬于android進(jìn)程
綠色部分是較難被回收的進(jìn)程,屬于android進(jìn)程
其他部分則不是android進(jìn)程胁艰,也不會被系統(tǒng)回收款筑,一般是ROM自帶的app和服務(wù)才能擁有
如何查看某個進(jìn)程的oom_adj數(shù)值呢?
oom_adj 存儲在proc/PID/oom_adj文件中腾么,其中PID是進(jìn)程的id奈梳,直接 adb shell進(jìn)入手機根目錄查看這個文件即可。
演示一下:以我自己的項目為例解虱,app中有兩個進(jìn)程攘须,一個是主進(jìn)程,另一個是運行service的進(jìn)程取名為:remote殴泰。首先用android studio查看每個進(jìn)程的PID:
然后分別查看app在前臺于宙,app退到后臺,這2中場景主進(jìn)程的oom_adj數(shù)值:
可見艰匙,當(dāng)app在前臺時 oom_adj = 0限煞,對應(yīng)上面的表格是前臺進(jìn)程。
當(dāng)app退到后臺時员凝,oom_adj = 6署驻,對應(yīng)后臺進(jìn)程。 然后查看運行著service的進(jìn)程:
ok健霹,知道了進(jìn)程優(yōu)先級的概念以及如何查看優(yōu)先級旺上,我們就可以對app進(jìn)程優(yōu)化,然后通過查看這個數(shù)值判斷我們的優(yōu)化是否有效果糖埋。
進(jìn)程被kill的場景
1.點擊home鍵使app長時間停留在后臺宣吱,內(nèi)存不足被kill
處理這種情況前提是你的app至少運行了一個service,然后通過Service.startForeground() 設(shè)置為前臺服務(wù)瞳别,可以將oom_adj的數(shù)值由4降低到1征候,大大提高存活率杭攻。
要注意的是android4.3之后Service.startForeground() 會強制彈出通知欄,解決辦法是再啟動一個service和推送共用一個通知欄疤坝,然后stop這個service使得通知欄消失兆解。
Android 7.1之后google修復(fù)這個bug,目前沒有解決辦法
下面的代碼放到你的service的onStartCommand方法中:
//設(shè)置service為前臺服務(wù)跑揉,提高優(yōu)先級
? ? ? ? if (Build.VERSION.SDK_INT < 18) {
? ? ? ? ? ? //Android4.3以下 锅睛,此方法能有效隱藏Notification上的圖標(biāo)
? ? ? ? ? ? service.startForeground(GRAY_SERVICE_ID, new Notification());
? ? ? ? } else if(Build.VERSION.SDK_INT>18 && Build.VERSION.SDK_INT<25){
? ? ? ? ? ? //Android4.3 - Android7.0,此方法能有效隱藏Notification上的圖標(biāo)
? ? ? ? ? ? Intent innerIntent = new Intent(service, GrayInnerService.class);
? ? ? ? ? ? service.startService(innerIntent);
? ? ? ? ? ? service.startForeground(GRAY_SERVICE_ID, new Notification());
? ? ? ? }else{
? ? ? ? ? ? //Android7.1 google修復(fù)了此漏洞历谍,暫無解決方法(現(xiàn)狀:Android7.1以上app啟動后通知欄會出現(xiàn)一條"正在運行"的通知消息)
? ? ? ? ? ? service.startForeground(GRAY_SERVICE_ID, new Notification());
? ? ? ? }
經(jīng)過改進(jìn)之后现拒,再來看下這個后臺service進(jìn)程的oom_adj,發(fā)現(xiàn)被提升為前臺進(jìn)程望侈。
2.在大多數(shù)國產(chǎn)手機下印蔬,進(jìn)入鎖屏狀態(tài)一段時間,省電機制會kill后臺進(jìn)程
這種情況和上面不太一樣甜无,是很過國產(chǎn)手機rom自帶的優(yōu)化扛点,當(dāng)鎖屏一段時間之后,即使手機內(nèi)存夠用為了省電岂丘,也會釋放掉一部分內(nèi)存陵究。
策略:注冊廣播監(jiān)聽鎖屏和解鎖事件, 鎖屏后啟動一個1像素的透明Activity奥帘,這樣直接把進(jìn)程的oom_adj數(shù)值降低到0铜邮,0是android進(jìn)程的最高優(yōu)先級。 解鎖后銷毀這個透明Activity寨蹋。這里我把這個Activity放到:remote進(jìn)程也就是我那個后臺服務(wù)進(jìn)程松蒜,當(dāng)然你也可以放到主進(jìn)程,看你打算币丫桑活哪個進(jìn)程秸苗。
我們可以寫一個KeepLiveManager來負(fù)責(zé)接收廣播,維護(hù)這個Activity的常見和銷毀运褪,注意鎖屏廣播和解鎖分別是:ACTION_SCREEN_OOF和ACTION_USER_PRESENT惊楼,并且只能通過動態(tài)注冊來綁定,并且是綁定到你的后臺service里面,onCreate綁定,onDestroy里面解綁
配好之后把手機鎖屏秸讹,看下:remote進(jìn)程的oom_adj:
3. 用戶手動釋放內(nèi)存:包括手機自帶清理工具檀咙,和第三方app(360,獵豹清理大師等)
清理內(nèi)存軟件會把 優(yōu)先級低于 前臺進(jìn)程(oom_adj = 0)的所有進(jìn)程放入清理列表璃诀,而當(dāng)我們打開了清理軟件就意味著其他app不可能處于前臺弧可。所以說理論上可以kill任何app。
以360安全衛(wèi)士為例劣欢,打開內(nèi)存清理:
因此這類場景唯一的處理辦法就是加入 手機rom 白名單棕诵,比如你打開小米裁良,魅族的權(quán)限管理 -> 自啟動管理可以看到 QQ,微信校套,天貓默認(rèn)被勾選趴久,這就是廠商合作。那我們普通app可以這么做:在app的設(shè)置界面加一個選項搔确,提示用戶自己去勾選自啟動,我封裝了一個工具類給出國內(nèi)各廠商的自啟動的Intent跳轉(zhuǎn)方法:
/**
* Created by carmelo on 2018/3/17.
* 國內(nèi)手機廠商白名單跳轉(zhuǎn)工具類
*/
public class SettingUtils {
? ? public static void enterWhiteListSetting(Context context){
? ? ? ? try {
? ? ? ? ? ? context.startActivity(getSettingIntent());
? ? ? ? }catch (Exception e){
? ? ? ? ? ? context.startActivity(new Intent(Settings.ACTION_SETTINGS));
? ? ? ? }
? ? }
? ? private static Intent getSettingIntent(){
? ? ? ? ComponentName componentName = null;
? ? ? ? String brand = android.os.Build.BRAND;
? ? ? ? switch (brand.toLowerCase()){
? ? ? ? ? ? case "samsung":
? ? ? ? ? ? ? ? componentName = new ComponentName("com.samsung.android.sm",
? ? ? ? ? ? ? ? ? ? ? ? "com.samsung.android.sm.app.dashboard.SmartManagerDashBoardActivity");
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case "huawei":
? ? ? ? ? ? ? ? componentName = new ComponentName("com.huawei.systemmanager",
? ? ? ? ? ? ? ? ? ? ? ? "com.huawei.systemmanager.startupmgr.ui.StartupNormalAppListActivity");
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case "xiaomi":
? ? ? ? ? ? ? ? componentName = new ComponentName("com.miui.securitycenter",
? ? ? ? ? ? ? ? ? ? ? ? "com.miui.permcenter.autostart.AutoStartManagementActivity");
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case "vivo":
? ? ? ? ? ? ? ? componentName = new ComponentName("com.iqoo.secure",
? ? ? ? ? ? ? ? ? ? ? ? "com.iqoo.secure.ui.phoneoptimize.AddWhiteListActivity");
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case "oppo":
? ? ? ? ? ? ? ? componentName = new ComponentName("com.coloros.oppoguardelf",
? ? ? ? ? ? ? ? ? ? ? ? "com.coloros.powermanager.fuelgaue.PowerUsageModelActivity");
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case "360":
? ? ? ? ? ? ? ? componentName = new ComponentName("com.yulong.android.coolsafe",
? ? ? ? ? ? ? ? ? ? ? ? "com.yulong.android.coolsafe.ui.activity.autorun.AutoRunListActivity");
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case "meizu":
? ? ? ? ? ? ? ? componentName = new ComponentName("com.meizu.safe",
? ? ? ? ? ? ? ? ? ? ? ? "com.meizu.safe.permission.SmartBGActivity");
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? case "oneplus":
? ? ? ? ? ? ? ? componentName = new ComponentName("com.oneplus.security",
? ? ? ? ? ? ? ? ? ? ? ? "com.oneplus.security.chainlaunch.view.ChainLaunchAppListActivity");
? ? ? ? ? ? ? ? break;
? ? ? ? ? ? default:
? ? ? ? ? ? ? ? break;
? ? ? ? }
? ? ? ? Intent intent = new Intent();
? ? ? ? intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
? ? ? ? if(componentName!=null){
? ? ? ? ? ? intent.setComponent(componentName);
? ? ? ? }else{
? ? ? ? ? ? intent.setAction(Settings.ACTION_SETTINGS);
? ? ? ? }
? ? ? ? return intent;
? ? }
}
補充幾點:
額外說下 自啟動是什么意思灭忠? 網(wǎng)上都是小米開啟自啟動就是加入白名單膳算,其實根本不是這樣的,經(jīng)過實測就算app勾選自啟動也會被內(nèi)存優(yōu)化加速清理掉弛作,只不過進(jìn)程會在半分鐘后復(fù)活涕蜂。
除了還有自啟動還有一個設(shè)置就是電池管理,比如小米的神隱模式映琳,這部分和自啟動不同的是它是管理app在鎖屏之后被省電機制殺死的場景机隙,當(dāng)然每家廠商也有對應(yīng)的Intent跳轉(zhuǎn)路徑。
如何查找不同廠商的設(shè)置界面跳轉(zhuǎn)Intent萨西,比如上面的國內(nèi)手機廠商白名單?
在酷安應(yīng)用市場下載一個叫 當(dāng)前Activity 的app有鹿,打開后可以看到當(dāng)前界面的className,例如:
就找到了魅族MX4 pro 后臺權(quán)限的Activity谎脯。
進(jìn)程喚醒
分兩種情況葱跋,一是主進(jìn)程(含有Activity沒有service),這種進(jìn)程由于內(nèi)存不足被kill之后源梭,用戶再次打開app系統(tǒng)會恢復(fù)到上次的Activity娱俺,這個不在本文話題之內(nèi)。另一種是service的后臺進(jìn)程被kill废麻,可以通過service自有api來重啟service:
@Override
? ? public int onStartCommand(Intent intent, int flags, int startId) {
? ? ? ? //.....
? ? ? ? return START_STICKY;? ? // service被異常停止后荠卷,系統(tǒng)嘗試重啟service,不能保證100%重啟成功
? ? }
配好START_STICKY后烛愧,通過android studio 釋放進(jìn)程的工具測試下油宜,可以發(fā)現(xiàn):remote進(jìn)程被kill之后馬上重啟了:
但它不是100%保證重啟成功,比如下面2種情況:(本人經(jīng)過測試屑彻,這里就不放效果圖了)
Service 第一次被異常殺死后會在5秒內(nèi)重啟验庙,第二次被殺死會在10秒內(nèi)重啟,第三次會在20秒內(nèi)重啟社牲,一旦在短時間內(nèi) Service 被殺死達(dá)到5次粪薛,則系統(tǒng)不再拉起。
進(jìn)程被取得 Root 權(quán)限的管理工具或系統(tǒng)工具通過 forestop 停止掉搏恤,無法重啟违寿。
總結(jié)
本文通過兩種 提高進(jìn)程優(yōu)先級的方法湃交,針對鎖屏 和非鎖屏模式下進(jìn)程在后臺被kill的場景處理,把后臺進(jìn)程優(yōu)先級提升到可見級別藤巢,基本可以保證絕大多數(shù)場景不會被kill搞莺。另外,針對含有service的進(jìn)程被kill給出了可喚醒的辦法掂咒。
后來收到不少留言評論才沧,大多數(shù)都是講這個進(jìn)程保活對很多手機沒有作用绍刮。我一直沒有回復(fù)温圆,因為我們項目在使用這個進(jìn)程保活策略時孩革,同時也加入了進(jìn)程存活時間的Log記錄機制岁歉,目的就是想看下有效果沒。后臺service的啟動就開啟計時器膝蜈,以分鐘為單位不停寫入SharePreference锅移,進(jìn)程被kill這個值就是存活時間(min),同時記錄機型饱搏,Android版本等信息非剃,以Exception的格式封裝上傳到bugly。由于是純手動分析數(shù)據(jù)很麻煩窍帝,最后取了1000條數(shù)據(jù)涵蓋了Android5.0-Android8.0努潘,小米,華為坤学,三星疯坤,oppo/vivo,金立等各種機型。
結(jié)論
和Android版本關(guān)系很大
對于Android6.0以及以下的大部分機型還是有效果的深浮,但是Android7.0和Android8.0基本上所有機型全部陣亡压怠,大部分后臺進(jìn)程在鎖屏后無法存活超過20分鐘。
這個可以從Android 6.0飞苇,7.0和8.0的新特性看出一些端倪菌瘫,google對于內(nèi)存/電量使用越來越嚴(yán)格。
2.和手機廠商關(guān)系比較大布卡,測試結(jié)果顯示雨让,oppo/vivo這兩家廠商進(jìn)程保活最困難忿等,小米和三星比較寬松栖忠。其他的機型居中。
3.綜合結(jié)果是,鑒于目前國內(nèi)大部分手機基本都到7.0了庵寞,進(jìn)程崩晗啵活暫時可以說是宣告失敗了。除了加入廠商白名單之外我不認(rèn)為有真正可行的方案捐川,畢竟google近幾個Android版本不停在系統(tǒng)層面限制脓鹃,我們在app層沒有很大的發(fā)揮空間,so古沥,之前的方案只能作為一個優(yōu)化策略吧瘸右。