如何實現程序保活的最新方案
簡介
現在只要是社交 APP 沒有哪個開發者不想讓自己的 APP 永久常駐的,想要永久常駐除非你們家的實力非常雄厚,APP 使用者量非常大,那麼廠商都會主動來找你,把你們家的 APP 加入白名單。否則永久常駐是不可能甚至都不給你許可權後臺執行。既然不能永久常駐,那麼我們有沒有一個辦法可以使我們的 APP 不那麼容易被系統殺死勒?或者說是殺死後能主動喚醒,顯然是可以的,下面我們進入主題吧。
怎麼使用
程式碼傳送陣
down 程式碼 https://github。com/yangkun19921001/KeepAlive。git ,將 live_library 放入自己工程
2。在 KeepAliveRuning onRuning 中實現需要保活的程式碼
public class KeepAliveRuning implements IKeepAliveRuning {
/**這裡實現 Socket / 推送 等一些保活元件*/
@Override
public void onRuning() {
//TODO——————————————————————
Log。e(“runing?KeepAliveRuning”, “true”);
}
@Override
public void onStop() {
Log。e(“runing?KeepAliveRuning”, “false”);
}
}
3。開啟保活
public void start() {
//啟動保活服務
KeepAliveManager。toKeepAlive(
getApplication()
, HIGH_POWER_CONSUMPTION,
“程序保活”,
“Process: System(哥們兒) 我不想被殺死”,
R。mipmap。ic_launcher,
new ForegroundNotification(
//定義前臺服務的通知點選事件
new ForegroundNotificationClickListener() {
@Override
public void foregroundNotificationClick(Context context, Intent intent) {
Log。d(“JOB——>”, “ foregroundNotificationClick”);
}
})
);
}
4。停止保活
KeepAliveManager。stopWork(getApplication());
最終效果
開啟保活
我們應該知道正常的話點選手機回收垃圾桶後臺的應用都會被 kill 掉,還有主動點選 AS Logcat 的程序停止執行的按鈕,我們也會發現程序會自動起來並且 pid 跟上一次不一樣了。要的就是這種效果,下面我們來了解下程序保活的知識吧。
未開啟保活
程序優先順序
官網詳細介紹
程序
如果記憶體不足,需要為其他使用者提供更緊急服務的程序又需要記憶體時,Android 可能會決定在某一時刻關閉某一程序。在被終止程序中執行的應用元件也會隨之銷燬。 當這些元件需要再次執行時,系統將為它們重啟程序。
決定終止哪個程序時,Android 系統將權衡它們對使用者的相對重要程度。例如,相對於託管可見 Activity 的程序而言,它更有可能關閉託管螢幕上不再可見的 Activity 的程序。 因此,是否終止某個程序的決定取決於該程序中所執行元件的狀態。 下面,我們介紹決定終止程序所用的規則。
程序生命週期
Android 系統將盡量長時間地保持應用程序,但為了新建程序或執行更重要的程序,最終需要移除舊程序來回收記憶體。 為了確定保留或終止哪些程序,系統會根據程序中正在執行的元件以及這些元件的狀態,將每個程序放入“重要性層次結構”中。 必要時,系統會首先消除重要性最低的程序,然後是重要性略遜的程序,依此類推,以回收系統資源。
重要性層次結構一共有 5 級。以下列表按照重要程度列出了各類程序(第一個程序
最重要
,將是
最後一個被終止
的程序):
名稱概括回收狀態
前臺程序正在互動只有在記憶體不足以支援它們同時繼續執行這一萬不得已的情況下,系統才會終止它們可見程序沒有任何前臺元件、但仍會影響使用者在螢幕上所見內容的程序可見程序被視為是極其重要的程序,除非為了維持所有前臺程序同時執行而必須終止,否則系統不會終止這些程序。服務程序正在執行已使用
startService()
方法啟動的服務且不屬於上述兩個更高類別程序的程序。除非記憶體不足以維持所有前臺程序和可見程序同時執行,否則系統會讓服務程序保持執行狀態。後臺程序對使用者不可見的 Activity 的程序系統可能隨時終止它們空程序不含任何活動應用元件的程序最容易為殺死
LMK(LowMemoryKiller)
為什麼引入 LMK ?
程序的啟動分冷啟動和熱啟動,當用戶退出某一個程序的時候,並不會真正的將程序退出,而是將這個程序放到後臺,以便下次啟動的時候可以馬上啟動起來,這個過程名為熱啟動,這也是Android 的設計理念之一。這個機制會帶來一個問題,每個程序都有自己獨立的記憶體地址空間,隨著應用開啟數量的增多, 系統已使用的記憶體越來越大,就很有可能導致系統記憶體不足。為了解決這個問題,系統引入 LowmemoryKiller (簡稱 lmk ) 管理所有程序,根據一定策略來 kill 某個程序並釋放佔用的記憶體,保證系統的正常執行。
LMK 基本原理
所有應用程序都是從 zygote 孵化出來的,記錄在 AMS 中mLruProcesses 列表中,由 AMS 進行統一管理,AMS 中會根據程序的狀態更新程序對應的 oom_adj 值,這個值會透過檔案傳遞到 kernel 中去,kernel 有個低記憶體回收機制,在記憶體達到一定閥值時會觸發清理 oom_adj 值高的程序騰出更多的記憶體空間
LMK 殺程序標準
minfree : 存放6個數值,單位記憶體頁面數 ( 一個頁面 4kb )
得到的數值為:18432 , 23040 , 27648 , 32256 , 36864 , 46080
這 6 個數值分別代表 android 系統回收 6 種程序的閾值,這麼看不方便檢視,轉換為 M 會更直觀,這 6 個數值的單位為 page 1 page = 4 KB ,所以透過數值 * 4 / 1024 就能轉換為M : 72 M , 90 M , 108 M , 126 M , 144 M , 180M
也就是說:
1。前臺程序(foreground),2。可見程序(visible),3。次要服務(secondary server),4。後臺程序(hidden),5。內容供應節點(content provider),6。空程序(empty)這6類程序進行回收的記憶體閾值分別為72M,90M,108M,126M,144M,180 M
當記憶體到 180 M的時候會將空程序進行回收,當記憶體到 144 M 的時候把空程序回收完以後開始對內容供應節點進行回收,並不是所有的內容供應節點都回收,而是透過判斷它的優先順序進行回收,優先順序是用 oom_adj 的值來表示,值越大回收的機率越高
adj 檢視:
cat /sys/module/lowmemorykiller/parameters/adj
檢視程序 adj 值:
adb shell ps
值越低越不易被回收,0 代表就不會被回收。
記憶體閾值在不同的手機上不一樣,一旦低於該值, Android 便開始按順序關閉程序。 因此 Android 開始結束優先順序最低的空程序,即當可用記憶體小於 180MB (46080)
程序保活方案
Activity 提權
這裡可見 oom_adj 為 0 是不會被回收的
後臺 oom_adj 為 6 記憶體不足會被回收
鎖屏 oom_adj 開啟一畫素 Activity 為 0 相當於可見程序,不易被回收
實現原理:
監控手機鎖屏解鎖事件,在螢幕鎖屏時啟動 1 個畫素透明的 Activity ,在使用者解鎖時將 Activity 銷燬掉,從而達到提高程序優先順序的作用。
程式碼實現
建立 onePxActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super。onCreate(savedInstanceState);
//設定一畫素的activity
Window window = getWindow();
window。setGravity(Gravity。START | Gravity。TOP);
WindowManager。LayoutParams params = window。getAttributes();
params。x = 0;
params。y = 0;
params。height = 1;
params。width = 1;
window。setAttributes(params);
//在一畫素activity裡註冊廣播接受者 接受到廣播結束掉一畫素
br = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
finish();
}
};
registerReceiver(br, new IntentFilter(“finish activity”));
checkScreenOn(“onCreate”);
}
2。建立鎖屏開屏廣播接收
@Override
public void onReceive(final Context context, Intent intent) {
if (intent。getAction()。equals(Intent。ACTION_SCREEN_OFF)) { //螢幕關閉的時候接受到廣播
appIsForeground = IsForeground(context);
try {
Intent it = new Intent(context, OnePixelActivity。class);
it。addFlags(Intent。FLAG_ACTIVITY_NEW_TASK);
it。addFlags(Intent。FLAG_ACTIVITY_SINGLE_TOP);
context。startActivity(it);
} catch (Exception e) {
e。printStackTrace();
}
//通知螢幕已關閉,開始播放無聲音樂
context。sendBroadcast(new Intent(“_ACTION_SCREEN_OFF”));
} else if (intent。getAction()。equals(Intent。ACTION_SCREEN_ON)) { //螢幕開啟的時候傳送廣播 結束一畫素
context。sendBroadcast(new Intent(“finish activity”));
if (!appIsForeground) {
appIsForeground = false;
try {
Intent home = new Intent(Intent。ACTION_MAIN);
home。setFlags(Intent。FLAG_ACTIVITY_NEW_TASK);
home。addCategory(Intent。CATEGORY_HOME);
context。getApplicationContext()。startActivity(home);
} catch (Exception e) {
e。printStackTrace();
}
}
//通知螢幕已點亮,停止播放無聲音樂
context。sendBroadcast(new Intent(“_ACTION_SCREEN_ON”));
}
}
Service 提權
建立一個前臺服務用於提高 app 在按下 home 鍵之後的程序優先順序
private void startService(Context context) {
try {
Log。i(TAG, “——-》啟動雙程序保活服務”);
//啟動本地服務
Intent localIntent = new Intent(context, LocalService。class);
//啟動守護程序
Intent guardIntent = new Intent(context, RemoteService。class);
if (Build。VERSION。SDK_INT >= 26) {
startForegroundService(localIntent);
startForegroundService(guardIntent);
} else {
startService(localIntent);
startService(guardIntent);
}
} catch (Exception e) {
Log。e(TAG, e。getMessage());
}
}
注意如果開啟 startForegroundService 前臺服務,那麼必須在 5 s內開啟一個前臺程序的服務通知欄,不會報 ANR
startForeground(KeepAliveConfig。FOREGROUD_NOTIFICATION_ID, notification);
廣播拉活(在 8。0 以下很受用)
在發生特定系統事件時,系統會發出廣播,透過在 AndroidManifest 中靜態註冊對應的廣播監聽器,即可在發生響應事件時拉活。但是從android 7。0 開始,對廣播進行了限制,而且在 8。0 更加嚴格。
以靜態廣播的形式註冊
全家桶 拉活
有多個 app 在使用者裝置上安裝,只要開啟其中一個就可以將其他的app 也拉活。比如手機裡裝了手 Q、QQ 空間、興趣部落等等,那麼開啟任意一個 app 後,其他的 app 也都會被喚醒。
Service 機制拉活
將 Service 設定為 START_STICKY,利用系統機制在 Service 掛掉後自動拉活
只要 targetSdkVersion 不小於5,就預設是 START_STICKY。 但是某些 ROM 系統不會拉活。並且經過測試,Service 第一次被異常殺死後很快被重啟,第二次會比第一次慢,第三次又會比前一次慢,一旦在短時間內 Service 被殺死 4-5 次,則系統不再拉起。
賬號同步拉活(只做瞭解,不靠譜)
手機系統設定裡會有 “帳戶” 一項功能,任何第三方 APP 都可以透過此功能將資料在一定時間內同步到伺服器中去。系統在將 APP 帳戶同步時,會將未啟動的 APP 程序拉活
JobScheduler 拉活(靠譜,8。0 官方推薦)
JobScheduler 允許在特定狀態與特定時間間隔週期執行任務。可以利用它的這個特點完成保活的功能,效果即開啟一個定時器,與普通定時器不同的是其排程由系統完成。
注意 setPeriodic 方法 在 7。0 以上如果設定小於 15 min 不起作用,可以使用setMinimumLatency 設定延時啟動,並且輪詢
public static void startJob(Context context) {
try {
mJobScheduler = (JobScheduler) context。getSystemService(
Context。JOB_SCHEDULER_SERVICE);
JobInfo。Builder builder = new JobInfo。Builder(10,
new ComponentName(context。getPackageName(),
JobHandlerService。class。getName()))。setPersisted(true);
/**
* I was having this problem and after review some blogs and the official documentation,
* I realised that JobScheduler is having difference behavior on Android N(24 and 25)。
* JobScheduler works with a minimum periodic of 15 mins。
*
*/
if (Build。VERSION。SDK_INT >= Build。VERSION_CODES。N) {
//7。0以上延遲1s執行
builder。setMinimumLatency(KeepAliveConfig。JOB_TIME);
} else {
//每隔1s執行一次job
builder。setPeriodic(KeepAliveConfig。JOB_TIME);
}
mJobScheduler。schedule(builder。build());
} catch (Exception e) {
Log。e(“startJob->”, e。getMessage());
}
}
推送拉活
根據終端不同,在小米手機(包括 MIUI)接入小米推送、華為手機接入華為推送。
Native 拉活
Native fork 子程序用於觀察當前 app 主程序的存亡狀態。對於 5。0以上成功率極低。
後臺迴圈播放一條無聲檔案
//如果選擇流氓模式,就預設接收了耗電的缺點,但是保活效果很好。
if (mediaPlayer == null && KeepAliveConfig。runMode ==
RunMode。HIGH_POWER_CONSUMPTION) {
mediaPlayer = MediaPlayer。create(this, R。raw。novioce);
mediaPlayer。setVolume(0f, 0f);
mediaPlayer。setOnCompletionListener(new MediaPlayer。OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mediaPlayer) {
Log。i(TAG, “迴圈播放音樂”);
play();
}
});
play();
}
雙程序守護 (靠譜)
兩個程序相互繫結 (bindService),如果有其中一個程序被殺,那麼另外一個程序就會將被殺的程序重新拉起
總結
程序保活就講到這裡了,最後我自己是結合裡面最靠譜的(Activity + Service 提權 + Service 機制拉活 + JobScheduler 定時檢測程序是否執行 + 後臺播放無聲檔案 + 雙程序守護),然後組成了一個程序保活終極方案。 文章中只是部分程式碼,感興趣的可以下載 demo 試下保活效果。