#进程保活机制
进程保活:说白了就是尽量的保证你的App不被系统杀死,或者被杀死后,还能后“复活”。以下介绍进程保活的几种常用方式。
Android杀死进程的机制来自于linux的low memory killer,他会对所有的进程进行一个排名,衡量的参数就是oom_adj。此值越大,进程越容易被系统杀死。 一般系统应用oom_adj的值都是小于0的,比如系统的启动init进程,oom_ajd = -16
**查看oom_adj值:**oom_adj:这个值越大,标明此进程越容易被回收。系统进程这个值都是小于0的。
adb shell cat /proc/pid(你的进程id)/oom_adj
pid的获取命令:ps | grep 进程名
首先我们看一下系统杀死app的优先级(从高到低,越低越容易被杀掉):
前台进程: 你当前正在使用的app进程,这是最后被系统杀死的进程。查看oom_adj,值为0
可见进程: 可见进程不包含任何的前台组件,可见进程依然会影响用户在屏幕上可以看到的内容。比如:activity弹出来一个不全屏的Dialog。从代码角度来说:就是调用了onPause,但是没有调用onStop。 查看oom_adj,值为1
服务进程: app进程启动了一个Servie,并且该进程并没有和任何的界面做绑定。 oom_adj 值为
后台进程: app退出后,会持有一个Activity,从代码逻辑上来说,就是activity没有调用onDestroy。 oom_adj值为7
空进程: app退出后,不包含任何活动的组件,从代码角度来说就是activity调用了onDestroy,但是系统还没有进行回收。该进程是最先被系统kill的。 oom_adj值为9
以上不同进程的oom_adj根据不同的具体手机,可能会有不同,仅仅是反应了一个趋势。
在Android系统里,进程被杀的原因通常为以下几个方面:
a. 应用Crash
b. 系统回收内存
c. 用户触发
d. 第三方root权限app.
本文仅仅是讨论前两种,通过以下发发实现后,你也会发现oom_adj的值确实变小了,这就意味着从low memeo killer 的角度来说,已经完成。一般来说:你通过用户触发的方式,你会得到onDestory的响应,这样你是可以做一些操作处理的。但是通过X60这种方式,你会发现:你根本不会相应onDestroy。而且它会把你杀的干干净净。由此我们也可以加单的猜测:X60这种清理方式:不仅仅是清理你的原始app的进程,他也会把你启动的另一个进程kill掉(可以根据包名查到你启动的相关的进程)从而无法相互唤醒(猜测而已)。
1。 前台服务(带有Notification)
设置前台服务,通过这样的方式,就会出现一个Notification来告知用户,比如QQ音乐。这种实现的方式比较简单:实现Service的时候,通过接口:setForeground(int id, Notification notification)就可以实现前台服务。这个属于上述中的“前台进程”;
缺点:会出现一个Notification,这个用户是的感知的,一般用户不喜欢这个东西的存在。
2。 通过Android的系统漏洞实现前台服务(无Notification)
利用系统的漏洞来启动一个前台Service进程。与普通启动方式的区别在于:没有生成一个Notification,用户感知不到(这是一个漏洞,android后续版本可能会随时的关闭)。
API<18:启动前台Service时直接传入一个new Notification()即可。
API>18:同时启动两个id相同的前台Service,然后再将后启动的Service做stop处理(这是个android漏洞,我自己测试了galaxy s6 android 6.0是可以的)
**查看是否启动成功:**adb shell dumpsys activity services packagename。回车,查看isForground字段是否为true。
具体的代码如下所示:
@Override public int onStartCommand(Intent intent, int flags, int startId) {
if (Build.VERSION.SDK_INT < 18) {
startForeground(GRAY_SERVICE_ID, new Notification());//API < 18 ,此方法能有效隐藏Notification上的图标
}
else {
Intent innerIntent = new Intent(this, SecondService.class);
startService(innerIntent);
startForeground(GRAY_SERVICE_ID, new Notification());
}
}
public static class SecondService extends Service {
@Override public void onCreate() {
super.onCreate();
}
@Override public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(GRAY_SERVICE_ID, new Notification());
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
@Override public IBinder onBind(Intent intent) {
return null;
}
}
通过上面的代码后,你在查看你进程的oom_adj。你会发现发生了很大的变化。
3。 通过多进程相互唤醒机制
**多进程启动:**在老的进程中新启动一个前台进程ServcieNew(通过上述的方案2),SericeNew进程每个一段时间检查换上一个进程是否存在,如果不存在立马启动,或者通过广播的方式发送广播,通知老的进程重新启动业务逻辑。
多进程需要注意的地方:你的老的进程的Application的onCreate会被调用两次,这个地方一般都是用来初始化操作,多进程需要通过AIDL或者广播的方式进程通信。
Application的onCreate会被调用两次的解决方法:
@Override public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate function");
int pid = Process.myPid();
String processName = null;
ActivityManager actManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
for (ActivityManager.RunningAppProcessInfo appProcess : actManager.getRunningAppProcesses()) {
if (appProcess.pid == pid) {
processName = appProcess.processName;
}
}
if (processName.equals(getPackageName())) {
init();
}
}
以下通过广播的方式来说明,具体的实现方式如下:
首先在你的老的进程中实现一个静态广播(在Androidmaniest.xml中声明该广播,这样程序就算被杀掉了也可以收到该广播)BroadcastReceiver :
public class RecoveryReceiver extends BroadcastReceiver {
private final static String TAG = WakeReceiver.class.getSimpleName();
private final static int RECOVERY_SERVICE_ID = -110;
public final static String RECOVERY_ACTION = "com.recovery.receiver";
@Override public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (RECOVERY_ACTION.equals(action)) {
Intent wakeIntent = new Intent(context, WakeNotifyService.class);
//收到此消息后启动自己的业务逻辑
}
}
}
其次在新启动的进程中,每隔一段时间发送一次广播(没有检测老的进行是否还存在),通知老进程启动业务逻辑,需要注意的是:新启动的进程也要使用进程保活机制,尽量的保证不能轻易的被系统杀掉:
private final static int ALARM_INTERVAL = 5 * 60 * 1000; //每个5分钟发送一次广播
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent alarmIntent = new Intent();
alarmIntent.setAction(RecoveryReceiver.RECOVERY_ACTION);
PendingIntent operation = PendingIntent.getBroadcast(this, WAKE_REQUEST_CODE, alarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), ALARM_INTERVAL,
operation);
**验证方式:**adb shell之后,kill掉你的第一个进程,然后等待看两一个Service能不能将该进程启动。
4。 通过系统广播来启动进程
Android提供了很多的系统广播,如:
系统重新启动
调用系统相拍照的广播(android4.0新增加了android.hardware.action.NEW_PICTURE、android.hardware.action.NEW_VIDEO,针对照片类型的APP,应用场景很大)等。
以拍照为例:
注册一个静态广播(静态广播在你程序不启动的时候就可以接收到,注意广播的exprote务必设置为true,否则你会接收不到,另外,这个不需要Camera等权限)
android:name=".PicReceiver"
android:enabled="true"
android:exported="true">
**验证方式:**adb shell之后,kill掉你的进程,然后拍照片看是否可以启动。
5。 通过SynAdapter
一种提高Android应用进程存活率新方法 来自csdn 作者:zgzhaobo
上述这种方法“利用Android系统提供的账号和同步机制实现”,进行被kill掉后,进程被系统kill后,可以由syn拉起。 缺点:用户可以单独的停止或者删除,而且有些手机是默认不同步,需要用户参与。个人感觉这种方案通用性不是很大。
关于Android系统账号可以参考文章:
Write your own Android Sync Adapter
Write your own Android Authenticator
6。 通过第三方的推送SDK
目前业界很多的第三方推送SDK(友盟、极光推送等),都是通过长链接的方式完成推送,我们就可以通过这些sdk来实现进程的保活。
总结: 以上介绍了low memoey killer 下的进程保活。其实,不管你采用哪种方式,当你的进程内存太大了,系统都会将你进程杀掉。以上手段只是辅助手段,降低进程的内存才是最终的手段。
7。应用Crash后的重新启动
在你的应用Crash之后,你是可以通过Android给你提供的消息回调进行相应的处理的,比如:保存crash日志等各种业务逻辑。目前很多手机Rom在你crash之后,会立马回到你刚才crash的页面,但是由于你代码的逻辑问题,你会发现该页面根本没有进行业务操作。有的时候,你当前的页面没有办法进行任何的操作来串起你的流程来。
这个时候建议crash之后,让你的app重启。不要让默认的Rom操作回到你原来crash的页面(当然有的Rom在你crash之后会重启应用,为了统一处理,这个逻辑最好还是我们自己来进行处理。这个逻辑最好放在你程序的基类当中,这样不管是那个一页面发生了crash,重新启动你都会进入到自己的重启流程。