Android進程保活方案

本身曾經也在這個問題上傷過腦經,前幾日恰好有一個北京的哥們在QQ說在作IM類的項目,問我進程保活如何處理比較恰當,決定去總結一下,網上搜索一下進程常駐的方案好多好多,可是不少的方案都是不靠譜的或者不是最好的,結合不少資料,今天總結一下Android進程保活的一些方案,都附有完整的實現源碼,有些可能你已經知道,可是有些你多是第一次據說,css

(1像素Activity,前臺服務,帳號同步,Jobscheduler,相互喚醒,系統服務捆綁,若是你都瞭解了,請忽略)
通過多方面的驗證,Android系統中在沒有白名單的狀況下作一個任何狀況下都不被殺死的應用是基本不可能的,可是咱們能夠作到咱們的應用基本不被殺死,若是殺死能夠立刻滿血復活,原諒我講的特別含蓄,畢竟如今的技術防不勝防啊,不死應用仍是可能的。

有幾個問題須要思考,系統爲何會殺掉進程,殺的爲何是個人進程,這是按照什麼標準來選擇的,是一次性幹掉多個進程,仍是一個接着一個殺,保活套路一堆,如何進行進程保活纔是比較恰當......若是這些問題你還還存在,或許這篇文章能夠解答。java

1、進程初步瞭解

每個Android應用啓動後至少對應一個進程,有的是多個進程,並且主流應用中多個進程的應用比例較大linux


一、如何查看進程解基本信息

對於任何一個進程,咱們均可以經過adb shell ps|grep <package_name>的方式來查看它的基本信息android

Picture
解釋
u0_a16 USER 進程當前用戶
3881 進程ID
1223 進程的父進程ID
873024 進程的虛擬內存大小
37108 實際駐留」在內存中」的內存大小
com.wangjing.processlive 進程名

二、進程劃分

Android中的進程跟封建社會同樣,分了三流九等,Android系統把進程的劃爲了以下幾種(重要性從高到低),網上多位大神都詳細總結過(備註:嚴格來講是劃分了6種)。shell

2.一、前臺進程(Foreground process)

場景:緩存

  • 某個進程持有一個正在與用戶交互的Activity而且該Activity正處於resume的狀態。微信

  • 某個進程持有一個Service,而且該Service與用戶正在交互的Activity綁定。網絡

  • 某個進程持有一個Service,而且該Service調用startForeground()方法使之位於前臺運行。app

  • 某個進程持有一個Service,而且該Service正在執行它的某個生命週期回調方法,好比onCreate()、 onStart()或onDestroy()。ide

  • 某個進程持有一個BroadcastReceiver,而且該BroadcastReceiver正在執行其onReceive()方法。

用戶正在使用的程序,通常系統是不會殺死前臺進程的,除非用戶強制中止應用或者系統內存不足等極端狀況會殺死。

2.二、可見進程(Visible process)

場景:

  • 擁有不在前臺、但仍對用戶可見的 Activity(已調用 onPause())。

  • 擁有綁定到可見(或前臺)Activity 的 Service

用戶正在使用,看獲得,可是摸不着,沒有覆蓋到整個屏幕,只有屏幕的一部分可見進程不包含任何前臺組件,通常系統也是不會殺死可見進程的,除非要在資源吃緊的狀況下,要保持某個或多個前臺進程存活

2.三、服務進程(Service process)

場景

  • 某個進程中運行着一個Service且該Service是經過startService()啓動的,與用戶看見的界面沒有直接關聯。

在內存不足以維持全部前臺進程和可見進程同時運行的狀況下,服務進程會被殺死

2.四、後臺進程(Background process)

場景:

  • 在用戶按了"back"或者"home"後,程序自己看不到了,可是其實還在運行的程序,好比Activity調用了onPause方法

系統可能隨時終止它們,回收內存

2.五、空進程(Empty process)

場景:

  • 某個進程不包含任何活躍的組件時該進程就會被置爲空進程,徹底沒用,殺了它只有好處沒壞處,第一個幹它!

三、內存閾值

上面是進程的分類,進程是怎麼被殺的呢?系統出於體驗和性能上的考慮,app在退到後臺時系統並不會真正的kill掉這個進程,而是將其緩存起來。打開的應用越多,後臺緩存的進程也越多。在系統內存不足的狀況下,系統開始依據自身的一套進程回收機制來判斷要kill掉哪些進程,以騰出內存來供給須要的app, 這套殺進程回收內存的機制就叫 Low Memory Killer。那這個不足怎麼來規定呢,那就是內存閾值,咱們可使用cat /sys/module/lowmemorykiller/parameters/minfree來查看某個手機的內存閾值。


注意這些數字的單位是page. 1 page = 4 kb.上面的六個數字對應的就是(MB): 72,90,108,126,144,180,這些數字也就是對應的內存閥值,內存閾值在不一樣的手機上不同,一旦低於該值,Android便開始按順序關閉進程. 所以Android開始結束優先級最低的空進程,即當可用內存小於180MB(46080*4/1024)。

讀到這裏,你或許有一個疑問,假設如今內存不足,空進程都被殺光了,如今要殺後臺進程,可是手機中後臺進程不少,難道要一次性所有都清理掉?固然不是的,進程是有它的優先級的,這個優先級經過進程的adj值來反映,它是linux內核分配給每一個系統進程的一個值,表明進程的優先級,進程回收機制就是根據這個優先級來決定是否進行回收,adj值定義在com.android.server.am.ProcessList類中,這個類路徑是${android-sdk-path}sourcesandroid-23comandroidserveramProcessList.java。oom_adj的值越小,進程的優先級越高,普通進程oom_adj值是大於等於0的,而系統進程oom_adj的值是小於0的,咱們能夠經過cat /proc/進程id/oom_adj能夠看到當前進程的adj值。


看到adj值是0,0就表明這個進程是屬於前臺進程,咱們按下Back鍵,將應用至於後臺,再次查看


adj值變成了8,8表明這個進程是屬於不活躍的進程,你能夠嘗試其餘狀況下,oom_adj值是多少,可是每一個手機的廠商可能不同,oom_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進程

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

根據上面的adj值,其實系統在進程回收跟內存回收相似也是有一套嚴格的策略,能夠本身去了解,大概是這個樣子的,oom_adj越大,佔用物理內存越多會被最早kill掉,OK,那麼如今對於進程如何保活這個問題就轉化成,如何下降oom_adj的值,以及如何使得咱們應用佔的內存最少。

1、進程保活方案

一、開啓一個像素的Activity

聽說這個是手Q的進程保活方案,基本思想,系統通常是不會殺死前臺進程的。因此要使得進程常駐,咱們只須要在鎖屏的時候在本進程開啓一個Activity,爲了欺騙用戶,讓這個Activity的大小是1像素,而且透明無切換動畫,在開屏幕的時候,把這個Activity關閉掉,因此這個就須要監聽系統鎖屏廣播,我試過了,的確好使,以下。

public class MainActivity extends AppCompatActivity {

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

複製代碼

若是直接啓動一個Activity,當咱們按下back鍵返回桌面的時候,oom_adj的值是8,上面已經提到過,這個進程在資源不夠的狀況下是容易被回收的。如今造一個一個像素的Activity。

public class LiveActivity extends Activity {

    public static final String TAG = LiveActivity.class.getSimpleName();

    public static void actionToLiveActivity(Context pContext) {
        Intent intent = new Intent(pContext, LiveActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        pContext.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
        setContentView(R.layout.activity_live);

        Window window = getWindow();
        //放在左上角
        window.setGravity(Gravity.START | Gravity.TOP);
        WindowManager.LayoutParams attributes = window.getAttributes();
        //寬高設計爲1個像素
        attributes.width = 1;
        attributes.height = 1;
        //起始座標
        attributes.x = 0;
        attributes.y = 0;
        window.setAttributes(attributes);

        ScreenManager.getInstance(this).setActivity(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy");
    }
}

複製代碼

爲了作的更隱藏,最好設置一下這個Activity的主題,固然也無所謂了

<style name="LiveStyle"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowAnimationStyle">@null</item> <item name="android:windowNoTitle">true</item> </style>

複製代碼

在屏幕關閉的時候把LiveActivity啓動起來,在開屏的時候把LiveActivity 關閉掉,因此要監聽系統鎖屏廣播,以接口的形式通知MainActivity啓動或者關閉LiveActivity。

public class ScreenBroadcastListener {

    private Context mContext;

    private ScreenBroadcastReceiver mScreenReceiver;

    private ScreenStateListener mListener;

    public ScreenBroadcastListener(Context context) {
        mContext = context.getApplicationContext();
        mScreenReceiver = new ScreenBroadcastReceiver();
    }

    interface ScreenStateListener {

        void onScreenOn();

        void onScreenOff();
    }

    /** * screen狀態廣播接收者 */
    private class ScreenBroadcastReceiver extends BroadcastReceiver {
        private String action = null;

        @Override
        public void onReceive(Context context, Intent intent) {
            action = intent.getAction();
            if (Intent.ACTION_SCREEN_ON.equals(action)) { // 開屏
                mListener.onScreenOn();
            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 鎖屏
                mListener.onScreenOff();
            }
        }
    }

    public void registerListener(ScreenStateListener listener) {
        mListener = listener;
        registerListener();
    }

    private void registerListener() {
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_ON);
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        mContext.registerReceiver(mScreenReceiver, filter);
    }
}

複製代碼
public class ScreenManager {

    private Context mContext;

    private WeakReference<Activity> mActivityWref;

    public static ScreenManager gDefualt;

    public static ScreenManager getInstance(Context pContext) {
        if (gDefualt == null) {
            gDefualt = new ScreenManager(pContext.getApplicationContext());
        }
        return gDefualt;
    }
    private ScreenManager(Context pContext) {
        this.mContext = pContext;
    }

    public void setActivity(Activity pActivity) {
        mActivityWref = new WeakReference<Activity>(pActivity);
    }

    public void startActivity() {
            LiveActivity.actionToLiveActivity(mContext);
    }

    public void finishActivity() {
        //結束掉LiveActivity
        if (mActivityWref != null) {
            Activity activity = mActivityWref.get();
            if (activity != null) {
                activity.finish();
            }
        }
    }
}

複製代碼

如今MainActivity改爲以下

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final ScreenManager screenManager = ScreenManager.getInstance(MainActivity.this);
        ScreenBroadcastListener listener = new ScreenBroadcastListener(this);
         listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() {
            @Override
            public void onScreenOn() {
                screenManager.finishActivity();
            }

            @Override
            public void onScreenOff() {
                screenManager.startActivity();
            }
        });
    }
}

複製代碼

按下back以後,進行鎖屏,如今測試一下oom_adj的值


果真將進程的優先級提升了。

可是還有一個問題,內存也是一個考慮的因素,內存越多會被最早kill掉,因此把上面的業務邏輯放到Service中,而Service是在另一個 進程中,在MainActivity開啓這個服務就好了,這樣這個進程就更加的輕量,

public class LiveService extends Service {

    public static void toLiveService(Context pContext){
        Intent intent=new Intent(pContext,LiveService.class);
        pContext.startService(intent);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //屏幕關閉的時候啓動一個1像素的Activity,開屏的時候關閉Activity
        final ScreenManager screenManager = ScreenManager.getInstance(LiveService.this);
        ScreenBroadcastListener listener = new ScreenBroadcastListener(this);
        listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() {
            @Override
            public void onScreenOn() {
                screenManager.finishActivity();
            }
            @Override
            public void onScreenOff() {
                screenManager.startActivity();
            }
        });
        return START_REDELIVER_INTENT;
    }
}

複製代碼
<service android:name=".LiveService" android:process=":live_service"/>

複製代碼

OK,經過上面的操做,咱們的應用就始終和前臺進程是同樣的優先級了,爲了省電,系統檢測到鎖屏事件後一段時間內會殺死後臺進程,若是採起這種方案,就能夠避免了這個問題。可是仍是有被殺掉的可能,因此咱們還須要作雙進程守護,關於雙進程守護,比較適合的就是aidl的那種方式,可是這個不是徹底的靠譜,原理是A進程死的時候,B還在活着,B能夠將A進程拉起來,反之,B進程死的時候,A還活着,A能夠將B拉起來。因此雙進程守護的前提是,系統殺進程只能一個個的去殺,若是一次性殺兩個,這種方法也是不OK的。

事實上
那麼咱們先來看看Android5.0如下的源碼,ActivityManagerService是如何關閉在應用退出後清理內存的

Process.killProcessQuiet(pid);  

複製代碼

應用退出後,ActivityManagerService就把主進程給殺死了,可是,在Android5.0之後,ActivityManagerService倒是這樣處理的:

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

複製代碼

在應用退出後,ActivityManagerService不只把主進程給殺死,另外把主進程所屬的進程組一併殺死,這樣一來,因爲子進程和主進程在同一進程組,子進程在作的事情,也就中止了。因此在Android5.0之後的手機應用在進程被殺死後,要採用其餘方案。

二、前臺服務

,這方案實際利用了Android前臺service的漏洞。
原理以下
對於 API level < 18 :調用startForeground(ID, new Notification()),發送空的Notification ,圖標則不會顯示。
對於 API level >= 18:在須要提優先級的service A啓動一個InnerService,兩個服務同時startForeground,且綁定一樣的 ID。Stop 掉InnerService ,這樣通知欄圖標即被移除。

public class KeepLiveService extends Service {

    public static final int NOTIFICATION_ID=0x11;

    public KeepLiveService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        super.onCreate();
         //API 18如下,直接發送Notification並將其置爲前臺
        if (Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN_MR2) {
            startForeground(NOTIFICATION_ID, new Notification());
        } else {
            //API 18以上,發送Notification並將其置爲前臺後,啓動InnerService
            Notification.Builder builder = new Notification.Builder(this);
            builder.setSmallIcon(R.mipmap.ic_launcher);
            startForeground(NOTIFICATION_ID, builder.build());
            startService(new Intent(this, InnerService.class));
        }
    }

    public  static class InnerService extends Service{
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
        @Override
        public void onCreate() {
            super.onCreate();
            //發送與KeepLiveService中ID相同的Notification,而後將其取消並取消本身的前臺顯示
            Notification.Builder builder = new Notification.Builder(this);
            builder.setSmallIcon(R.mipmap.ic_launcher);
            startForeground(NOTIFICATION_ID, builder.build());
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    stopForeground(true);
                    NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
                    manager.cancel(NOTIFICATION_ID);
                    stopSelf();
                }
            },100);

        }
    }
}

複製代碼

在沒有采起前臺服務以前,啓動應用,oom_adj值是0,按下返回鍵以後,變成9(不一樣ROM可能不同)


在採起前臺服務以後,啓動應用,oom_adj值是0,按下返回鍵以後,變成2(不一樣ROM可能不同),確實進程的優先級有所提升。


三、相互喚醒

相互喚醒的意思就是,假如你手機裏裝了支付寶、淘寶、天貓、UC等阿里系的app,那麼你打開任意一個阿里系的app後,有可能就順便把其餘阿里系的app給喚醒了。這個徹底有可能的。此外,開機,網絡切換、拍照、拍視頻時候,利用系統產生的廣播也能喚醒app,不過Android N已經將這三種廣播取消了。



若是應用想保活,要是QQ,微信願意救你也行,有多少手機上沒有QQ,微信呢?或者像友盟,信鴿這種推送SDK,也存在喚醒app的功能。
拉活方法

四、JobSheduler

JobSheduler是做爲進程死後復活的一種手段,native進程方式最大缺點是費電, Native 進程費電的緣由是感知主進程是否存活有兩種實現方式,在 Native 進程中經過死循環或定時器,輪訓判斷主進程是否存活,當主進程不存活時進行拉活。其次5.0以上系統不支持。 可是JobSheduler能夠替代在Android5.0以上native進程方式,這種方式即便用戶強制關閉,也能被拉起來,親測可行。

JobSheduler@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
    @Override
    public void onCreate() {
        super.onCreate();
        startJobSheduler();
    }

    public void startJobSheduler() {
        try {
            JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), MyJobService.class.getName()));
            builder.setPeriodic(5);
            builder.setPersisted(true);
            JobScheduler jobScheduler = (JobScheduler) this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
            jobScheduler.schedule(builder.build());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters jobParameters) {
        return false;
    }
}

複製代碼

五、粘性服務&與系統服務捆綁

這個是系統自帶的,onStartCommand方法必須具備一個整形的返回值,這個整形的返回值用來告訴系統在服務啓動完畢後,若是被Kill,系統將如何操做,這種方案雖然能夠,可是在某些狀況or某些定製ROM上可能失效,我認爲能夠多作一種保保守方案

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    return START_REDELIVER_INTENT;
}

複製代碼
  • START_STICKY
    若是系統在onStartCommand返回後被銷燬,系統將會從新建立服務並依次調用onCreate和onStartCommand(注意:根據測試Android2.3.3如下版本只會調用onCreate根本不會調用onStartCommand,Android4.0能夠辦到),這種至關於服務又從新啓動恢復到以前的狀態了)。

  • START_NOT_STICKY
    若是系統在onStartCommand返回後被銷燬,若是返回該值,則在執行完onStartCommand方法後若是Service被殺掉系統將不會重啓該服務。

  • START_REDELIVER_INTENT
    START_STICKY的兼容版本,不一樣的是其不保證服務被殺後必定能重啓。

的研究,這裏說的系統服務很好理解,好比NotificationListenerService,NotificationListenerService就是一個監聽通知的服務,只要手機收到了通知,NotificationListenerService都能監聽到,即時用戶把進程殺死,也能重啓,因此說要是把這個服務放到咱們的進程之中,那麼就能夠呵呵了

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class LiveService extends NotificationListenerService {

    public LiveService() {

    }

    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
    }

    @Override
    public void onNotificationRemoved(StatusBarNotification sbn) {
    }
}

複製代碼

可是這種方式須要權限

<service android:name=".LiveService" android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
            <intent-filter>
                <action android:name="android.service.notification.NotificationListenerService" />
            </intent-filter>
        </service>

複製代碼

因此你的應用要是有消息推送的話,那麼能夠用這種方式去欺騙用戶。


+qq羣457848807:。獲取以上高清技術思惟圖,以及相關技術的免費視頻學習資料

相關文章
相關標籤/搜索