性能優化 (十) APP 持續運行之進程保活實現

性能優化系列

APP 啓動優化html

UI 繪製優化java

內存優化android

圖片壓縮git

長圖優化github

電量優化shell

Dex 加解密性能優化

動態替換 Application服務器

APP 穩定性之熱修復原理探索app

APP 持續運行之進程保活實現ide

ProGuard 對代碼和資源壓縮

APK 極限壓縮

簡介

如今只要是社交 APP 沒有哪一個開發者不想讓本身的 APP 永久常駐的,想要永久常駐除非大家家的實力很是雄厚,APP 用戶量很是大,那麼廠商都會主動來找你,把大家家的 APP 加入白名單。不然永久常駐是不可能甚至都不給你權限後臺運行。既然不能永久常駐,那麼咱們有沒有一個辦法可使咱們的 APP 不那麼容易被系統殺死勒?或者說是殺死後能主動喚醒,顯然是能夠的,下面咱們進入主題吧。

怎麼使用

代碼傳送陣

  1. down 代碼 github.com/yangkun1992… ,將 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 72 M .前臺進程(foreground)
23040 90 M 可見進程(visible)
27648 108 M 次要服務(secondary server)
32256 126 M 後臺進程(hidden)
36864 144 M 內容供應節點(content provider)
46080 180 M 空進程(empty)

當內存到 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 銷燬掉,從而達到提升進程優先級的做用。

代碼實現

  1. 建立 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 更加嚴格。

以靜態廣播的形式註冊

<receiver android:name=".receive.NotificationClickReceiver">
<intent-filter>
<action android:name="CLICK_NOTIFICATION"></action>
</intent-filter>
</receiver>
複製代碼

全家桶 拉活

有多個 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 試下保活效果。

感謝

相關文章
相關標籤/搜索