- Android 进程优先级介绍
- Android 进程回收策略介绍
- Android 目前系统保活策略
- 项目中使用进程保活代码
1、Android进程等级分级和等级介绍
Android 系统将尽量长时间地保持应用的进程,但是系统运行内存是有限的,所以为了 新建进程或者运行更重要的进程,最终还是需要清除旧进程来回收内存。
所以分区进程重要程度,系统会根据进程的运行组件和组件的状态,将每一个进程放入“重要性层级结构”,必要时系统会首先清除掉重要性最低的进程,在内存不足时,再清除重要性稍低的进程,以此类推,以回收系统资源。
进程重要性分级
前台进程
可见进程
服务进程
后台进程
空进程
前台进程:用户当前操作所必需的进程,只有在内部不足以支它们同时运行。系统才会终止它们。
- 用户正在交互的Activity(已调用 onResume())
- Service,绑定在用户正在交互的Activity
- 正在前台运行的Service(服务已经调用 startForeground())
- 正在执行生命周期的Service(onCreat,onStart,onDestory)
- 正在执行 onReceive 方法的广播 BrocadcastReceiver
可见进程:没有任何前台组件,但是仍然会影响用户在屏幕上所见内容的进程。
- 不在前台,但是用户仍然可见(已调用 onPause())例如没dialog覆盖
- 绑定在可见 Activity的Service
服务进程:没有任何前台组件,但是通过它们执行一些用户关心的操作(音乐播放,网络下载)
- 正在运行 startService() 方法启动的服务
后台服务:后台进程对用户体验没有直接影响,系统可以随时终止它们
- 对用户不可见的Activity的进程,(已调用 onstop方法)
空进程:保留这种进程的唯一作用是用做缓存,缩短下次在运行组件所需的启动时间
- 不含任何活动组件的进程
2、Android 进程回收策略
众所周知,Android是基于Linux系统的。在Android进程回收策略中,Android进程与Linux进程根据OOM_ADJ阈值进行区分:
OOM_ADJ >= 4:比较容易被杀死的进程
OOM_ADJ 0 ~ 3:不容易被杀死的进程
OOM_ADJ < 0 :纯Linux进程,非Android进程
当Android系统察觉设备内存不足时,会按照阈值从大到小杀死进程。
具体的oom_adj值的意义我们可以查看AOSP中的com.android.server.am.ProcessList 文件(其中本人添加了一些中文注释):
/**
1. Activity manager code dealing with processes.
*/
final class ProcessList {
...
// OOM adjustments for processes in various states:
// Adjustment used in certain places where we don't know it yet.
// (Generally this is something that is going to be cached, but we
// don't know the exact value in the cached range to assign yet.)
// 未知进程,通常是用作缓存
static final int UNKNOWN_ADJ = 16;
// This is a process only hosting activities that are not visible,
// so it can be killed without any disruption.
// 拥有不可视的Activity的进程,可以不影响影响用户的情况下杀掉
static final int CACHED_APP_MAX_ADJ = 15;
static final int CACHED_APP_MIN_ADJ = 9;
// The B list of SERVICE_ADJ -- these are the old and decrepit
// services that aren't as shiny and interesting as the ones in the A list.
// 一些旧的服务进程
static final int SERVICE_B_ADJ = 8;
// This is the process of the previous application that the user was in.
// This process is kept above other things, because it is very common to
// switch back to the previous app. This is important both for recent
// task switch (toggling between the two top recent apps) as well as normal
// UI flow such as clicking on a URI in the e-mail app to view in the browser,
// and then pressing back to return to e-mail.
// 用户使用的前一个进程
static final int PREVIOUS_APP_ADJ = 7;
// This is a process holding the home application -- we want to try
// avoiding killing it, even if it would normally be in the background,
// because the user interacts with it so much.
// 主界面进程
static final int HOME_APP_ADJ = 6;
// This is a process holding an application service -- killing it will not
// have much of an impact as far as the user is concerned.
// 持有应用服务的进程
static final int SERVICE_ADJ = 5;
// This is a process with a heavy-weight application. It is in the
// background, but we want to try to avoid killing it. Value set in
// system/rootdir/init.rc on startup.
// 重量级应用进程
static final int HEAVY_WEIGHT_APP_ADJ = 4;
// This is a process currently hosting a backup operation. Killing it
// is not entirely fatal but is generally a bad idea.
// 执行备份操作的进程
static final int BACKUP_APP_ADJ = 3;
// This is a process only hosting components that are perceptible to the
// user, and we really want to avoid killing them, but they are not
// immediately visible. An example is background music playback.
// 拥有用户可感知组件的进程
static final int PERCEPTIBLE_APP_ADJ = 2;
// This is a process only hosting activities that are visible to the
// user, so we'd prefer they don't disappear.
// 拥有用户仅可见、不可交互的Activity的进程
static final int VISIBLE_APP_ADJ = 1;
// This is the process running the current foreground app. We'd really
// rather not kill it!
// 前台运行的进程
static final int FOREGROUND_APP_ADJ = 0;
// This is a system persistent process, such as telephony. Definitely
// don't want to kill it, but doing so is not completely fatal.
// 系统常驻进程
static final int PERSISTENT_PROC_ADJ = -12;
// The system process runs at the default adjustment.
// 系统进程
static final int SYSTEM_ADJ = -16;
// Special code for native processes that are not being managed by the system (so
// don't have an oom adj assigned by the system).
// 为native进程保留,他们不被系统管理
static final int NATIVE_ADJ = -17;
...
}
Android 进程被杀死情况:
①触发系统进程管理机制回收(Lowmemorykiller):这种方法会按照阈值从大到小进行清理
②被没有进行Root的第三方应用杀死(使用killBackgroundProcess方法):这种方法只能杀死OOM_ADJ为4以上的进程
③被进行Root的第三方应用杀死(使用force-stop或者kill):理论上来说可以杀死所有进程,但一般只会清理非系统关键进程和非前台可见进程
④厂商的杀进程功能(force-stop或者kill):理论上来说可以杀死所有进程,包括Linux原生进程
⑤用户主动“强行停止”进程(force-stop):只能停用第三方和非system/phone进程应用(停用system进程应用会造成Android系统重启)
我们可以通过adb shell命令实时查看这个adj值。
adb shell
ps | grep <关键字>
USER PID PPID VSIZE RSS WCHAN PC NAME
root 1 0 812 668 ffffffff 00000000 S /init
root 2 0 0 0 ffffffff 00000000 S kthreadd
adb shell
cat /proc/PID/oom_adj
cat命令执行后,会得到一个adj整数数值。
3. Android 目前系统保活策略
A: Service的onStartCommand函数返回START_STICKY
START_STICKY是官方提供的参数,意思是当service被内存回收了,系统会对service进行重启。面对360等内存回收,并没什么作用。
B:在service 的onDestory里面重启服务
onDestroy()方法只有在service正常停止的时候才会被调用,面对上述回收的第二与第三种方法没有效果。
C:守护线程相互监听
AB两个进程,A进程里面轮询检查B进程是否存活,没存活的话将其拉起,同样B进程里面轮询检查A进程是否存活,没存活的话也将其拉起,而我们的后台逻辑则随便放在某个进程里执行即可。
这种方法面对回收的时候,其实作用也不大,而且很消耗性能。另外,也有人提到过使用两个native进程监控,那种方法没试过。
D: AlarmManager or JobScheduler循环触发
public class AlarmService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
initAlarm(this);
Log.d("czh", "AlarmService onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
private void initAlarm(Context context) {
Intent intent = new Intent(context, AlarmReceiver.class);
intent.setAction("repeating");
PendingIntent sender = PendingIntent.getBroadcast(context, 0, intent, 0);
//开始时间
long firsTime = SystemClock.elapsedRealtime();
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, firsTime, 5 * 1000, sender);
}
}
PendingIntent.getBroadcase的注册广播
E:与系统service绑定
论Android应用进程长存的可行性 一文中,提到用NotificationListenerService代替普通service,从而达到保活的作用。 原理是没有问题,在小米4上亲测过后,发现并没什么用。
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class SimulateNotificationService extends NotificationListenerService {
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
super.onNotificationPosted(sbn);
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
super.onNotificationRemoved(sbn);
}
}
<service android:name=".service.SimulateNotificationService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
android:process=":test5">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
F:监听系统Receiver保活
使用Receiver来检测目标进程是否存活不失为一个好方法,静态注册一系列广播,什么开机启动、网络状态变化、时区地区变化、充电状态变化等等等等,这听起来好像很6,而且在大部分手机中都是可行的方案,但是对于深度定制的ROM,是的,又是深度定制,你没有看错,而且代表性人物还是魅族、小米,这两个业界出了名的喜欢“深度定制”系统。
自从Android 3.1开始系统对我们的应用增加了一种叫做STOPPED的状态,什么叫STOPPED?就是安装了之后从未启动过的,大家可能经常在网上看到对开机广播的解释,说要想应用正确接收到开机广播那么就得先启动一下应用,这个说法的技术支持就来源于此,因为自Android 3.1后所有的系统广播都会在Intent添加一个叫做FLAG_EXCLUDE_STOPPED_PACKAGES的标识,说白了就是所有处于STOPPED状态的应用都不可以接收到系统广播。
H:提高进程优先级, Notification提权
这种保活手段是应用范围最广泛。它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程一样。这样做带来的好处就是,用户无法察觉到你运行着一个前台进程(因为看不到Notification),但你的进程优先级又是高于普通后台进程的。
这种方法面对第二种回收方式有效,但是面对小米之类的后台回收,还是无能为力。
public class NotificationService extends Service {
private final static int SERVICE_ID = 1001;
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (Build.VERSION.SDK_INT < 18) {
startForeground(SERVICE_ID, new Notification());
} else {
Intent innerIntent = new Intent(this, InnerService.class);
startService(innerIntent);
startForeground(SERVICE_ID, new Notification());
}
return super.onStartCommand(intent, flags, startId);
}
/**
* 给 API >= 18
*/
public static class InnerService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
startForeground(SERVICE_ID, new Notification());
stopForeground(true);
stopSelf();
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
}
I:不同的app进程,用广播相互唤醒
如果你手机安装了各种app,或者应用了各种第三方代sdk,即可互相唤醒。
假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。
这个方法针对内存回收的三种方式均有效,只要有一个活着,其他的就会活下来。
这篇文章是东拼西凑出来的,目的是为了加深印象,方面自己之后的学习。
分别引用:
安卓笔记侠