1. 概述
上一期我們已經(jīng)閱讀了源碼Android進(jìn)程間的通信 - IPC(機(jī)制)Binder的原理和源碼閱讀,這一期我們就得用到它了泣港。記得前幾年在公司做購物商城,有一個倒計時的功能价匠,上頭非得要實現(xiàn)這個功能当纱,當(dāng)時的想法就是引咎辭職,因為APP應(yīng)用一退出后臺啟動的Service根本不管用直接被殺掉了踩窖,前幾年的事今天我們就來拯救拯救坡氯。
聲明:
1.文章綜合了很多其他的一些資料,所以非常感謝某些哥們的分享精神洋腮;
2.不能夠保證所有的機(jī)型都適用箫柳,因為我們要面對的是各大Rom廠商;
3.不想帶壞大家啥供,我們應(yīng)該把更多的時間放在內(nèi)存管理和性能優(yōu)化上面悯恍。
所有分享大綱:2017Android進(jìn)階之路與你同行
視頻講解地址:http://pan.baidu.com/s/1pLrvFj9
2. 論Service為什么會被殺死
Service被強(qiáng)一般通常是三個方面,第一種現(xiàn)象是當(dāng)手機(jī)內(nèi)存不足伙狐,你的應(yīng)用或者Service在后臺這個時候你精力或者營養(yǎng)更不上可能就被強(qiáng)了涮毫;第二種現(xiàn)象是用了第三方的一些管理軟件比如流氓360,騰訊管家去清理進(jìn)程他們也會對你下手贷屎;第三種現(xiàn)象就是各大Rom廠商罢防,在你退出應(yīng)用的時候會做一些清理工作。
2.1 進(jìn)程的優(yōu)先級:
google的官網(wǎng)給進(jìn)程劃分了等級唉侄,并且明確告訴你哥們我不會亂來咒吐,回收進(jìn)程的時候有自己套路。擔(dān)心有些哥們不想翻墻這里我就Copy一下属划。
Android 系統(tǒng)將盡量長時間地保持應(yīng)用進(jìn)程恬叹,但為了新建進(jìn)程或運行更重要的進(jìn)程,最終需要移除舊進(jìn)程來回收內(nèi)存同眯。 為了確定保留或終止哪些進(jìn)程妄呕,系統(tǒng)會根據(jù)進(jìn)程中正在運行的組件以及這些組件的狀態(tài),將每個進(jìn)程放入“重要性層次結(jié)構(gòu)”中嗽测。 必要時绪励,系統(tǒng)會首先消除重要性最低的進(jìn)程肿孵,然后是重要性略遜的進(jìn)程,依此類推疏魏,以回收系統(tǒng)資源停做。
重要性層次結(jié)構(gòu)一共有 5 級。以下列表按照重要程度列出了各類進(jìn)程(第一個進(jìn)程最重要大莫,將是最后一個被終止的進(jìn)程):
1.前臺進(jìn)程
用戶當(dāng)前操作所必需的進(jìn)程蛉腌。如果一個進(jìn)程滿足以下任一條件,即視為前臺進(jìn)程:
- 托管用戶正在交互的Activity已調(diào)用Activity的onResume()方法
- 托管某個Service只厘,后者綁定到用戶正在交互的 Activity
- 托管正在“前臺”運行的Service服務(wù)已調(diào)用startForeground()
- 托管正執(zhí)行一個生命周期回調(diào)的Service (onCreate() 烙丛、onStart()或onDestroy())
- 托管正執(zhí)行其onReceive()方法的BroadcastReceiver
通常,在任意給定時間前臺進(jìn)程都為數(shù)不多羔味。只有在內(nèi)存不足以支持它們同時繼續(xù)運行這一萬不得已的情況下河咽,系統(tǒng)才會終止它們。 此時赋元,設(shè)備往往已達(dá)到內(nèi)存分頁狀態(tài)忘蟹,因此需要終止一些前臺進(jìn)程來確保用戶界面正常響應(yīng)。
2.可見進(jìn)程
沒有任何前臺組件搁凸、但仍會影響用戶在屏幕上所見內(nèi)容的進(jìn)程媚值。 如果一個進(jìn)程滿足以下任一條件,即視為可見進(jìn)程:
- 托管不在前臺护糖、但仍對用戶可見的Activity已調(diào)用其onPause()方法褥芒。例如,如果前臺 Activity 啟動了一個對話框嫡良,允許在其后顯示上一 Activity喂很,則有可能會發(fā)生這種情況。
- 托管綁定到可見(或前臺)Activity 的Service皆刺。
可見進(jìn)程被視為是極其重要的進(jìn)程少辣,除非為了維持所有前臺進(jìn)程同時運行而必須終止,否則系統(tǒng)不會終止這些進(jìn)程羡蛾。
3.服務(wù)進(jìn)程
正在運行已使用startService()方法啟動的服務(wù)且不屬于上述兩個更高類別進(jìn)程的進(jìn)程漓帅。盡管服務(wù)進(jìn)程與用戶所見內(nèi)容沒有直接關(guān)聯(lián),但是它們通常在執(zhí)行一些用戶關(guān)心的操作(例如痴怨,在后臺播放音樂或從網(wǎng)絡(luò)下載數(shù)據(jù))忙干。因此,除非內(nèi)存不足以維持所有前臺進(jìn)程和可見進(jìn)程同時運行浪藻,否則系統(tǒng)會讓服務(wù)進(jìn)程保持運行狀態(tài)捐迫。
4.后臺進(jìn)程
包含目前對用戶不可見的 Activity 的進(jìn)程已調(diào)用 Activity 的onStop()方法。這些進(jìn)程對用戶體驗沒有直接影響爱葵,系統(tǒng)可能隨時終止它們施戴,以回收內(nèi)存供前臺進(jìn)程反浓、可見進(jìn)程或服務(wù)進(jìn)程使用。 通常會有很多后臺進(jìn)程在運行赞哗,因此它們會保存在 LRU (最近最少使用)列表中雷则,以確保包含用戶最近查看的 Activity 的進(jìn)程最后一個被終止。如果某個 Activity 正確實現(xiàn)了生命周期方法肪笋,并保存了其當(dāng)前狀態(tài)月劈,則終止其進(jìn)程不會對用戶體驗產(chǎn)生明顯影響,因為當(dāng)用戶導(dǎo)航回該 Activity 時藤乙,Activity 會恢復(fù)其所有可見狀態(tài)猜揪。 有關(guān)保存和恢復(fù)狀態(tài)的信息,請參閱Activity文檔坛梁。
5.空進(jìn)程
不含任何活動應(yīng)用組件的進(jìn)程而姐。保留這種進(jìn)程的的唯一目的是用作緩存,以縮短下次在其中運行組件所需的啟動時間罚勾。 為使總體系統(tǒng)資源在進(jìn)程緩存和底層內(nèi)核緩存之間保持平衡,系統(tǒng)往往會終止這些進(jìn)程吭狡。
根據(jù)進(jìn)程中當(dāng)前活動組件的重要程度尖殃,Android 會將進(jìn)程評定為它可能達(dá)到的最高級別。例如划煮,如果某進(jìn)程托管著服務(wù)和可見 Activity送丰,則會將此進(jìn)程評定為可見進(jìn)程,而不是服務(wù)進(jìn)程弛秋。
此外器躏,一個進(jìn)程的級別可能會因其他進(jìn)程對它的依賴而有所提高,即服務(wù)于另一進(jìn)程的進(jìn)程其級別永遠(yuǎn)不會低于其所服務(wù)的進(jìn)程蟹略。 例如登失,如果進(jìn)程 A 中的內(nèi)容提供程序為進(jìn)程 B 中的客戶端提供服務(wù),或者如果進(jìn)程 A 中的服務(wù)綁定到進(jìn)程 B 中的組件挖炬,則進(jìn)程 A 始終被視為至少與進(jìn)程 B 同樣重要揽浙。
由于運行服務(wù)的進(jìn)程其級別高于托管后臺 Activity 的進(jìn)程,因此啟動長時間運行操作的 Activity 最好為該操作啟動服務(wù)意敛,而不是簡單地創(chuàng)建工作線程馅巷,當(dāng)操作有可能比 Activity 更加持久時尤要如此。例如草姻,正在將圖片上傳到網(wǎng)站的 Activity 應(yīng)該啟動服務(wù)來執(zhí)行上傳钓猬,這樣一來,即使用戶退出 Activity撩独,仍可在后臺繼續(xù)執(zhí)行上傳操作敞曹。使用服務(wù)可以保證账月,無論 Activity 發(fā)生什么情況,該操作至少具備“服務(wù)進(jìn)程”優(yōu)先級异雁。 同理捶障,廣播接收器也應(yīng)使用服務(wù),而不是簡單地將耗時冗長的操作放入線程中纲刀。
以上是google官網(wǎng)的進(jìn)程的優(yōu)先級劃分项炼,就是說越是前面越不容易被殺,越是后面越不容易被殺示绊,那么5是最容易被殺的锭部,下面我們來看一看殺進(jìn)程回收內(nèi)存的機(jī)制Low Memory Killer 到底是怎么對我們下手的:
2.2 LowmemoryKiller的工作機(jī)制:
LowmemoryKiller會在內(nèi)存不足的時候掃描所有的用戶進(jìn)程,找到不是太重要的進(jìn)程殺死面褐,至于LowmemoryKiller殺進(jìn)程夠不夠狠拌禾,要看當(dāng)前的內(nèi)存使用情況,內(nèi)存越少展哭,下手越狠湃窍。在內(nèi)核中,lowmemorykiller.c定義了幾種內(nèi)存回收等級如下:
static short lowmem_adj[6] = {
0,
1,
6,
12,
};
static int lowmem_adj_size = 4;
static int lowmem_minfree[6] = {
3 * 512,
2 * 1024,
4 * 1024,
16 * 1024,
};
static int lowmem_minfree_size = 4;
lowmem_adj中各項數(shù)值代表閾值的警戒級數(shù)匪傍,lowmem_minfree代表對應(yīng)級數(shù)的剩余內(nèi)存您市,兩者一一對應(yīng),比如當(dāng)系統(tǒng)的可用內(nèi)存小于6MB時役衡,警戒級數(shù)為0茵休;當(dāng)系統(tǒng)可用內(nèi)存小于8M而大于6M時,警戒級數(shù)為1手蝎;當(dāng)可用內(nèi)存小于64M大于16MB時榕莺,警戒級數(shù)為12。LowmemoryKiller就是根據(jù)當(dāng)前系統(tǒng)的可用內(nèi)存多少來獲取當(dāng)前的警戒級數(shù)棵介,如果進(jìn)程的oom_adj大于警戒級數(shù)并且占內(nèi)存最大钉鸯,將會被優(yōu)先殺死。omm_adj越小邮辽,代表進(jìn)程越重要亏拉。一些前臺的進(jìn)程,oom_adj會比較小逆巍,而后臺的服務(wù)及塘,omm_adj會比較大,所以當(dāng)內(nèi)存不足的時候锐极,Lowmemorykiller先殺掉的是后臺服務(wù)而不是前臺的進(jìn)程笙僚。對于LowmemoryKiller的殺死,這里有一句話很重要灵再,就是: 具有相同omm_adj的進(jìn)程肋层,則殺死占用內(nèi)存較多的亿笤,因此,如果我們的APP進(jìn)入后臺栋猖,就盡量釋放不必要的資源净薛,以降低自己被殺的風(fēng)險。簡單看一下實現(xiàn)源碼:
static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask)
{
...
<!--獲取free內(nèi)存狀況-->
int other_free = global_page_state(NR_FREE_PAGES);
int other_file = global_page_state(NR_FILE_PAGES);
<!--找到min_adj -->
for(i = 0; i < array_size; i++) {
if (other_free < lowmem_minfree[i] &&
other_file < lowmem_minfree[i]) {
min_adj = lowmem_adj[i];
break;
}
}
<!--找到p->oomkilladj>min_adj并且oomkilladj最大蒲拉,內(nèi)存最大的進(jìn)程-->
for_each_process(p) {
// 找到第一個大于等于min_adj的肃拜,也就是優(yōu)先級比閾值低的
if (p->oomkilladj < min_adj || !p->mm)
continue;
// 找到tasksize這個是什么呢
tasksize = get_mm_rss(p->mm);
if (tasksize <= 0)
continue;
if (selected) {
// 找到優(yōu)先級最低,并且內(nèi)存占用大的
if (p->oomkilladj < selected->oomkilladj)
continue;
if (p->oomkilladj == selected->oomkilladj &&
tasksize <= selected_tasksize)
continue;
}
selected = p;
selected_tasksize = tasksize;
lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n",
p->pid, p->comm, p->oomkilladj, tasksize);
}
if(selected != NULL) {...
force_sig(SIGKILL, selected);
}
return rem;
}
那么現(xiàn)在我們就可以針對性的做一些工作了比如:
1.提高進(jìn)程的優(yōu)先級雌团,其實就是減小進(jìn)程的p->oomkilladj(越小越重要)燃领,如啟動Service調(diào)用startForeground()盡量提高進(jìn)程的優(yōu)先級;
2.當(dāng)應(yīng)用推到后臺適當(dāng)釋放資源然后降低APP的內(nèi)存占用量锦援,因為在oom_adj相同的時候猛蔽,會優(yōu)先干掉內(nèi)存消耗大的進(jìn)程;
3.對于要一直在后臺運行的Service我們一定要輕灵寺,不能太風(fēng)騷曼库。
3. 針對各大Rom廠商和清理軟件:
我其實要面對的不是google原生的系統(tǒng),在各大手機(jī)廠商以及各種流氓清理軟件面前我們按套路出牌往往然并卵略板,我們要面對的也是他們毁枯,這也是說往往寫的代碼你會發(fā)現(xiàn)這里行那里不行,真是心中有一萬只動物蚯根。會發(fā)現(xiàn)采用上面的方式根本沒用后众,網(wǎng)上也有很多的解決方案胀糜,如QQ的1像素的Activity颅拦,某些第三方應(yīng)用救救你,NDK方式hook子線程等等等等教藻。我們這里決定采用雙進(jìn)程守護(hù)相互喚醒以及5.0以上的JobSheduler:
public class MessageService extends Service {
private static final String TAG = "MessageService";
private MessageServiceConnection mServiceConnection;
private MessageBind mMessageBind;
@Override
public void onCreate() {
super.onCreate();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
Log.e(TAG, "等待接收消息");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
if (mServiceConnection == null) {
mServiceConnection = new MessageServiceConnection();
}
if (mMessageBind == null) {
mMessageBind = new MessageBind();
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
MessageService.this.bindService(new Intent(MessageService.this, GuardService.class),
mServiceConnection, Context.BIND_IMPORTANT);
return Service.START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mMessageBind;
}
private class MessageBind extends ProcessConnection.Stub {
@Override
public void processConnected() throws RemoteException {
}
}
private class MessageServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 建立連接
Toast.makeText(MessageService.this, "建立連接", Toast.LENGTH_LONG).show();
}
@Override
public void onServiceDisconnected(ComponentName name) {
// 斷開連接
Toast.makeText(MessageService.this, "斷開連接", Toast.LENGTH_LONG).show();
Intent guardIntent = new Intent(MessageService.this, GuardService.class);
// 發(fā)現(xiàn)斷開我就從新啟動和綁定
startService(guardIntent);
MessageService.this.bindService(guardIntent,
mServiceConnection, Context.BIND_IMPORTANT);
}
}
}
雙進(jìn)程守護(hù)和喚醒在5.0以下還是很爽的距帅,至少能保證各種流氓的清理軟件不能夠清理掉我們的Service,但是在5.0以后就不管用了括堤,尤其實在小米碌秸、魅族這些手機(jī)上面。不過柳暗花明又一村悄窃,我們可以利用JobSheduler讥电。
/**
* Created by Darren on 2017/3/25.
* Email: 240336124@qq.com
* Description:
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class JobAwakenService extends JobService{
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
JobInfo.Builder builder = new JobInfo.Builder(1,new ComponentName(this,JobAwakenService.class));
builder.setPeriodic(500);
JobInfo jobInfo = builder.build();
JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(jobInfo);
return START_STICKY;
}
@Override
public boolean onStartJob(JobParameters params) {
Log.e("TAG","onStartJob");
// boolean isGuardAlive = isServiceWork(this,GuardService.class.getName());
boolean isMessageAlive = isServiceWork(this,MessageService.class.getName());
if(!isMessageAlive){
// startService(new Intent(this,GuardService.class));
startService(new Intent(this,MessageService.class));
}
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
/**
* 判斷某個服務(wù)是否正在運行的方法
*
* @param mContext
* @param serviceName
* 是包名+服務(wù)的類名(例如:net.loonggg.testbackstage.TestService)
* @return true代表正在運行,false代表服務(wù)沒有正在運行
*/
public boolean isServiceWork(Context mContext, String serviceName) {
boolean isWork = false;
ActivityManager myAM = (ActivityManager) mContext
.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> myList = myAM.getRunningServices(100);
if (myList.size() <= 0) {
return false;
}
for (int i = 0; i < myList.size(); i++) {
String mName = myList.get(i).service.getClassName().toString();
if (mName.equals(serviceName)) {
isWork = true;
break;
}
}
return isWork;
}
}
所有分享大綱:2017Android進(jìn)階之路與你同行