前言
進(jìn)程保活的關(guān)鍵點(diǎn)有兩個(gè)搭独,一個(gè)是進(jìn)程優(yōu)先級(jí)的理解婴削,優(yōu)先級(jí)越高存活幾率越大。二是弄清楚哪些場(chǎng)景會(huì)導(dǎo)致進(jìn)程會(huì)kill牙肝,然后采取下面的策略對(duì)各種場(chǎng)景進(jìn)行優(yōu)化:
- 提高進(jìn)程的優(yōu)先級(jí)
- 在進(jìn)程被kill之后能夠喚醒
一. 進(jìn)程優(yōu)先級(jí)
Android一般的進(jìn)程優(yōu)先級(jí)劃分: 1.前臺(tái)進(jìn)程 (Foreground process) 2.可見進(jìn)程 (Visible process) 3.服務(wù)進(jìn)程 (Service process) 4.后臺(tái)進(jìn)程 (Background process) 5.空進(jìn)程 (Empty process) 這是一種粗略的劃分唉俗,進(jìn)程其實(shí)有一種具體的數(shù)值嗤朴,稱作oom_adj,注意:數(shù)值越大優(yōu)先級(jí)越低:
- 紅色部分是容易被回收的進(jìn)程虫溜,屬于android進(jìn)程
- 綠色部分是較難被回收的進(jìn)程雹姊,屬于android進(jìn)程
- 其他部分則不是android進(jìn)程,也不會(huì)被系統(tǒng)回收衡楞,一般是ROM自帶的app和服務(wù)才能擁有
如何查看某個(gè)進(jìn)程的oom_adj數(shù)值呢容为? oom_adj 存儲(chǔ)在proc/PID/oom_adj文件中,其中PID是進(jìn)程的id寺酪,直接 adb shell進(jìn)入手機(jī)根目錄查看這個(gè)文件即可。
演示一下:以我自己的項(xiàng)目為例替劈,app中有兩個(gè)進(jìn)程寄雀,一個(gè)是主進(jìn)程,另一個(gè)是運(yùn)行service的進(jìn)程取名為:remote陨献。首先用android studio查看每個(gè)進(jìn)程的PID:
然后分別查看app在前臺(tái)盒犹,app退到后臺(tái),這2中場(chǎng)景主進(jìn)程的oom_adj數(shù)值:
可見眨业,當(dāng)app在前臺(tái)時(shí) oom_adj = 0急膀,對(duì)應(yīng)上面的表格是前臺(tái)進(jìn)程。
當(dāng)app退到后臺(tái)時(shí)龄捡,oom_adj = 6卓嫂,對(duì)應(yīng)后臺(tái)進(jìn)程。
然后查看運(yùn)行著service的進(jìn)程:
ok聘殖,知道了進(jìn)程優(yōu)先級(jí)的概念以及如何查看優(yōu)先級(jí)晨雳,我們就可以對(duì)app進(jìn)程優(yōu)化,然后通過查看這個(gè)數(shù)值判斷我們的優(yōu)化是否有效果奸腺。
二.進(jìn)程被kill的場(chǎng)景
1.點(diǎn)擊home鍵使app長(zhǎng)時(shí)間停留在后臺(tái)餐禁,內(nèi)存不足被kill
處理這種情況前提是你的app至少運(yùn)行了一個(gè)service,然后通過Service.startForeground() 設(shè)置為前臺(tái)服務(wù)突照,可以將oom_adj的數(shù)值由4降低到1帮非,大大提高存活率。
要注意的是android4.3之后Service.startForeground() 會(huì)強(qiáng)制彈出通知欄讹蘑,解決辦法是再啟動(dòng)一個(gè)service和推送共用一個(gè)通知欄末盔,然后stop這個(gè)service使得通知欄消失。
Android 7.1之后google修復(fù)這個(gè)bug衔肢,目前沒有解決辦法 下面的代碼放到你的service的onStartCommand方法中:
//設(shè)置service為前臺(tái)服務(wù)庄岖,提高優(yōu)先級(jí)
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啟動(dòng)后通知欄會(huì)出現(xiàn)一條"正在運(yùn)行"的通知消息)
service.startForeground(GRAY_SERVICE_ID, new Notification());
}
經(jīng)過改進(jìn)之后心剥,再來看下這個(gè)后臺(tái)service進(jìn)程的oom_adj,發(fā)現(xiàn)被提升為前臺(tái)進(jìn)程背桐。
2.在大多數(shù)國(guó)產(chǎn)手機(jī)下优烧,進(jìn)入鎖屏狀態(tài)一段時(shí)間,省電機(jī)制會(huì)kill后臺(tái)進(jìn)程
這種情況和上面不太一樣链峭,是很過國(guó)產(chǎn)手機(jī)rom自帶的優(yōu)化畦娄,當(dāng)鎖屏一段時(shí)間之后,即使手機(jī)內(nèi)存夠用為了省電弊仪,也會(huì)釋放掉一部分內(nèi)存熙卡。
策略:注冊(cè)廣播監(jiān)聽鎖屏和解鎖事件, 鎖屏后啟動(dòng)一個(gè)1像素的透明Activity励饵,這樣直接把進(jìn)程的oom_adj數(shù)值降低到0驳癌,0是android進(jìn)程的最高優(yōu)先級(jí)。 解鎖后銷毀這個(gè)透明Activity役听。這里我把這個(gè)Activity放到:remote進(jìn)程也就是我那個(gè)后臺(tái)服務(wù)進(jìn)程颓鲜,當(dāng)然你也可以放到主進(jìn)程,看你打算钡溆瑁活哪個(gè)進(jìn)程甜滨。
我們可以寫一個(gè)KeepLiveManager來負(fù)責(zé)接收廣播,維護(hù)這個(gè)Activity的常見和銷毀瘤袖,注意鎖屏廣播和解鎖分別是:ACTION_SCREEN_OOF和ACTION_USER_PRESENT衣摩,并且只能通過動(dòng)態(tài)注冊(cè)來綁定,并且是綁定到你的后臺(tái)service里面,onCreate綁定,onDestroy里面解綁
配好之后把手機(jī)鎖屏孽椰,看下:remote進(jìn)程的oom_adj:
3. 用戶手動(dòng)釋放內(nèi)存:包括手機(jī)自帶清理工具昭娩,和第三方app(360,獵豹清理大師等)
清理內(nèi)存軟件會(huì)把 優(yōu)先級(jí)低于 前臺(tái)進(jìn)程(oom_adj = 0)的所有進(jìn)程放入清理列表黍匾,而當(dāng)我們打開了清理軟件就意味著其他app不可能處于前臺(tái)栏渺。所以說理論上可以kill任何app。 以360安全衛(wèi)士為例锐涯,打開內(nèi)存清理:
因此這類場(chǎng)景唯一的處理辦法就是加入 手機(jī)rom 白名單磕诊,比如你打開小米,魅族的權(quán)限管理 -> 自啟動(dòng)管理可以看到 QQ纹腌,微信霎终,天貓默認(rèn)被勾選,這就是廠商合作升薯。那我們普通app可以這么做:在app的設(shè)置界面加一個(gè)選項(xiàng)莱褒,提示用戶自己去勾選自啟動(dòng),我封裝了一個(gè)工具類給出國(guó)內(nèi)各廠商的自啟動(dòng)的Intent跳轉(zhuǎn)方法:
/**
* Created by carmelo on 2018/3/17.
* 國(guó)內(nèi)手機(jī)廠商白名單跳轉(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;
}
}
補(bǔ)充幾點(diǎn):
額外說下 自啟動(dòng)是什么意思涎劈? 網(wǎng)上都是小米開啟自啟動(dòng)就是加入白名單广凸,其實(shí)根本不是這樣的阅茶,經(jīng)過實(shí)測(cè)就算app勾選自啟動(dòng)也會(huì)被內(nèi)存優(yōu)化加速清理掉,只不過進(jìn)程會(huì)在半分鐘后復(fù)活谅海。
除了還有自啟動(dòng)還有一個(gè)設(shè)置就是電池管理脸哀,比如小米的神隱模式,這部分和自啟動(dòng)不同的是它是管理app在鎖屏之后被省電機(jī)制殺死的場(chǎng)景扭吁,當(dāng)然每家廠商也有對(duì)應(yīng)的Intent跳轉(zhuǎn)路徑撞蜂。
-
如何查找不同廠商的設(shè)置界面跳轉(zhuǎn)Intent,比如上面的國(guó)內(nèi)手機(jī)廠商白名單侥袜,給個(gè)方法:(前提是你有N多個(gè)不同手機(jī)蝌诡,像我這樣。枫吧。送漠。)
image
在酷安應(yīng)用市場(chǎng)下載一個(gè)叫 當(dāng)前Activity 的app,打開后可以看到當(dāng)前界面的className由蘑,例如:
就找到了魅族MX4 pro 后臺(tái)權(quán)限的Activity。
三.進(jìn)程喚醒
分兩種情況代兵,一是主進(jìn)程(含有Activity沒有service)尼酿,這種進(jìn)程由于內(nèi)存不足被kill之后,用戶再次打開app系統(tǒng)會(huì)恢復(fù)到上次的Activity植影,這個(gè)不在本文話題之內(nèi)裳擎。另一種是service的后臺(tái)進(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)程的工具測(cè)試下谷饿,可以發(fā)現(xiàn):remote進(jìn)程被kill之后馬上重啟了:
但它不是100%保證重啟成功惶我,比如下面2種情況:(本人經(jīng)過測(cè)試,這里就不放效果圖了)
- Service 第一次被異常殺死后會(huì)在5秒內(nèi)重啟博投,第二次被殺死會(huì)在10秒內(nèi)重啟绸贡,第三次會(huì)在20秒內(nèi)重啟,一旦在短時(shí)間內(nèi) Service 被殺死達(dá)到5次毅哗,則系統(tǒng)不再拉起听怕。
- 進(jìn)程被取得 Root 權(quán)限的管理工具或系統(tǒng)工具通過 forestop 停止掉,無法重啟虑绵。
總結(jié)
本文通過兩種 提高進(jìn)程優(yōu)先級(jí)的方法尿瞭,針對(duì)鎖屏 和非鎖屏模式下進(jìn)程在后臺(tái)被kill的場(chǎng)景處理,把后臺(tái)進(jìn)程優(yōu)先級(jí)提升到可見級(jí)別翅睛,基本可以保證絕大多數(shù)場(chǎng)景不會(huì)被kill声搁。另外黑竞,針對(duì)含有service的進(jìn)程被kill給出了可喚醒的辦法。