1 前言
前面一篇【Android實例】1像素進程保活(一)是最基礎的寫法,但是這種寫法還有些不足,還可以進行優化。因爲內存也是一個考慮的因素,內存越多的進程會最先被kill掉,所以我們可以開啓一個服務,然後將該服務放在另一個進程中,這樣這個進程就更加的輕量,更不容易被殺死。
系統出於體驗和性能上的考慮,app在退到後臺時系統並不會真正的kill掉這個進程,而是將其緩存起來。打開的應用越多,後臺緩存的進程也越多。在系統內存不足的情況下,系統開始依據自身的一套進程回收機制來判斷要kill掉哪些進程,以騰出內存來供給需要的app, 這套殺進程回收內存的機制就叫 Low Memory Killer。
2 查看手機內存閾值
- 首先你的手機需要得到root權限
- 在獲得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,,所以通過 就能轉換爲M:72M,90M,108M,126M,144M,180M,即、、、、、這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以上的手機上無法保證應用被殺死後能再次“復活”應用。