程序保活攻略 —— 技術積累
- Android 程序優先順序介紹
- Android 程序回收策略介紹
- Android 目前系統保活策略
- 專案中使用程序保活程式碼
1、Android程序等級分級和等級介紹
Android 系統將盡量長時間地保持應用的程序,但是系統執行記憶體是有限的,所以為了 新建程序或者執行更重要的程序,最終還是需要清除舊程序來回收記憶體。
所以分割槽程序重要程度,系統會根據程序的執行元件和元件的狀態,將每一個程序放入“重要性層級結構”,必要時系統會首先清除掉重要性最低的程序,在記憶體不足時,再清除重要性稍低的程序,以此類推,以回收系統資源。
程序重要性分級
前臺程序
可見程序
服務程序
後臺程序
空程序
前臺程序:使用者當前操作所必需的程序,只有在內部不足以支它們同時執行。系統才會終止它們。
- 使用者正在互動的Activity(已呼叫 onResume())
- Service,繫結在使用者正在互動的Activity
- 正在前臺執行的Service(服務已經呼叫 startForeground())
- 正在執行生命週期的Service(onCreat,onStart,onDestory)
- 正在執行 onReceive 方法的廣播 BrocadcastReceiver
可見程序:沒有任何前臺元件,但是仍然會影響使用者在螢幕上所見內容的程序。
- 不在前臺,但是使用者仍然可見(已呼叫 onPause())例如沒dialog覆蓋
- 繫結在可見 Activity的Service
服務程序:沒有任何前臺元件,但是通過它們執行一些使用者關心的操作(音樂播放,網路下載)
- 正在執行 startService() 方法啟動的服務
後臺服務:後臺程序對使用者體驗沒有直接影響,系統可以隨時終止它們
- 對使用者不可見的Activity的程序,(已呼叫 onstop方法)
空程序:保留這種程序的唯一作用是用做快取,縮短下次在執行元件所需的啟動時間
- 不含任何活動元件的程序
2、Android 程序回收策略
眾所周知,Android是基於Linux系統的。在Android程序回收策略中,Android程序與Linux程序根據OOM_ADJ閾值進行區分:
OOM_ADJ >= 4:比較容易被殺死的程序
OOM_ADJ 0 ~ 3:不容易被殺死的程序
OOM_ADJ < 0 :純Linux程序,非Android程序
當Android系統察覺裝置記憶體不足時,會按照閾值從大到小殺死程序。
具體的oom_adj值的意義我們可以檢視AOSP中的com.android.server.am.ProcessList 檔案(其中本人添加了一些中文註釋):
/**
1. Activity manager code dealing with processes.
*/
final class ProcessList {
...
// OOM adjustments for processes in various states:
// Adjustment used in certain places where we don't know it yet.
// (Generally this is something that is going to be cached, but we
// don't know the exact value in the cached range to assign yet.)
// 未知程序,通常是用作快取
static final int UNKNOWN_ADJ = 16;
// This is a process only hosting activities that are not visible,
// so it can be killed without any disruption.
// 擁有不可視的Activity的程序,可以不影響影響使用者的情況下殺掉
static final int CACHED_APP_MAX_ADJ = 15;
static final int CACHED_APP_MIN_ADJ = 9;
// The B list of SERVICE_ADJ -- these are the old and decrepit
// services that aren't as shiny and interesting as the ones in the A list.
// 一些舊的服務程序
static final int SERVICE_B_ADJ = 8;
// This is the process of the previous application that the user was in.
// This process is kept above other things, because it is very common to
// switch back to the previous app. This is important both for recent
// task switch (toggling between the two top recent apps) as well as normal
// UI flow such as clicking on a URI in the e-mail app to view in the browser,
// and then pressing back to return to e-mail.
// 使用者使用的前一個程序
static final int PREVIOUS_APP_ADJ = 7;
// This is a process holding the home application -- we want to try
// avoiding killing it, even if it would normally be in the background,
// because the user interacts with it so much.
// 主介面程序
static final int HOME_APP_ADJ = 6;
// This is a process holding an application service -- killing it will not
// have much of an impact as far as the user is concerned.
// 持有應用服務的程序
static final int SERVICE_ADJ = 5;
// This is a process with a heavy-weight application. It is in the
// background, but we want to try to avoid killing it. Value set in
// system/rootdir/init.rc on startup.
// 重量級應用程序
static final int HEAVY_WEIGHT_APP_ADJ = 4;
// This is a process currently hosting a backup operation. Killing it
// is not entirely fatal but is generally a bad idea.
// 執行備份操作的程序
static final int BACKUP_APP_ADJ = 3;
// This is a process only hosting components that are perceptible to the
// user, and we really want to avoid killing them, but they are not
// immediately visible. An example is background music playback.
// 擁有使用者可感知元件的程序
static final int PERCEPTIBLE_APP_ADJ = 2;
// This is a process only hosting activities that are visible to the
// user, so we'd prefer they don't disappear.
// 擁有使用者僅可見、不可互動的Activity的程序
static final int VISIBLE_APP_ADJ = 1;
// This is the process running the current foreground app. We'd really
// rather not kill it!
// 前臺執行的程序
static final int FOREGROUND_APP_ADJ = 0;
// This is a system persistent process, such as telephony. Definitely
// don't want to kill it, but doing so is not completely fatal.
// 系統常駐程序
static final int PERSISTENT_PROC_ADJ = -12;
// The system process runs at the default adjustment.
// 系統程序
static final int SYSTEM_ADJ = -16;
// Special code for native processes that are not being managed by the system (so
// don't have an oom adj assigned by the system).
// 為native程序保留,他們不被系統管理
static final int NATIVE_ADJ = -17;
...
}
Android 程序被殺死情況:
①觸發系統程序管理機制回收(Lowmemorykiller):這種方法會按照閾值從大到小進行清理
②被沒有進行Root的第三方應用殺死(使用killBackgroundProcess方法):這種方法只能殺死OOM_ADJ為4以上的程序
③被進行Root的第三方應用殺死(使用force-stop或者kill):理論上來說可以殺死所有程序,但一般只會清理非系統關鍵程序和非前臺可見程序
④廠商的殺程序功能(force-stop或者kill):理論上來說可以殺死所有程序,包括Linux原生程序
⑤使用者主動“強行停止”程序(force-stop):只能停用第三方和非system/phone程序應用(停用system程序應用會造成Android系統重啟)
我們可以通過adb shell命令實時檢視這個adj值。
adb shell
ps | grep <關鍵字>
USER PID PPID VSIZE RSS WCHAN PC NAME
root 1 0 812 668 ffffffff 00000000 S /init
root 2 0 0 0 ffffffff 00000000 S kthreadd
adb shell
cat /proc/PID/oom_adj
cat命令執行後,會得到一個adj整數數值。
3. Android 目前系統保活策略
A: Service的onStartCommand函式返回START_STICKY
START_STICKY是官方提供的引數,意思是當service被記憶體回收了,系統會對service進行重啟。面對360等記憶體回收,並沒什麼作用。
B:在service 的onDestory裡面重啟服務
onDestroy()方法只有在service正常停止的時候才會被呼叫,面對上述回收的第二與第三種方法沒有效果。
C:守護執行緒相互監聽
AB兩個程序,A程序裡面輪詢檢查B程序是否存活,沒存活的話將其拉起,同樣B程序裡面輪詢檢查A程序是否存活,沒存活的話也將其拉起,而我們的後臺邏輯則隨便放在某個程序裡執行即可。
這種方法面對回收的時候,其實作用也不大,而且很消耗效能。另外,也有人提到過使用兩個native程序監控,那種方法沒試過。
D: AlarmManager or JobScheduler迴圈觸發
public class AlarmService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
initAlarm(this);
Log.d("czh", "AlarmService onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
private void initAlarm(Context context) {
Intent intent = new Intent(context, AlarmReceiver.class);
intent.setAction("repeating");
PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, 0);
//開始時間
long firsTime = SystemClock.elapsedRealtime();
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, firsTime, 5 * 1000, sender);
}
}
PendingIntent.getBroadcase的註冊廣播
E:與系統service繫結
論Android應用程序長存的可行性 一文中,提到用NotificationListenerService代替普通service,從而達到保活的作用。 原理是沒有問題,在小米4上親測過後,發現並沒什麼用。
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class SimulateNotificationService extends NotificationListenerService {
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
super.onNotificationPosted(sbn);
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
super.onNotificationRemoved(sbn);
}
}
<service android:name=".service.SimulateNotificationService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
android:process=":test5">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
F:監聽系統Receiver保活
使用Receiver來檢測目標程序是否存活不失為一個好方法,靜態註冊一系列廣播,什麼開機啟動、網路狀態變化、時區地區變化、充電狀態變化等等等等,這聽起來好像很6,而且在大部分手機中都是可行的方案,但是對於深度定製的ROM,是的,又是深度定製,你沒有看錯,而且代表性人物還是魅族、小米,這兩個業界出了名的喜歡“深度定製”系統。
自從Android 3.1開始系統對我們的應用增加了一種叫做STOPPED的狀態,什麼叫STOPPED?就是安裝了之後從未啟動過的,大家可能經常在網上看到對開機廣播的解釋,說要想應用正確接收到開機廣播那麼就得先啟動一下應用,這個說法的技術支援就來源於此,因為自Android 3.1後所有的系統廣播都會在Intent新增一個叫做FLAG_EXCLUDE_STOPPED_PACKAGES的標識,說白了就是所有處於STOPPED狀態的應用都不可以接收到系統廣播。
H:提高程序優先順序, Notification提權
這種保活手段是應用範圍最廣泛。它是利用系統的漏洞來啟動一個前臺的Service程序,與普通的啟動方式區別在於,它不會在系統通知欄處出現一個Notification,看起來就如同執行著一個後臺Service程序一樣。這樣做帶來的好處就是,使用者無法察覺到你執行著一個前臺程序(因為看不到Notification),但你的程序優先順序又是高於普通後臺程序的。
這種方法面對第二種回收方式有效,但是面對小米之類的後臺回收,還是無能為力。
public class NotificationService extends Service {
private final static int SERVICE_ID = 1001;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (Build.VERSION.SDK_INT < 18) {
startForeground(SERVICE_ID, new Notification());
} else {
Intent innerIntent = new Intent(this, InnerService.class);
startService(innerIntent);
startForeground(SERVICE_ID, new Notification());
}
return super.onStartCommand(intent, flags, startId);
}
/**
* 給 API >= 18
*/
public static class InnerService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(SERVICE_ID, new Notification());
stopForeground(true);
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
}
I:不同的app程序,用廣播相互喚醒
如果你手機安裝了各種app,或者應用了各種第三方代sdk,即可互相喚醒。
假如你手機裡裝了支付寶、淘寶、天貓、UC等阿里系的app,那麼你開啟任意一個阿里系的app後,有可能就順便把其他阿里系的app給喚醒了。
這個方法針對記憶體回收的三種方式均有效,只要有一個活著,其他的就會活下來。
這篇文章是東拼西湊出來的,目的是為了加深印象,方面自己之後的學習。
分別引用:
安卓筆記俠