進程優(yōu)先級原理
https://developer.android.google.cn/guide/components/processes-and-threads.html?hl=zh-cn
LMK(LowMemoryKiller)
- 為什么引入LMK错洁?
進程的啟動分冷啟動和熱啟動酥夭,當用戶退出某一個進程的時候暂幼,并不會真正的將進程退出藐窄,而是將這個進程放到后臺,以便下次啟動的時候可以馬上啟動起來楞慈,這個過程名為熱啟動幔烛,這也是Android的設計理念之一。這個機制會帶來一個問題囊蓝,每個進程都有自己獨立的內存地址空間饿悬,隨著應用打開數(shù)量的增多,系統(tǒng)已使用的內存越來越大,就很有可能導致系統(tǒng)內存不足聚霜。為了解決這個問題狡恬,系統(tǒng)引入LowmemoryKiller(簡稱lmk)管理所有進程珠叔,根據一定策略來kill某個進程并釋放占用的內存,保證系統(tǒng)的正常運行 - LMK基本原理
所有應用進程都是從zygote孵化出來的弟劲,記錄在AMS中mLruProcesses列表中祷安,由AMS進行統(tǒng)一管理,AMS中會根據進程的狀態(tài)更新進程對應的oom_adj值兔乞,這個值會通過文件傳遞到kernel中去汇鞭,kernel有個低內存回收機制,在內存達到一定閥值時會觸發(fā)清理oom_adj值高的進程騰出更多的內存空間 - LMK殺進程標準
minfree(存放6個數(shù)值庸追,單位內存頁面數(shù)(一個頁面4kb))
adb shell
su
cat /sys/module/lowmemorykiller/parameters/minfree
adj
cat /sys/module/lowmemorykiller/parameters/adj
查看進程的adj值
/proc/<pid>/oom_adj
/proc/<pid>/oom_score_adj
內存閾值在不同的手機上不一樣霍骄,一旦低于該值,Android便開始按順序關閉進程. 因此Android開始結束優(yōu)先級最低的空進程,即當可用內存小于180MB(46080)
常用的钡荩活方式
- Activity提權
- Service提權
- 廣播拉活
- “全家桶”拉活
- Service機制(Sticky)拉活
- 賬戶同步拉活
- JobScheduler拉活
- 推送拉活
- Native拉活(NDK)
- 雙進程守護(JobScheduler和兩個Service結合用)
Activity提權
- 原理
監(jiān)控手機鎖屏解鎖事件读整,在屏幕鎖屏時啟動1個像素透明的 Activity,在用戶解鎖時將 Activity 銷毀掉咱娶,從而達到提高進程優(yōu)先級的作用米间。 - 代碼實現(xiàn)
- 創(chuàng)建1個像素的Activity KeepActivity
Window window = getWindow();
window.setGravity(Gravity.START | Gravity.TOP);
WindowManager.LayoutParams attributes = window.getAttributes();
attributes.width = 1;
attributes.height = 1;
attributes.x = 0;
attributes.y = 0;
window.setAttributes(attributes);
manifest注冊
styles添加主題樣式
- 創(chuàng)建廣播接收者KeepReceiver
if (TextUtils.equals(action, Intent.ACTION_SCREEN_OFF)) {
//滅屏 開啟1px activity
KeepManager.getInstance().startKeep(context);
} else if (TextUtils.equals(action, Intent.ACTION_SCREEN_ON)) {
//亮屏 關閉
KeepManager.getInstance().finishKeep();
}
- 創(chuàng)建廣播注冊管理單例類KeepManager
注冊/反注冊廣播
啟動/關閉keepActivity
設置keepActivity弱引用
Service提權
- 創(chuàng)建一個前臺服務用于提高app在按下home鍵之后的進程優(yōu)先級
- Service限制https://developer.android.google.cn/about/versions/oreo/background#services
- startForeground(ID,Notification):使Service成為前臺Service。 前臺服務需要在通知欄顯示一條通知
廣播拉活
- 在發(fā)生特定系統(tǒng)事件時豺总,系統(tǒng)會發(fā)出廣播车伞,通過在 AndroidManifest 中靜態(tài)注冊對應的廣播監(jiān)聽器择懂,即可在發(fā)生響應事件時拉活喻喳。但是從android 7.0開始,對廣播進行了限制困曙,而且在8.0更加嚴格
- 可靜態(tài)注冊廣播列表https://developer.android.google.cn/guide/components/broadcast-exceptions.html
“全家桶”拉活
- 有多個app在用戶設備上安裝表伦,只要開啟其中一個就可以將其他的app也拉活。比如手機里裝了手Q慷丽、QQ空間蹦哼、興趣部落等等,那么打開任意一個app后要糊,其他的app也都會被喚醒纲熏。
Service機制(Sticky)拉活
- 將 Service 設置為 START_STICKY,利用系統(tǒng)機制在 Service 掛掉后自動拉活
- START_STICKY:
“粘性”锄俄。如果service進程被kill掉局劲,保留service的狀態(tài)為開始狀態(tài),但不保留遞送的intent對象奶赠。隨后系統(tǒng)會嘗試重新創(chuàng)建service鱼填,由于服務狀態(tài)為開始狀態(tài),所以創(chuàng)建服務后一定會調用onStartCommand(Intent,int,int)方法毅戈。如果在此期間沒有任何啟動命令被傳遞到service苹丸,那么參數(shù)Intent將為null愤惰。 - START_NOT_STICKY:
“非粘性的”。使用這個返回值時赘理,如果在執(zhí)行完onStartCommand后宦言,服務被異常kill掉,系統(tǒng)不會自動重啟該服務商模。 - START_REDELIVER_INTENT:
重傳Intent蜡励。使用這個返回值時,如果在執(zhí)行完onStartCommand后阻桅,服務被異常kill掉凉倚,系統(tǒng)會自動重啟該服務,并將Intent的值傳入嫂沉。 - START_STICKY_COMPATIBILITY:
START_STICKY的兼容版本稽寒,但不保證服務被kill后一定能重啟。
- 只要 targetSdkVersion 不小于5趟章,就默認是 START_STICKY杏糙。
但是某些ROM 系統(tǒng)不會拉活。并且經過測試蚓土,Service 第一次被異常殺死后很快被重啟宏侍,第二次會比第一次慢,第三次又會比前一次慢蜀漆,一旦在短時間內 Service 被殺死4-5次谅河,則系統(tǒng)不再拉起。
賬戶同步拉活
- 手機系統(tǒng)設置里會有“帳戶”一項功能确丢,任何第三方APP都可以通過此功能將數(shù)據在一定時間內同步到服務器中去绷耍。系統(tǒng)在將APP帳戶同步時,會將未啟動的APP進程拉活https://github.com/googlesamples/android-BasicSyncAdapter.比較繁瑣鲜侥,效果還不一定好褂始,這里就不研究了.
JobScheduler拉活(為后續(xù)雙進程守護做準備)
- JobScheduler允許在特定狀態(tài)與特定時間間隔周期執(zhí)行任務∶韬可以利用它的這個特點完成逼槊纾活的功能,效果即開啟一個定時器,與普通定時器不同的是其調度由系統(tǒng)完成舀寓。
- 創(chuàng)建一個JobService
注意setPeriodic方法
在7.0以上如果設置小于15min不起作用胆数,可以使用setMinimumLatency設置延時啟動,并且輪詢
推送拉活
- 根據終端不同基公,在小米手機(包括 MIUI)接入小米推送幅慌、華為手機接入華為推送。
Native拉活(NDK)
- Native fork子進程用于觀察當前app主進程的存亡狀態(tài)轰豆。對于5.0以上成功率極低胰伍。
雙進程守護(最后一個當然是重點啦)
- 兩個進程共同運行齿诞,如果有其中一個進程被殺,那么另外一個進程就會將被殺的進程重新拉起骂租,結合JobScheduler一起拉活祷杈,這種效率是最高的。會附上核心代碼渗饮。
public class LocalService extends Service {
private MyBinder myBinder;
class MyBinder extends IMyAidlInterface.Stub{
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString) throws RemoteException {
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
@Override
public void onCreate() {
super.onCreate();
myBinder = new MyBinder();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("deamon", "deamon",
NotificationManager.IMPORTANCE_LOW);
NotificationManager manager = (NotificationManager) getSystemService(
Context.NOTIFICATION_SERVICE);
if (manager == null)
return;
manager.createNotificationChannel(channel);
Notification notification = new NotificationCompat.Builder(this,
"deamon").setAutoCancel(true).setCategory(
Notification.CATEGORY_SERVICE).setOngoing(true).setPriority(
NotificationManager.IMPORTANCE_LOW).build();
startForeground(10, notification);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
//如果 18 以上的設備 啟動一個Service startForeground給相同的id
//然后結束那個Service
startForeground(10, new Notification());
startService(new Intent(this, InnnerService.class));
} else {
startForeground(10, new Notification());
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
bindService(new Intent(this, RemoteService.class), new MyServiceConnection(),
BIND_AUTO_CREATE);
return super.onStartCommand(intent, flags, startId);
}
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//回調
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
//
startService(new Intent(LocalService.this, RemoteService.class));
bindService(new Intent(LocalService.this, RemoteService.class), new MyServiceConnection(),
BIND_AUTO_CREATE);
}
}
public static class InnnerService extends Service {
@Override
public void onCreate() {
super.onCreate();
startForeground(10, new Notification());
stopSelf();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
}
public class RemoteService extends Service {
private MyBinder myBinder;
class MyBinder extends IMyAidlInterface.Stub {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString) throws RemoteException {
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return myBinder;
}
@Override
public void onCreate() {
super.onCreate();
myBinder = new MyBinder();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel("deamon", "deamon",
NotificationManager.IMPORTANCE_LOW);
NotificationManager manager = (NotificationManager) getSystemService(
Context.NOTIFICATION_SERVICE);
if (manager == null)
return;
manager.createNotificationChannel(channel);
Notification notification = new NotificationCompat.Builder(this,
"deamon").setAutoCancel(true).setCategory(
Notification.CATEGORY_SERVICE).setOngoing(true).setPriority(
NotificationManager.IMPORTANCE_LOW).build();
startForeground(10, notification);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
//如果 18 以上的設備 啟動一個Service startForeground給相同的id
//然后結束那個Service
startForeground(10, new Notification());
startService(new Intent(this, InnnerService.class));
} else {
startForeground(10, new Notification());
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
bindService(new Intent(this, LocalService.class), new MyServiceConnection(),
BIND_AUTO_CREATE);
return super.onStartCommand(intent, flags, startId);
}
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
startService(new Intent(RemoteService.this, LocalService.class));
bindService(new Intent(RemoteService.this, LocalService.class),
new MyServiceConnection(), BIND_AUTO_CREATE);
}
}
public static class InnnerService extends Service {
@Override
public void onCreate() {
super.onCreate();
startForeground(10, new Notification());
stopSelf();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
}
@SuppressLint("NewApi")
public class MyJobService extends JobService {
private static final String TAG = "MyJobService";
public static void startJob(Context context) {
JobScheduler jobScheduler = (JobScheduler) context.getSystemService(
Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(10,
new ComponentName(context.getPackageName(),
MyJobService.class.getName())).setPersisted(true);
/**
* I was having this problem and after review some blogs and the official documentation,
* I realised that JobScheduler is having difference behavior on Android N(24 and 25).
* JobScheduler works with a minimum periodic of 15 mins.
*
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//7.0以上延遲1s執(zhí)行
builder.setMinimumLatency(1000);
} else {
//每隔1s執(zhí)行一次job
builder.setPeriodic(1000);
}
jobScheduler.schedule(builder.build());
}
@Override
public boolean onStartJob(JobParameters jobParameters) {
Log.e(TAG, "開啟job");
//7.0以上輪詢
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
startJob(this);
}
//判斷服務是否在運行
boolean isLocalServiceRun = isServiceRunning(this, LocalService.class.getName());
boolean isRemoteServiceRun = isServiceRunning(this, RemoteService.class.getName());
if (!isLocalServiceRun || !isRemoteServiceRun) {
startService(new Intent(this, LocalService.class));
startService(new Intent(this, RemoteService.class));
}
return false;
}
private boolean isServiceRunning(Context context, String serviceName) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> runningServices = am.getRunningServices(10);
for (ActivityManager.RunningServiceInfo runningService : runningServices) {
if (TextUtils.equals(runningService.service.getClassName(), serviceName)) {
return true;
}
}
return false;
}
@Override
public boolean onStopJob(JobParameters jobParameters) {
return false;
}
}
總結:目前沒有說百分之百保證能拉活的一些方法但汞,除非你叫廠商給你開白名單。你不是大廠的APP并且有幾千萬用戶的沒這個必要互站,一般來說你給不起廠商這個錢私蕾。以上說的這些拉活其實是些流氓軟件常常用到的,有風險胡桃。并且慎用踩叭,這些APP裝在手機上面雖然功能實現(xiàn)了,但是可能會造成內存不足(贝湟龋活了就釋放不了了嘛,根據需求)容贝。
應用場景:
比如你的APP對于用戶地理位置很敏感,需要5分鐘上傳一次給服務器記錄每個用戶的地理位置之景,按照普通的做法沒苯锔唬活的話那肯定實現(xiàn)不了嘛,用這種方式是可以的锻狗。
自身經歷:
幾年前因為業(yè)務需要满力,跟著我?guī)煾杆麄児咀隽艘粋€短信扣費的功能的,也就是傳說中的SP吧屋谭,屬于打擦邊球的脚囊,也算是屬于和中國xx合作龟糕。我用的廣播的形式桐磁,只要用戶一啟動APP,就自動獲取手機信息讲岁,包括SIM卡我擂,上傳到服務器,然后服務器根據這些信息直接去扣費缓艳,或者服務器給客戶端發(fā)送什么指令校摩,要用戶根據指令發(fā)送短信(目前也可以實現(xiàn)),如果是在2012年左右阶淘,這項業(yè)務是非常非常賺錢的(可惜那時候我還沒學安卓).如果是4.4手機系統(tǒng)的話衙吩,收到扣費信息都可以屏蔽掉,現(xiàn)在都是高版本的手機一般來說屏蔽不了短信了溪窒。那時候各項機制都不是特別健全坤塞。所以很多這種冯勉。所以,少去不正經的網站下載小游戲摹芙,一不小心灼狰,你口袋里面20塊可能就沒了。