1. 程式人生 > >探討Android6.0及以上系統APP保活實現

探討Android6.0及以上系統APP保活實現



1簡介

APP保活系列(最高支援到Android 7.0):

  • (1) 探討一種新型的雙程序守護應用保活方法

    http://blog.csdn.net/andrexpert/article/details/53485360

  • (2) 探討Android6.0及以上系統APP常駐記憶體(保活)實現-爭寵篇(本文)

  • (3) 探討Android6.0及以上系統APP常駐記憶體(保活)實現-復活篇

    http://blog.csdn.net/andrexpert/article/details/75174586


APP常駐記憶體(保活),舊事重提,距離上一次的研究亦有半年有餘。最近,使用者反饋說多程序守護方案在華為Mate8(7.0)保活效果不是很好,有時候還是不能及時收到訊息,於是,又帶著懷疑的眼光,重新找回原來的程式碼進行測試,順便分析了市場上主流運動類APP保活方法(微信、手Q就算了,富人家的孩子,不具代表性),同時也對系統對記憶體中APP的管理規則進行了進一步探索。

本文便是對最近一週的探索、學習、測試的總結之一,以備將來不時之需。

2APP保活核心思想歸納

對於Android6.0及其以上系統APP保活,我覺得主要還是通過這兩個方面進行,即:降低omm_adj值,儘量保證程序不被系統殺死;程序被殺死後,通過其他方式將程序復活。

但需要明白的是,面對各手機廠商的深度定製和谷歌越來越嚴格的資源管理機制,這兩種方式結合的保活不是永久的,只能是相對存在,不同的機型結果也是不一樣的。

由於篇幅限制,本文主要剖析下通過何種方式降低oom_adj的值來降低APP被殺的機率,以及oom_adj值是怎樣做到的?

接下來,我們需要了解下Android系統回收記憶體中的程序所依據的規則:程序在記憶體中時活動主要有五種狀態,即前臺程序、可見程序、服務程序、後臺程序、空程序,這幾種狀態的程序優先順序由高到低,oom_adj值由低到高(在ProcessList定義)。

然後Android系統會根據當前系統資源和程序oom_adj值來回收相應的程序,前臺程序一般不會被回收,空程序最容易被回收,這種管理規則就是"傳說中"的Low Memory Killer。

為了更直觀的瞭解這套規則,我畫了個表:


注:優先順序1表示最高階,普通程序的oom_adj>=0,系統程序oom_adj<0,系統會根據相應的記憶體閥值對符合某段oom_adj值的程序進行回收。另外,oom_adj值也會隨著佔用實體記憶體越大而增大,系統程序絕對不會被系統殺死。

3市場主流運動類APP分析

1. 咕咚(v 7.17.0)

(1) 一鍵清理/滑動清理

a. 當"咕咚"處於停止狀態,其程序被殺死,通知欄圖示被清理,等待幾分鐘沒有 自動重啟,當重新進入“咕咚”時,會從歡迎介面重新進入;

b. 當"咕咚"處於運動進行狀態,程序死亡,通知欄圖示被清除,等待幾分鐘沒有自動重啟,但當重新進入“咕咚”時,其直接顯示運動介面,而沒有從歡迎介面進入,運動時間等狀態與被清理時一樣;

c. 當"咕咚"處於運動暫停狀態,其程序正常存活,通知欄圖示正常顯示。如果是單獨清理,程序死亡,通知欄圖示被清除;但當重新進入“咕咚”時,其直接顯示運動介面,而沒有從歡迎介面進入,運動時間等狀態與被清理時一樣;

(2) 黑屏/鎖屏

a. 當"咕咚"處於停止狀態,退到後臺,鎖屏進入黑屏狀態,等待5分鐘,程序死亡,通知欄被清除;

b. 當"咕咚"處於運動進行狀態,退到後臺,鎖屏進入黑屏狀態,然後再進入系統,“咕咚”跑步介面自動彈出。再次鎖屏,等待20分鐘,程序沒有被殺死,"咕咚"跑步介面自動彈出,運動狀態保持不變;

c. 當"咕咚"處於運動暫停狀態,退到後臺,鎖屏進入黑屏狀態,然後再進入系統,"咕咚"跑步介面自動彈出。再次鎖屏。等待20分鐘,程序沒有被殺死,"咕咚"跑步介面自動彈出,運動狀態保持不變;

 前提:

手機管家->鎖屏清理應用"關閉;
手機管家->自啟管理"關閉;
運動狀態,禁用返回鍵,使用者只能從Home鍵退到後臺;
運動介面文字閃爍或運動計時;
斷網;

分析:

當"咕咚"處於停止狀態時,一鍵清理和黑屏狀態會被殺死,說明在沒有進入運動介面之前,其保活機制沒有被啟動(即沒有使運動介面切換到後臺等)。

當“咕咚”處於運動狀態時,一鍵清理和黑屏狀態沒有被殺死(滑動清理除外),說明已經啟動保活機制:

①"咕咚"禁止了返回鍵,以保證運動Activity不被銷燬;

②不斷更新通知欄計時,以保證APP始終在前臺,防止被系統回收;

③"咕咚"被清理後能夠自動重啟,通知被刪除後自動彈出,說明可能有另外一個東西(程序或Service)監聽器運動Service(或程序)存活狀態,當Service被銷燬時,立馬將其拉起來;④“咕咚”被強制停止或清理殺死後,再次進入會直接顯示運動介面且能夠保持殺死之前的運動狀態,說明其可能利用配置檔案記錄了相關狀態;

⑤鎖屏/解鎖後,"咕咚"運動介面會自動彈出,說明其利用了廣播機制對鎖屏廣播進行監聽,彈出Activity以保證程序始終在前臺;

結論:常駐通知欄,雙程序守護,廣播鎖屏,自定義鎖屏。

備註:以上為華為Mate8(7.0)測試結果;其他如三星C9(6.0)保活較好,特別是當一鍵清理時,"咕咚會自動啟動,估計是使用了程序守護策略,而三星使用的是原生系統,因此結果你懂得;360F4(6.0)保活很差,以更厲害的方式幹掉流氓APP;

2. 樂動力(v7.3.2)


(1) 一鍵清理 / 滑動清理
     

 三星C9(6.0):無論何種狀態,"樂動力" 程序被殺死,等待幾分鐘,沒有自動啟動;
 360F4(6.0):無論何種狀態,"樂動力" 程序被殺死,等待幾分鐘,沒有自動啟動;
 華為Mate8(7.0):無論何種狀態,"樂動力" 程序被殺死,等待幾分鐘,沒有自動啟動;

(2) 鎖屏/黑屏

 a. 當"樂動力"處於停止狀態,退到後臺,鎖屏,等待5分鐘,程序死亡,通知欄被清除;
 b. 當"樂動力"處於運動暫停狀態,退到後臺,鎖屏再開啟,運動介面被切換到前臺,並強制彈出自定義鎖屏介面(覆蓋在系統鎖屏介面之上);再次鎖屏,等待20分鐘,應用程序存活;
 c. 當"樂動力"處於運動進行狀態,退到後臺,鎖屏再開啟,運動介面被切換到前臺,並強制彈出自定義鎖屏介面(覆蓋在系統鎖屏介面之上);再次鎖屏,等待20分鐘,應用程序存活;

前提:

手機管家->鎖屏清理應用"關閉;
手機管家->自啟管理"關閉;
運動狀態,禁用返回鍵,使用者只能從Home鍵退到後臺
斷網


分析:

當"樂動力"處於停止狀態時,黑屏狀態下,其在短時間內被系統殺死,說明保活機制沒有啟用;

但當處於運動暫停或進行狀態時,"樂動力"在一段時間內沒有被殺死,且當鎖屏時,"樂動力"會自動將運動介面切換到前臺,此外,還會強制彈出自定鎖屏介面,這就說明"樂動力"的保活機制很可能是利用監聽鎖屏廣播強制將相關介面切換到前臺,以提高"樂動力"在黑屏狀態下的存活率。

結論:

常駐通知欄,廣播鎖屏,自定義鎖屏


3. 悅動圈(v3.1.2.9)

(1) 一鍵清理 / 滑動清理

三星C9(6.0):效果與樂動力一致;
360F4(6.0):效果與樂動力一致;
華為Mate8(7.0):效果與樂動力一致;

(2) 鎖屏/黑屏

a. 當"悅動圈"處於停止狀態,退到後臺,鎖屏,等待3分鐘,程序死亡,通知欄被清除;
b. 當"悅動圈"處於運動暫停狀態時,自定義鎖屏、切換介面到前臺與咕咚、樂動力一樣,效果一致;
c. 當"悅動圈"處於運動進行狀態時,自定義鎖屏、切換介面到前臺與咕咚、樂動力一樣,效果一致;


結論:常駐通知欄,廣播鎖屏,自定義鎖屏

4APP保活方案探討

經過上面的討論分析,"咕咚"、"樂動力"等這類APP主要是通過監聽鎖屏、網路等系統廣播,將程序置於前臺以提高程序的級別,從而防止程序不那麼輕易被系統幹掉。

另外,"咕咚"可能還使用了相關的程序被清理復活策略。當然,對於復活策略,我們下一篇文章再探討,本文主要討論以上APP是通過哪些方式降低程序omm_adj值,防止其被系統殺死的。

為了達到與"咕咚"等APP類似效果,我們模擬這麼一種場景:當用戶登入測試APP後,先不開啟保活功能;當用戶開始跑步時,開啟保活功能,然後再在這基礎上做黑屏執行、一鍵清理、強制停止等功能測試。

也就是說,Android專案中SplashActivity、LoginActivity只是配合我們"演戲"的,真正啟動APP保活邏輯的是在SportsActivity,它將上演"後宮爭寵"戲碼。


好吧,小夥子,開始你的表演!


1. 開啟前臺Service,“逼君上位”


 將Service置為前臺,目的時提高程序Service的oom_adj值,以降低其被系統回收的機率。該方案的原理是,通過使用 startForeground()方法將當前Service置於前臺來提高Service的優先順序。

需要注意的是,對API大於18而言 startForeground()方法需要彈出一個可見通知,如果你覺得不爽,可以開啟另一個Service將通知欄移除,其oom_adj值還是沒變的。實現程式碼如下:

a) DaemonService.java

/**前臺Service,使用startForeground 
* 這個Service儘量要輕,不要佔用過多的系統資源,否則
* 系統在資源緊張時,照樣會將其殺死
*
* Created by jianddongguo on 2017/7/7.
* http://blog.csdn.net/andrexpert
*/
 
public class DaemonService extends Service {  
   private static final String TAG = "DaemonService";  
   public static final int NOTICE_ID = 100;  
   @Nullable  
   @Override  
   public IBinder onBind(Intent intent) {  
       return null;  
   }  
   @Override  
   public void onCreate() {  
       super.onCreate();  
       if(Contants.DEBUG)  
           Log.d(TAG,"DaemonService---->onCreate被呼叫,啟動前臺service");  
       //如果API大於18,需要彈出一個可見通知  
       if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2){  
           Notification.Builder builder = new Notification.Builder(this);  
           builder.setSmallIcon(R.mipmap.ic_launcher);  
           builder.setContentTitle("KeepAppAlive");  
           builder.setContentText("DaemonService is runing...");  
           startForeground(NOTICE_ID,builder.build());  
           // 如果覺得常駐通知欄體驗不好  
           // 可以通過啟動CancelNoticeService,將通知移除,oom_adj值不變  
           Intent intent = new Intent(this,CancelNoticeService.class);  
           startService(intent);  
       }else{  
           startForeground(NOTICE_ID,new Notification());  
       }  
   }  
   @Override  
   public int onStartCommand(Intent intent, int flags, int startId) {  
       // 如果Service被終止  
       // 當資源允許情況下,重啟service  
       return START_STICKY;  
   }  
   @Override  
   public void onDestroy() {  
       super.onDestroy();  
       // 如果Service被殺死,幹掉通知  
       if(Build.VERSION.SDK_INT
               >= Build.VERSION_CODES.JELLY_BEAN_MR2){  
           NotificationManager mManager =
               (NotificationManager)getSystemService(NOTIFICATION_SERVICE);  
           mManager.cancel(NOTICE_ID);  
       }  
       if(Contants.DEBUG)  
           Log.d(TAG,"DaemonService---->onDestroy,前臺service被殺死");  
       // 重啟自己  
       Intent intent = new Intent(getApplicationContext(),DaemonService.class);  
       startService(intent);  
   }  
}  

講解一下:

這裡還用到了兩個技巧:一是在onStartCommand方法中返回START_STICKY,其作用是當Service程序被kill後,系統會嘗試重新建立這個Service,且會保留Service的狀態為開始狀態,但不保留傳遞的Intent物件,onStartCommand方法一定會被重新呼叫。

其二在onDestory方法中重新啟動自己,也就是說,只要Service在被銷燬時走到了onDestory這裡我們就重新啟動它。

相關推薦

探討Android6.0以上系統APP實現

1簡介 APP保活系列(最高支援到Android 7.0): (1) 探討一種新型的雙程序守護應用保活方法 http://blog.csdn.net/andrexpert/article/details/53485360

02-ABB工業機器人6.0以上系統與之前系統的區別

1、示教器介面的區別:6.0及以上系統的示教器介面如下: 6.0以前系統的示教器介面如下: 2、通訊配置選型介面不同,IO設定流程有區別,省去了Unit: 6.0及以上系統的介面: 6.0以前系統的介面如下: 後邊再具體講IO的前後配置。 3、幾個高階

Android6.0以上版本一次請求多個許可權的處理方式

final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124; private void insertDummyContactWrapper() { List<Stri

Android 7.0以上系統訪問相機問題詳解

本章節講述Android 7.0及以上獲取相機問題 1.清單檔案 2.專案配置 3.程式碼 /** * Android 6.0系統及以上申請敏感許可權方法 * */ private void reque

Android6.0以上版本申請許可權講解

我們先來了解一個概念 在執行時請求許可權 從 Android 6.0(API 級別 23)開始,使用者開始在應用執行時向其授予許可權,而不是在應用安裝時授予。此方法可以簡化應用安裝過程,因為使用者在安裝或更新應用時不需要授予許可權。它還讓使用者可以對應用的功能進行更多控制;

Android6.0以上在SD卡上建立資料夾

首先在AndroidManifest.xml新增許可權 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 接著在佈局中新增一個按鈕

Android 6.0以上系統動態申請許可權詳解

1.Android 許可權簡介 自從Android6.0釋出以來,在許可權上做出了很大的變動,不再是之前的只要在manifest設定就可以任意獲取許可權,而是更加的注重使用者的隱私和體驗,不會再強迫使用者因拒絕不該擁有的許可權而導致的無法安裝的事情,也不會再不徵求使用者授權

android6.0以上獲取wifi mac地址的方法(親測可行)

由於android6.0對wifi mac地址獲取進行了限制,用原來的方法獲取會獲取到02:00:00:00:00:00這個固定地址。 但是可以通過讀取節點進行獲取"/sys/class/net/w

Android獲取Mac地址-相容6.0以上系統

在網上找了好久如何獲取Android mac地址,最後還是在大谷歌上找到的,經測試,4.0一直到6.0,7.0系統都可以獲取得到Mac地址 在AndroidManifest.xml中加入以下許可權: <uses-permission android:name="android.permission

visual studio 2017 中默認無法開發 Android 8.0 以上系統的解決方案

打開 andro sdk 並且 window 擴展 and 最新 最新版 一般默認比較舊有兩個原因,系統版本過舊,Visual Studio 版本過舊。 第一步,將windows 更新到最新版,必須是windows 10 並且更新到最新。 第二步,將visual studi

android系統版本6.0以上設定沉浸式狀態列

系統版本6.0及以上設定沉浸式狀態列程式碼。   requestWindowFeature(Window.FEATURE_NO_TITLE); //系統版本6.0及以上設定沉浸式狀態列 if (RomUtil.hasM()) { int flag = getWindow().get

Android 7.0以上使用OpenCL

由於從Android 7.0, API 24, 開始, 系統將阻止應用連結至非公開NDK庫, 所以, 使用libOpenCL.so時與面向低版本的Android平臺有所不同, 需要把依賴的非公開NDK庫打包到APK中 確定依賴的庫 首先應該確定你所使用的libOpenC.so所依賴的庫, 使用命令objd

關於安卓7.0以上的Https請求抓包問題android:networkSecurityConfig

Android 7.0(API 級別 24)及更高版本預設只承認系統CA,所以預設是安全的,不需要配置。如果想要系統承認使用者自己安裝的CA,則修改配置(比如除錯時會需要抓包)。 面向 Android 6.0(API 級別 23)及更低版本應用的預設配置如下所示,這段配置的意思是,承認系

Android 自用 App——音樂播放適配8.0 (賊好用)

又是好久沒有積累東西了。慚愧,慚愧。。。手動哭泣。閒話說到這裡,下面我介紹一種新的 App 保活方式哈,目前用小米家族手機 涵蓋 Android 5.0 到 Android 8.1家族的測試。結論是,不主動幹掉,是死不了的。但是主動幹掉了,是活不了的。 之前介紹介紹了 雙程序保活,我

android6.0動態獲取系統許可權

介紹:Google公司在android API23之後為了保護用的隱私和敏感資訊,一些許可權必須要使用者同意才能使用 1、不僅僅靜態註冊還需要動態獲取的許可權 如何獲取呢? 2、獲取的兩種方式 1)可參考時光與夢s的文章 使用文件地址:https://www.jianshu

Android 5.0以上WebView不能使用第三方Cookies解決方案

最近在做老專案SDK的升級,突然發現以前能用的功能,現在不能使用了。除錯的時候發現返回了錯誤401.需要認證。奇怪以前都是正常的。所以就去看看webView是否有過變動 解決方法 if (Build.VERSION.SDK_INT >= Build.VERSION_CODE

Android6.0的許可權系統

Android6.0棉花糖,app將不會在安裝的時候授予許可權。取而代之,app不得不在執行的時候一個一個詢問使用者授予許可權。 只有在把 targetSdkVersion 23  以上的版本才會出現。 PROTECTI

Unity在Android 6.0以上版本彈出許可權申請視窗的問題

Android 版本大於等於6.0(SDK版本大於等於23),許可權需要應用自主申請,動態申請許可權。 如果Unity做為Android的子模組,除了應用本身會有申請許可權的彈窗之外,Unity也會自動彈出許可權申請視窗。 網路許可權(普通許可權)、手機震動許

Android7.0以上 獲取uri真實路徑

背景:各大應用商店強制要求應用基於Android O(target 26)適配 無奈只能進行適配了。 嘗試過使用以下方法獲取真實路徑: String[] filePathColumn = {MediaStore.MediaColumns.DATA, MediaS

如何隱藏Android4 0以上版本的ActionBar NotificationBar StatusBar Syst

                1.ActionBar:<activityandroid:name="Demo"android:label="@string/app_name"android:theme="@android:style/Theme.NoTitleBar.Fullscreen">&l