【Android實例】1像素進程保活(二)

1 前言

前面一篇【Android實例】1像素進程保活(一)是最基礎的寫法,但是這種寫法還有些不足,還可以進行優化。因爲內存也是一個考慮的因素,內存越多的進程會最先被kill掉,所以我們可以開啓一個服務,然後將該服務放在另一個進程中,這樣這個進程就更加的輕量,更不容易被殺死。

系統出於體驗和性能上的考慮,app在退到後臺時系統並不會真正的kill掉這個進程,而是將其緩存起來。打開的應用越多,後臺緩存的進程也越多。在系統內存不足的情況下,系統開始依據自身的一套進程回收機制來判斷要kill掉哪些進程,以騰出內存來供給需要的app, 這套殺進程回收內存的機制就叫 Low Memory Killer。

2 查看手機內存閾值

  1. 首先你的手機需要得到root權限
  2. 在獲得root權限的前提下進行以下步驟:
  • 在手機開啓USB調試並連接AS的情況下,在Terminal裏輸入:adb shell
  • 然後輸入(獲取超級權限):su
  • 接着輸入(授權):chmod 777 /sys/module/lowmemorykiller/parameters/minfree
  • 最後輸入(查看):cat /sys/module/lowmemorykiller/parameters/minfree

一般,你可以得到的數值爲:18432,23040,27648,32256,36864,46080。這6個數值分別代表android系統回收6種進程的閾值,這麼看不方便查看,轉換爲M會更直觀,這6個數值的單位爲page,\color{red}{1page = 4K},所以通過\color{red}{數值*4/1024} 就能轉換爲M:72M,90M,108M,126M,144M,180M,即\color{red}{前臺進程(foreground)}\color{red}{可見進程(visible)}\color{red}{次要服務(secondary server)}\color{red}{後臺進程(hidden)}\color{red}{內容供應節點(content provider)}\color{red}{空進程(empty)}這6類進程進行回收的內存閾值分別爲72M,90M,108M,126M,144M,180M。這些數值指的是可用內存。

當內存到180M的時候會將空進程進行回收,當內存到144M的時候把空進程回收完以後開始對內容供應節點進行回收,並不是所有的內容供應節點都回收,而是通過判斷它的優先級進行回收,優先級是用oom_adj的值來表示,值越大回收的機率越高。可以通過:

ps|grep 包名

來得到進程的id。通過進程id可以查看oom_adj的值:

cat /proc/進程id/oom_adj

adj值爲0,代表前臺進程;我們按下Back鍵,將應用至於後臺,再次查看adj值爲9;如果按home鍵adj值爲8,不同的手機可能值不太一樣。adj級別如下表所示:

adj級別 解釋
UNKNOWN_ADJ 16 預留的最低級別,一般對於緩存的進程纔有可能設置成這個級別
CACHED_APP_MAX_ADJ 15 緩存進程,空進程,在內存不足的情況下就會優先被kill
CACHED_APP_MIN_ADJ 9 緩存進程,也就是空進程
SERVICE_B_ADJ 8 不活躍的進程
PREVIOUS_APP_ADJ 7 切換進程
HOME_APP_ADJ 6 與Home交互的進程
SERVICE_ADJ 5 有Service的進程
HEAVY_WEIGHT_APP_ADJ 4 高權重進程
BACKUP_APP_ADJ 3 正在備份的進程
PERCEPTIBLE_APP_ADJ 2 可感知的進程,比如那種播放音樂
VISIBLE_APP_ADJ 1 可見進程
FOREGROUND_APP_ADJ 0 前臺進程
PERSISTENT_SERVICE_ADJ -11 重要進程
PERSISTENT_PROC_ADJ -12 核心進程
SYSTEM_ADJ -16 系統進程
NATIVE_ADJ -17 系統起的Native進程

備註:(上表的數字可能在不同系統會有一定的出入)

3 Service的onStartCommand的4種返回值

  • START_STICKY:如果service進程被kill掉,保留service的狀態爲開始狀態,但不保留遞送的intent對象。隨後系統會嘗試重新創建service,由於服務狀態爲開始狀態,所以創建服務後一定會調用onStartCommand(Intent,int,int)方法。如果在此期間沒有任何啓動命令被傳遞到service,那麼參數Intent將爲null。

  • START_NOT_STICKY:“非粘性的”。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統不會自動重啓該服務。

  • START_REDELIVER_INTENT:重傳Intent。使用這個返回值時,如果在執行完onStartCommand後,服務被異常kill掉,系統會自動重啓該服務,並將Intent的值傳入。

  • START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保證服務被kill後一定能重啓。

4 源碼講解

代碼在【Android實例】1像素進程保活(一)基礎上改動,改動的地方有:

  • 新建一個OnePixelService服務
package com.yds.jianshu.onepixel;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;

public class OnePixelService extends Service {
    OnePixelManager manager;
    public OnePixelService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        manager = new OnePixelManager();
        manager.registerOnePixelReceiver(this);//註冊廣播接收者
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        manager.unregisterOnePixelReceiver(this);
    }
}

這裏onStartCommand返回爲START_STICKY,保證服務被殺死後可以被重新創建。初始化OnePixelManager管理類,然後註冊OnePixelReceiver服務,該服務主要是用來監聽屏幕變化,registerOnePixelReceiver源碼如下:

    /**
     * 一像素廣播接收者註冊方法。該方法中初始化OnePixelReceiver,並添加了過濾條件
     * 屏幕息屏和亮屏。然後註冊該廣播接收者
     * @param context
     */
    public void registerOnePixelReceiver(Context context){
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_SCREEN_ON);
        onePixelReceiver = new OnePixelReceiver();
        context.registerReceiver(onePixelReceiver,filter);
    }
  • AndroidManifest.xml中註冊服務
        <service
            android:name="com.yds.jianshu.onepixel.OnePixelService"
            android:enabled="true"
            android:exported="false"
            android:process=":one_pixel_service"
            ></service>
  • android:exported="false":表示該服務不能被其他應用程序組件調用或跟它交互,只有同一個應用程序的組件或帶有相同用戶ID的應用程序才能啓動或綁定該服務。
  • android:process=":one_pixel_service":開啓一個進程,並且服務處於該進程中。
  • MainActivity中調用
package com.yds.jianshu.mobile;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import com.example.myapplication.R;
import com.yds.jianshu.onepixel.OnePixelManager;
import com.yds.jianshu.onepixel.OnePixelService;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity{
    private static final String TAG = "[MainActivity]";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initData();
    }
    private void initData(){
        startOnePixelService();

    }

    private void startOnePixelService(){
        Intent intent = new Intent();
        intent.setClass(MainActivity.this, OnePixelService.class);
        startService(intent);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

Android5.0以下,ActivityManagerService殺死進程代碼如下:

Process.killProcessQuiet(pid);  

如上代碼所示,應用退出後,ActivityManagerService就把主進程給殺死了。在Android5.0以後,ActivityManagerService殺死進程代碼如下:

Process.killProcessQuiet(app.pid);  
Process.killProcessGroup(app.info.uid, app.pid);  

如上所示,在應用退出後,ActivityManagerService不僅把主進程給殺死,另外把主進程所屬的進程組一併殺死,這樣一來,由於子進程和主進程在同一進程組,子進程在做的事情,也就停止了。所以在Android5.0以後的手機應用在進程被殺死後,要採用其他方案。

5 總結

  • 爲什麼要用服務來進行1像素保活?
    因爲當進程在同一adj級別下,內存越多,就會最先被殺死,所以可以重開一個進程,將一像素的業務邏輯放在Service裏,這樣使得相比之前,Service所在進程更加輕量,更不容易被殺死。而且,在onStartCommand中使用START_STICKY,可以讓服務在被殺死後重新創建,提高保活率。

  • 1像素保活是爲了降低應用被殺死的概率,在Android5.0以上的手機上無法保證應用被殺死後能再次“復活”應用。

源碼:https://github.com/Yedongsheng/Jianshu/tree/develop

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章