1.概述
據前人驗證,在沒有白名單的情況下,安卓系統要做一個任何情況下都不被殺死的應用是基本不可能的,但是我們可以做到應用基本不被殺死,如果殺死可以立即復活.經過上網查詢,進程常駐的方案眾說紛紜,但是很多的方案都是不靠譜的或不是最好的,結合很多資料,今天總結一下Android進程保活的一些可行方法.
2.問題
系統為什么會殺掉進程,殺的為什么是我們的進程,這是根據什么規則來決定的,是一次性干掉多個進程,還是一個接着一個殺掉?保活套路一堆,如何進行進程保活才是比較恰當......
3.分析
3.1進程的划分
Android中的進程也是有着嚴格的等級,分了三流九等,Android系統把進程划為了如下幾種(重要性從高到低):
具體地,活動進程指的就是用戶正在操作的程序,是前台進程,可以看到且能夠操作;可見進程就是看得見摸不着的,不能直接操作的進程;服務進程是沒有界面的一直在后台工作的進程,優先級不高,當系統內存不足時會被殺死,再次充裕的時候會再次開啟;后台進程就是用戶按了"back"或者"home"后,程序本身看不到了,但是其實還在運行的程序,比如Activity調用了onPause方法系統可能隨時終止它們,回收內存.空進程:某個進程不包含任何活躍的組件時該進程就會被置為空進程,完全沒用,殺了它只有好處沒壞處,第一個被處理!
3.2內存閾值
進程是怎么被殺的呢?系統出於體驗和性能上的考慮,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}\sources\android-23\com\android\server\am\ProcessList.java。oom_adj的值越小,進程的優先級越高,普通進程oom_adj值是大於等於0的,而系統進程oom_adj的值是小於0的,我們可以通過cat /proc/進程id/oom_adj可以看到當前進程的adj值。
也就是說,oom_adj越大,占用物理內存越多會被最先kill掉,OK,那么現在對於進程如何保活這個問題就轉化成,如何降低oom_adj的值,以及如何使得我們應用占的內存最少。
4.進程保活
4.1開啟一個像素的Activity
據說這個是手Q的進程保活方案,基本思想,系統一般是不會殺死前台進程的。所以要使得進程常駐,我們只需要在鎖屏的時候在本進程開啟一個Activity,為了欺騙用戶,讓這個Activity的大小是1像素,並且透明無切換動畫,在開屏幕的時候,把這個Activity關閉掉,所以這個就需要監聽系統鎖屏廣播.
public class SinglePixelActivity extends Activity { public static final String TAG = SinglePixelActivity.class.getSimpleName(); public static void actionToSinglePixelActivity(Context pContext) { Intent intent = new Intent(pContext, SinglePixelActivity.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_singlepixel); 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(); } }
在屏幕關閉的時候把SinglePixelActivity啟動起來,在開屏的時候把SinglePixelActivity 關閉掉,所以要監聽系統鎖屏廣播,以接口的形式通知MainActivity啟動或者關閉SinglePixActivity。
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() { SinglePixelActivity.actionToSinglePixelActivity(mContext); } public void finishActivity() { //結束掉SinglePixelActivity 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的值

果然將進程的優先級提高了。
據說這個微信也用過的進程保活方案,該方案實際利用了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可能不一樣),確實進程的優先級有所提高。

顧名思義,就是指的不同進程,不同app之間互相喚醒,如你手機里裝了支付寶、淘寶、天貓、UC等阿里系的app,那么你打開任意一個阿里系的app后,有可能就順便把其他阿里系的app給喚醒了。
4.4JobSheduler
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>
所以你的應用要是有消息推送的話,那么可以用這種方式去欺騙用戶。
5.總結
多種保活方式,沒有說哪一種最好,只有是在什么場景下,使用哪一種最合適;當然,這些方式不是我發明或發現的,但是我覺得如果不知道的好好了解一下,對自己會有很大的幫助.掌握一些進程保活的手段,這不是耍流氓,是很多場景如果要想為用戶服務,就必須有一個進程常駐,以便在特定的時候做特定的事情。誠然,但凡進程常駐內存,無論怎樣優化,都會或多或少的增加一些額外的性能開支,在為用戶最負責任的服務,最高品質的體現我們的價值的前提下,我們要盡可能減少內存和電量的消耗。