自己曾經(jīng)也在這個問題上傷過腦經(jīng),前幾日剛好有一個北京的哥們在QQ說在做IM類的項目,問我進程?;钊绾翁幚肀容^恰當(dāng),決定去總結(jié)一下,網(wǎng)上搜索一下進程常駐的方案好多好多,但是很多的方案都是不靠譜的或者不是最好的,結(jié)合很多資料,今天總結(jié)一下Android進程保活的一些方案,都附有完整的實現(xiàn)源碼,有些可能你已經(jīng)知道,但是有些你可能是第一次聽說,
(1像素Activity,前臺服務(wù),賬號同步,Jobscheduler,相互喚醒,系統(tǒng)服務(wù)捆綁,如果你都了解了,請忽略)經(jīng)過多方面的驗證,Android系統(tǒng)中在沒有白名單的情況下做一個任何情況下都不被殺死的應(yīng)用是基本不可能的,但是我們可以做到我們的應(yīng)用基本不被殺死,如果殺死可以馬上滿血復(fù)活,原諒我講的特別含蓄,畢竟現(xiàn)在的技術(shù)防不勝防啊,不死應(yīng)用還是可能的。
有幾個問題需要思考,系統(tǒng)為什么會殺掉進程,殺的為什么是我的進程,這是按照什么標(biāo)準(zhǔn)來選擇的,是一次性干掉多個進程,還是一個接著一個殺,?;钐茁芬欢?,如何進行進程?;畈攀潜容^恰當(dāng)......如果這些問題你還還存在,或許這篇文章可以解答。
一、進程初步了解
每一個Android應(yīng)用啟動后至少對應(yīng)一個進程,有的是多個進程,而且主流應(yīng)用中多個進程的應(yīng)用比例較大

1、如何查看進程解基本信息
對于任何一個進程,我們都可以通過adb shell ps|grep <package_name>的方式來查看它的基本信息


[圖片上傳中...(image.png-afd0c1-1556955294471-0)]
2、進程劃分
Android中的進程跟封建社會一樣,分了三流九等,Android系統(tǒng)把進程的劃為了如下幾種(重要性從高到低),網(wǎng)上多位大神都詳細總結(jié)過(備注:嚴(yán)格來說是劃分了6種)。
2.1、前臺進程(Foreground process)
場景:
- 某個進程持有一個正在與用戶交互的Activity并且該Activity正處于resume的狀態(tài)。
- 某個進程持有一個Service,并且該Service與用戶正在交互的Activity綁定。
- 某個進程持有一個Service,并且該Service調(diào)用startForeground()方法使之位于前臺運行。
- 某個進程持有一個Service,并且該Service正在執(zhí)行它的某個生命周期回調(diào)方法,比如onCreate()、 onStart()或onDestroy()。
- 某個進程持有一個BroadcastReceiver,并且該BroadcastReceiver正在執(zhí)行其onReceive()方法。
用戶正在使用的程序,一般系統(tǒng)是不會殺死前臺進程的,除非用戶強制停止應(yīng)用或者系統(tǒng)內(nèi)存不足等極端情況會殺死。
2.2、可見進程(Visible process)
場景:
- 擁有不在前臺、但仍對用戶可見的 Activity(已調(diào)用 onPause())。
- 擁有綁定到可見(或前臺)Activity 的 Service
用戶正在使用,看得到,但是摸不著,沒有覆蓋到整個屏幕,只有屏幕的一部分可見進程不包含任何前臺組件,一般系統(tǒng)也是不會殺死可見進程的,除非要在資源吃緊的情況下,要保持某個或多個前臺進程存活
2.3、服務(wù)進程(Service process)
場景
- 某個進程中運行著一個Service且該Service是通過startService()啟動的,與用戶看見的界面沒有直接關(guān)聯(lián)。
在內(nèi)存不足以維持所有前臺進程和可見進程同時運行的情況下,服務(wù)進程會被殺死
2.4、后臺進程(Background process)
場景:
- 在用戶按了"back"或者"home"后,程序本身看不到了,但是其實還在運行的程序,比如Activity調(diào)用了onPause方法
系統(tǒng)可能隨時終止它們,回收內(nèi)存
2.5、空進程(Empty process)
場景:
- 某個進程不包含任何活躍的組件時該進程就會被置為空進程,完全沒用,殺了它只有好處沒壞處,第一個干它!
3、內(nèi)存閾值
上面是進程的分類,進程是怎么被殺的呢?系統(tǒng)出于體驗和性能上的考慮,app在退到后臺時系統(tǒng)并不會真正的kill掉這個進程,而是將其緩存起來。打開的應(yīng)用越多,后臺緩存的進程也越多。在系統(tǒng)內(nèi)存不足的情況下,系統(tǒng)開始依據(jù)自身的一套進程回收機制來判斷要kill掉哪些進程,以騰出內(nèi)存來供給需要的app, 這套殺進程回收內(nèi)存的機制就叫 Low Memory Killer。那這個不足怎么來規(guī)定呢,那就是內(nèi)存閾值,我們可以使用cat /sys/module/lowmemorykiller/parameters/minfree來查看某個手機的內(nèi)存閾值。

注意這些數(shù)字的單位是page. 1 page = 4 kb.上面的六個數(shù)字對應(yīng)的就是(MB): 72,90,108,126,144,180,這些數(shù)字也就是對應(yīng)的內(nèi)存閥值,內(nèi)存閾值在不同的手機上不一樣,一旦低于該值,Android便開始按順序關(guān)閉進程. 因此Android開始結(jié)束優(yōu)先級最低的空進程,即當(dāng)可用內(nèi)存小于180MB(46080*4/1024)。
讀到這里,你或許有一個疑問,假設(shè)現(xiàn)在內(nèi)存不足,空進程都被殺光了,現(xiàn)在要殺后臺進程,但是手機中后臺進程很多,難道要一次性全部都清理掉?當(dāng)然不是的,進程是有它的優(yōu)先級的,這個優(yōu)先級通過進程的adj值來反映,它是linux內(nèi)核分配給每個系統(tǒng)進程的一個值,代表進程的優(yōu)先級,進程回收機制就是根據(jù)這個優(yōu)先級來決定是否進行回收,adj值定義在com.android.server.am.ProcessList類中,這個類路徑是${android-sdk-path}sourcesandroid-23comandroidserveramProcessList.java。oom_adj的值越小,進程的優(yōu)先級越高,普通進程oom_adj值是大于等于0的,而系統(tǒng)進程oom_adj的值是小于0的,我們可以通過cat /proc/進程id/oom_adj可以看到當(dāng)前進程的adj值。

看到adj值是0,0就代表這個進程是屬于前臺進程,我們按下Back鍵,將應(yīng)用至于后臺,再次查看

adj值變成了8,8代表這個進程是屬于不活躍的進程,你可以嘗試其他情況下,oom_adj值是多少,但是每個手機的廠商可能不一樣,oom_adj值主要有這么幾個,可以參考一下。

備注:(上表的數(shù)字可能在不同系統(tǒng)會有一定的出入)
根據(jù)上面的adj值,其實系統(tǒng)在進程回收跟內(nèi)存回收類似也是有一套嚴(yán)格的策略,可以自己去了解,大概是這個樣子的,oom_adj越大,占用物理內(nèi)存越多會被最先kill掉,OK,那么現(xiàn)在對于進程如何?;钸@個問題就轉(zhuǎn)化成,如何降低oom_adj的值,以及如何使得我們應(yīng)用占的內(nèi)存最少。
二、進程保活方案
1、開啟一個像素的Activity
據(jù)說這個是手Q的進程?;罘桨?,基本思想,系統(tǒng)一般是不會殺死前臺進程的。所以要使得進程常駐,我們只需要在鎖屏的時候在本進程開啟一個Activity,為了欺騙用戶,讓這個Activity的大小是1像素,并且透明無切換動畫,在開屏幕的時候,把這個Activity關(guān)閉掉,所以這個就需要監(jiān)聽系統(tǒng)鎖屏廣播,我試過了,的確好使,如下。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
如果直接啟動一個Activity,當(dāng)我們按下back鍵返回桌面的時候,oom_adj的值是8,上面已經(jīng)提到過,這個進程在資源不夠的情況下是容易被回收的?,F(xiàn)在造一個一個像素的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();
//寬高設(shè)計為1個像素
attributes.width = 1;
attributes.height = 1;
//起始坐標(biāo)
attributes.x = 0;
attributes.y = 0;
window.setAttributes(attributes);
ScreenManager.getInstance(this).setActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
}
為了做的更隱藏,最好設(shè)置一下這個Activity的主題,當(dāng)然也無所謂了
<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>
在屏幕關(guān)閉的時候把LiveActivity啟動起來,在開屏的時候把LiveActivity 關(guān)閉掉,所以要監(jiān)聽系統(tǒng)鎖屏廣播,以接口的形式通知MainActivity啟動或者關(guān)閉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狀態(tài)廣播接收者
*/
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() {
//結(jié)束掉LiveActivity
if (mActivityWref != null) {
Activity activity = mActivityWref.get();
if (activity != null) {
activity.finish();
}
}
}
}
現(xiàn)在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之后,進行鎖屏,現(xiàn)在測試一下oom_adj的值

果然將進程的優(yōu)先級提高了。
但是還有一個問題,內(nèi)存也是一個考慮的因素,內(nèi)存越多會被最先kill掉,所以把上面的業(yè)務(wù)邏輯放到Service中,而Service是在另外一個 進程中,在MainActivity開啟這個服務(wù)就行了,這樣這個進程就更加的輕量,
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) {
//屏幕關(guān)閉的時候啟動一個1像素的Activity,開屏的時候關(guān)閉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,通過上面的操作,我們的應(yīng)用就始終和前臺進程是一樣的優(yōu)先級了,為了省電,系統(tǒng)檢測到鎖屏事件后一段時間內(nèi)會殺死后臺進程,如果采取這種方案,就可以避免了這個問題。但是還是有被殺掉的可能,所以我們還需要做雙進程守護,關(guān)于雙進程守護,比較適合的就是aidl的那種方式,但是這個不是完全的靠譜,原理是A進程死的時候,B還在活著,B可以將A進程拉起來,反之,B進程死的時候,A還活著,A可以將B拉起來。所以雙進程守護的前提是,系統(tǒng)殺進程只能一個個的去殺,如果一次性殺兩個,這種方法也是不OK的。
事實上
那么我們先來看看Android5.0以下的源碼,ActivityManagerService是如何關(guān)閉在應(yīng)用退出后清理內(nèi)存的
Process.killProcessQuiet(pid);
應(yīng)用退出后,ActivityManagerService就把主進程給殺死了,但是,在Android5.0以后,ActivityManagerService卻是這樣處理的:
Process.killProcessQuiet(app.pid);
Process.killProcessGroup(app.info.uid, app.pid);
在應(yīng)用退出后,ActivityManagerService不僅把主進程給殺死,另外把主進程所屬的進程組一并殺死,這樣一來,由于子進程和主進程在同一進程組,子進程在做的事情,也就停止了。所以在Android5.0以后的手機應(yīng)用在進程被殺死后,要采用其他方案。
2、前臺服務(wù)
,這方案實際利用了Android前臺service的漏洞。原理如下對于 API level < 18 :調(diào)用startForeground(ID, new Notification()),發(fā)送空的Notification ,圖標(biāo)則不會顯示。對于 API level >= 18:在需要提優(yōu)先級的service A啟動一個InnerService,兩個服務(wù)同時startForeground,且綁定同樣的 ID。Stop 掉InnerService ,這樣通知欄圖標(biāo)即被移除。
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以下,直接發(fā)送Notification并將其置為前臺
if (Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN_MR2) {
startForeground(NOTIFICATION_ID, new Notification());
} else {
//API 18以上,發(fā)送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();
//發(fā)送與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);
}
}
}
在沒有采取前臺服務(wù)之前,啟動應(yīng)用,oom_adj值是0,按下返回鍵之后,變成9(不同ROM可能不一樣)

在采取前臺服務(wù)之后,啟動應(yīng)用,oom_adj值是0,按下返回鍵之后,變成2(不同ROM可能不一樣),確實進程的優(yōu)先級有所提高。

3、相互喚醒
相互喚醒的意思就是,假如你手機里裝了支付寶、淘寶、天貓、UC等阿里系的app,那么你打開任意一個阿里系的app后,有可能就順便把其他阿里系的app給喚醒了。這個完全有可能的。此外,開機,網(wǎng)絡(luò)切換、拍照、拍視頻時候,利用系統(tǒng)產(chǎn)生的廣播也能喚醒app,不過Android N已經(jīng)將這三種廣播取消了。


如果應(yīng)用想?;?,要是QQ,微信愿意救你也行,有多少手機上沒有QQ,微信呢?或者像友盟,信鴿這種推送SDK,也存在喚醒app的功能。
拉活方法
4、JobSheduler
JobSheduler是作為進程死后復(fù)活的一種手段,native進程方式最大缺點是費電, Native 進程費電的原因是感知主進程是否存活有兩種實現(xiàn)方式,在 Native 進程中通過死循環(huán)或定時器,輪訓(xùn)判斷主進程是否存活,當(dāng)主進程不存活時進行拉活。其次5.0以上系統(tǒng)不支持。 但是JobSheduler可以替代在Android5.0以上native進程方式,這種方式即使用戶強制關(guān)閉,也能被拉起來,親測可行。
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;
}
}
5、粘性服務(wù)&與系統(tǒng)服務(wù)捆綁
這個是系統(tǒng)自帶的,onStartCommand方法必須具有一個整形的返回值,這個整形的返回值用來告訴系統(tǒng)在服務(wù)啟動完畢后,如果被Kill,系統(tǒng)將如何操作,這種方案雖然可以,但是在某些情況or某些定制ROM上可能失效,我認為可以多做一種保保守方案。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_REDELIVER_INTENT;
}
- START_STICKY
如果系統(tǒng)在onStartCommand返回后被銷毀,系統(tǒng)將會重新創(chuàng)建服務(wù)并依次調(diào)用onCreate和onStartCommand(注意:根據(jù)測試Android2.3.3以下版本只會調(diào)用onCreate根本不會調(diào)用onStartCommand,Android4.0可以辦到),這種相當(dāng)于服務(wù)又重新啟動恢復(fù)到之前的狀態(tài)了)。 - START_NOT_STICKY
如果系統(tǒng)在onStartCommand返回后被銷毀,如果返回該值,則在執(zhí)行完onStartCommand方法后如果Service被殺掉系統(tǒng)將不會重啟該服務(wù)。 - START_REDELIVER_INTENT
START_STICKY的兼容版本,不同的是其不保證服務(wù)被殺后一定能重啟。
這里說的系統(tǒng)服務(wù)很好理解,比如NotificationListenerService,NotificationListenerService就是一個監(jiān)聽通知的服務(wù),只要手機收到了通知,NotificationListenerService都能監(jiān)聽到,即時用戶把進程殺死,也能重啟,所以說要是把這個服務(wù)放到我們的進程之中,那么就可以呵呵了
@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) {
}
}
但是這種方式需要權(quán)限
<service
android:name=".LiveService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
所以你的應(yīng)用要是有消息推送的話,那么可以用這種方式去欺騙用戶。
喜歡請點擊+關(guān)注哦
