一、進程初步瞭解
每一個Android應用啓動後至少對應一個進程,有的是多個進程,而且主流應用中多個進程的應用比例較大
1、如何查看進程解基本信息
對於任何一個進程,我們都可以通過adb shell ps|grep <package_name>的方式來查看它的基本信息
值 | 解釋 |
---|---|
u0_a16 | USER 進程當前用戶 |
3881 | 進程ID |
1223 | 進程的父進程ID |
873024 | 進程的虛擬內存大小 |
37108 | 實際駐留”在內存中”的內存大小 |
com.wangjing.processlive | 進程名 |
2、進程劃分
Android中的進程跟封建社會一樣,分了三流九等,Android系統把進程的劃爲瞭如下幾種(重要性從高到低),網上多位大神都詳細總結過(備註:嚴格來說是劃分了6種)。
2.1、前臺進程(Foreground process)
場景:
- 某個進程持有一個正在與用戶交互的Activity並且該Activity正處於resume的狀態。
- 某個進程持有一個Service,並且該Service與用戶正在交互的Activity綁定。
- 某個進程持有一個Service,並且該Service調用startForeground()方法使之位於前臺運行。
- 某個進程持有一個Service,並且該Service正在執行它的某個生命週期回調方法,比如onCreate()、 onStart()或onDestroy()。
- 某個進程持有一個BroadcastReceiver,並且該BroadcastReceiver正在執行其onReceive()方法。
用戶正在使用的程序,一般系統是不會殺死前臺進程的,除非用戶強制停止應用或者系統內存不足等極端情況會殺死。
2.2、可見進程(Visible process)
場景:
- 擁有不在前臺、但仍對用戶可見的 Activity(已調用 onPause())。
- 擁有綁定到可見(或前臺)Activity 的 Service
用戶正在使用,看得到,但是摸不着,沒有覆蓋到整個屏幕,只有屏幕的一部分可見進程不包含任何前臺組件,一般系統也是不會殺死可見進程的,除非要在資源吃緊的情況下,要保持某個或多個前臺進程存活
2.3、服務進程(Service process)
場景
- 某個進程中運行着一個Service且該Service是通過startService()啓動的,與用戶看見的界面沒有直接關聯。
在內存不足以維持所有前臺進程和可見進程同時運行的情況下,服務進程會被殺死
2.4、後臺進程(Background process)
場景:
- 在用戶按了"back"或者"home"後,程序本身看不到了,但是其實還在運行的程序,比如Activity調用了onPause方法
系統可能隨時終止它們,回收內存
2.5、空進程(Empty process)
場景:
- 某個進程不包含任何活躍的組件時該進程就會被置爲空進程,完全沒用,殺了它只有好處沒壞處,第一個幹它!
3、內存閾值
上面是進程的分類,進程是怎麼被殺的呢?系統出於體驗和性能上的考慮,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值。
看到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以後的手機應用在進程被殺死後,要採用其他方案。
2、前臺服務
這種大部分人都瞭解,據說這個微信也用過的進程保活方案,移步微信Android客戶端後臺保活經驗分享,這方案實際利用了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可能不一樣),確實進程的優先級有所提高。
3、相互喚醒
相互喚醒的意思就是,假如你手機裏裝了支付寶、淘寶、天貓、UC等阿里系的app,那麼你打開任意一個阿里系的app後,有可能就順便把其他阿里系的app給喚醒了。這個完全有可能的。此外,開機,網絡切換、拍照、拍視頻時候,利用系統產生的廣播也能喚醒app,不過Android N已經將這三種廣播取消了。
如果應用想保活,要是QQ,微信願意救你也行,有多少手機上沒有QQ,微信呢?或者像友盟,信鴿這種推送SDK,也存在喚醒app的功能。
拉活方法
4、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;
}
}
5、粘性服務&與系統服務捆綁
這個是系統自帶的,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>
所以你的應用要是有消息推送的話,那麼可以用這種方式去欺騙用戶。
結束:
聽說賬號同步喚醒APP這種機制很不錯,用戶強制停止都殺不起創建一個賬號並設置同步器,創建週期同步,系統會自動調用同步器,這樣就能激活我們的APP,侷限是國產機會修改最短同步週期(魅藍NOTE2長達30分鐘),並且需要聯網才能使用。在國內各大ROM"欣欣向榮"的大背景下,關於進程保活,不加入白名單,我也很想知道有沒有一個應用永活的方案,這種方案性能好,不費電,或許做不到,或許有牛人可以,但是,通過上面幾種措施,在絕大部分的機型下,絕大部分用戶手機中,我們的進程壽命確實得到了提高,這篇文章都是圍繞怎麼去提高進程oom_adj的值來進行,關於降低內存佔用方面,移步
Android性能優化的方方面面,所以關於我們的應用更加“健康”,性能優化必不可少。
參考鏈接:
關於 Android 進程保活,你所需要知道的一切http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0418/4158.html
Android進程保活招式大全 http://dev.qq.com/topic/57ac4a0ea374c75371c08ce8
論Android應用進程長存的可行性http://blog.csdn.net/aigestudio/article/details/51348408
Android 通過JNI實現守護進程,使Service服務不被殺死 http://mp.weixin.qq.com/s?__biz=MzI0MjE3OTYwMg==&mid=401629367&idx=1&sn=9f086cfdc00f954e21e6a6253f1ae288&scene=21#wechat_redirect