概述:
进程保活是android的一个特色,承担了很多应用上层的业务。
但随着android的发展,尤其是android 6.0+,系统对这块进行了收拢,常规的保活技术越来越难。
常规的保活技术:
1.进程保活[提升优先级,防杀]
Android系统在APP退出后台时并不会真正杀掉这个进程,而是将其缓存起来方便下次的快速启用。
内存不足的情况下,系统会依据一套Low Memory Killer 机制杀进程。
科普:
Linux内核会为每个进程分配一个oom_adj值,代表进程的优先级。值越大,代表进程优先级越低,进程就更容易被回收,Lovw Memory Killer 就是根据这个值来据欸的那个哪个进程被回收。
oom_adj级别 | 值 | 解释 |
UNKNOWN_ADJ | 16 | 预留的最低级别,一般对于缓存的进程才有可能设置这个级别。 |
CACHED_APP_MAX_ADJ | 15 | 缓存进程,空进程,在内存不足的情况下就会被优先杀死。 |
CACHED_APP_MIN_ADJ | 9 | 缓存进程,空进程 |
SERVICE_B_ADJ | 8 | 不活跃的进程 |
PREVIOUS_APP_ADJ | 7 | 切换进程 |
HOME_APP_ADJ | 6 | 与Home交互的进程 |
SERVICE_ADK | 5 | 有Service的进程 |
HEAVY_WEIGHT_APP_ADJ | 4 | 高权重进程 |
BACKUP_APP_ADJ | 3 |
正在备份的进程 |
PERCEPTIBLE_APP_ADJ | 2 | 可感知的进程, 如播放音乐的进程 |
VISIBLE_APP_ADJ | 1 | 可见进程,如Acitivty |
FOREGROUND_APP_ADJ | 0 | 前台进程 |
PERSISTENT_SERVICE_ADJ | -11 | 重要进程 |
PERSISTENT_PROC_ADJ | -12 | 核心进程 |
SYSTEM_ADJ | -16 | 系统进程 |
NATIVE_ADJ | -17 | Native进程 |
2.利用系统通知管理权限提升保活能力
·当APP拥有通知栏管理权限时,在NotificationListenerSerivce启动后,进程oom_adj可被提升到1.
具有很强的保活能力,需要用户授权。
3.利用系统辅助功能提升保活能力
在APP获取到辅助功能权限后,进程oom_adj可被提升到1.
但是进程被杀后需要用户重新授权。
4.利用系统机制开启前台服务提升保活能力
- API < 18 : startForeground(ID, new Notification())发送空的Notification,图标不会显示
- API >= 18 : 需要提高优先级的service A中启动一个InnerService,2个服务同时startForeground且绑定同一个ID,停止掉InnerService,这样通知图标就会被移除。
利用系统机制开启前台服务提升保活能力在api >= 24 时候就失效了。
5.伪装成输入法提升保活能力
有些系统ROM中,可以将自己需要保活的进程伪装为输入法进程,拥有很强的保活能力,但是一般在一些系统软件能力较差的手机上有效。
6. 在后台播放无声音乐
就像前端的flash开发,使用1像素点一样。
主要是将需要保活 的进程伪装成播放音乐进程,播放音乐的进程一般都有较强的保活能力,但是一般在一些系统软件能力较差的手机上有效。
7. 进程拉活[被杀后重启]
7.1 利用系统广播拉活:
常用系统广播:
- ACTION_BOOT_COMPLETED
- CONNECTIVITY_ACTION
- ACITON_USER_PRESENT
很多热门的ROM都增加了自启管理,导致无法接收广播,从而服务无法自启。
7.2 利用系统Service.START_STICKY机制拉活:
将Serive设置START_STICKY,利用系统机制在Service挂掉后将其自动拉活。
- Service在被第一次异常杀死后会在5s内重启,第2次杀死后会在10s内重启,第3次杀死后会在20s内重启,一旦短时间内Service被杀死的次数达到5次,则系统不再拉活该Service
- 进程被获取ROOT权限的管理工具或系统工具通过force-stop系统停掉,这时无法重启。
7.3 利用Native进程拉活:
android系统中,所有进程和系统组件的生命周期受到AMS(ActivityManagerService的统一管理。通过Linux的fork机制创建的进程为纯linux进程,其生命周期不受到Android系统的管理)
监听到主进程死亡后,通过am命令拉活主进程。
适用于android 5.0-手机,android 5.0+的手机中Native进程照样被杀。
7.4 利用AlarmManager定时拉活:
开一个定时任务,在后台隔一段时间拉起进程。【注意新系统对后台的延时影响】
7.5 利用JobScheduler机制拉活:
android 5.0+,系统提供JobScheduler,允许开发者在符合某些条件时创建在后台执行的任务。利用该机制可使得系统拉活进程。
适用5.0+,进程被强制停止后也可以进行拉活。
7.6利用账号同步机制拉活:
所有的android版本都可以,包括被强制停止后也可进行拉活,但一些自启管理的手机机型可能会不支持该方案。
7.7 利用推送SDK拉活:
一般第三方推送SDK都具备矩阵拉活能力,推送SDK的相关接入方通常都可以相互保活。
保活的谬论:
进程保活常伴随着耗电增加,android系统升级伴随的是省电。不能节能的维持进程保活的手段都是不好的。
正确的保活应当对性能、电量、用户体验足够小。
随着andorid官方的管束,保活成了越来越大的难题。
就android P而言,加入了多项目设备电量管理新特性,让app后台消息推送、应用保活变得困难。
例如android P:
1. 应用待机分组:使系统可以根据用户的使用情况而限制应用调用CPU or 网络等设备资源。
2. 后台限制:若应用出现了Android Vitals内所描述的不良行为,系统将提醒用户限制该应用访问设备资源。
3. 省电模式的优化:启用该功能后,系统将对所有应用的后台运行服务加以限制。
4. 低耗电模式:用户一段时间后没有使用设备,设备就会进入低耗电模式,所有app都将受到影响。
进程保活牵涉系统底层,本质需要对Android的文件系统有深入的了解。
关于P 、Q的限制,可查看bilibili上的goolge官方视频.
保活实操:(基于jobService)
保活分为多线程保活和应用层保活:
核心方法是onBind:
代码:
import android.annotation.TargetApi;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
* 一个轻量的后台job service,利用空闲时间执行一些小事情,提高进程不被回收的概率
*/
@TargetApi(value = Build.VERSION_CODES.LOLLIPOP)
public class AliveJobService extends JobService {
private static final String TAG = AliveJobService.class.getName();
private JobScheduler mJobScheduler;
private Handler mJobHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.d(TAG, "pull alive.");
jobFinished((JobParameters) msg.obj, false);
return true;
}
});
public static void start(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Intent intent = new Intent(context, AliveJobService.class);
context.startService(intent);
}
}
@Override
public void onCreate() {
super.onCreate();
mJobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
JobInfo job = initJobInfo(startId);
if (mJobScheduler.schedule(job) <= 0) {
Log.d(TAG, "AliveJobService failed");
} else {
Log.d(TAG, "AliveJobService success");
}
return START_STICKY;
}
//开始任务
@Override
public boolean onStartJob(JobParameters params) {
mJobHandler.sendMessage(Message.obtain(mJobHandler, 1, params));
return true;
}
//结束任务
@Override
public boolean onStopJob(JobParameters params) {
mJobHandler.sendEmptyMessage(1);
return false;
}
//执行条件
private JobInfo initJobInfo(int startId) {
JobInfo.Builder builder = new JobInfo.Builder(startId,
new ComponentName(getPackageName(), AliveJobService.class.getName()));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setMinimumLatency(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS); //执行的最小延迟时间
builder.setOverrideDeadline(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS); //执行的最长延时时间
builder.setBackoffCriteria(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS,
JobInfo.BACKOFF_POLICY_LINEAR);//线性重试方案
} else {
builder.setPeriodic(JobInfo.DEFAULT_INITIAL_BACKOFF_MILLIS);
}
builder.setPersisted(false);
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE);
builder.setRequiresCharging(false);
return builder.build();
}
}
清单文件中: