1、介绍
rescue party救援程序是Android 8加入的,主要目的是如果监控到核心程序出现循环崩溃时,就会启动救援程序,根据不同的救援级别做不同的操作,最严重的情况下可能会进入Recovery模式。Rescue Party共有5种救援级别,对应如下。
救援级别 | 对应数值 |
---|
LEVEL_NONE | 0 | LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS | 1 | LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES | 2 | LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS | 3 | LEVEL_FACTORY_RESET | 4 |
那么什么情况下会触发该程序呢?有两种情况
- system_server在5分钟内重启5次以上调整一次救援级别
- 永久性系统应用在30s内崩溃5次以上调整一次级别
2、源码分布
frameworks\base\services\core\java\com\android\server\RescueParty.java
frameworks\base\services\java\com\android\server\SystemServer.java
3、流程分析
Threshold是RescueParty的一个静态内部类,通过它对进程的崩溃次数进行统计。
private abstract static class Threshold {
public abstract int getCount();
public abstract void setCount(int count);
public abstract long getStart();
public abstract void setStart(long start);
private final int uid;
private final int triggerCount;
private final long triggerWindow;
public Threshold(int uid, int triggerCount, long triggerWindow) {
this.uid = uid;
this.triggerCount = triggerCount;
this.triggerWindow = triggerWindow;
}
...
}
从上面的代码中我们可以总结出它的基本功能,每一个进程对应一个Threshold对象,通过uid进行标识,通过triggerCount统计崩溃次数,通过triggerWindow统计崩溃的时间边界,其余几个函数看名字就知道它们是用来计数和计时的。
Threshold是一个抽象类,它定义了需要实现的基本功能,对应于我们之前提到的两种触发事件,又定义了Threadshold的两个子类BootThreshold和AppThreshold来分别处理两种情况。
3.1 对象构造
上文说道Threadshold的子类BootThreshold负责处理system_server崩溃的情况,我们看一下它的初始化函数
public BootThreshold() {
super(android.os.Process.ROOT_UID, 5, 300 * DateUtils.SECOND_IN_MILLIS);
}
android.os.Process.ROOT_UID是zygote进程的UID,因为system_server重启必然会导致zygote进程重启,300s表示统计周期,5 是统计次数。
下面是APPThreshold的构造函数
public AppThreshold(int uid) {
super(uid, 5, 30 * DateUtils.SECOND_IN_MILLIS);
}
不同的进程UID不同,和system_server相比,统计周期编程了30s.
RescueParty 通过两个重要的成员对System_server和系统进程进行监控,在一开始就初始化好了。
private static final Threshold sBoot = new BootThreshold();
private static SparseArray<Threshold> sApps = new SparseArray<>();
3.2 system_server崩溃
RescuParty和其他的重要服务一样,同样是由System_server启动的,在System_server中有如下调用
RescueParty.noteBoot(mSystemContext);
看一下RescueParty的noteBoot到底做了什么
public static void noteBoot(Context context) {
if (isDisabled()) return;
if (sBoot.incrementAndTest()) {
sBoot.reset();
incrementRescueLevel(sBoot.uid);
executeRescueLevel(context);
}
}
上诉代码中**isDisabled()**函数用来判断是否被禁止,代码很简单
private static boolean isDisabled() {
if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) {
return false;
}
if (Build.IS_ENG) {
Slog.v(TAG, "Disabled because of eng build");
return true;
}
if (Build.IS_USERDEBUG && isUsbActive()) {
Slog.v(TAG, "Disabled because of active USB connection");
return true;
}
if (SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false)) {
Slog.v(TAG, "Disabled because of manual property");
return true;
}
return false;
}
主要就是检查PROP_DISABLE_RESCUE,eng版本,手机连接USB模式。
函数incrementRescueLevel()用来调整级别,每次调用+1,最高到LEVEL_FACTORY_RESET。下面开始看一下executeRescueLevel()函数是怎么开始救援操作的。
private static void executeRescueLevel(Context context) {
这里我只展示了主要函数,看得出救援工作主要在 executeRescueLevelInternal()函数中完成的
private static void executeRescueLevelInternal(Context context, int level) throws Exception { switch (level) { case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS); break; case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: resetAllSettings(context, Settings.RESET_MODE_UNTRUSTED_CHANGES); break; case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: resetAllSettings(context, Settings.RESET_MODE_TRUSTED_DEFAULTS); break; case LEVEL_FACTORY_RESET: RecoverySystem.rebootPromptAndWipeUserData(context, TAG); break; } }
从上面的代码可以看出,救援登记1-3通过更深入的重置Setting属性设置来实现,4等级即最高等级通过进入recovery,让客户重置data分区实现。
3.3 系统常驻进程崩溃
上面我们说道,应用进程是通过一个列表sApps进行管理的,每个进程对应一个AppThreshold对象。那么系统常驻进程崩溃时又是在哪里被调用的呢?
在AppErrors.java 文件中,有这样的调用
# frameworks/base/services/core/java/com/android/server/am/AppErrors.javavoid crashApplicationInner(ProcessRecord r, ... if (r != null && r.persistent) { RescueParty.notePersistentAppCrash(mContext, r.uid); } ...}
当进程崩溃时,会将进程的信息传送到RescueParty
public static void notePersistentAppCrash(Context context, int uid) { if (isDisabled()) return; Threshold t = sApps.get(uid); if (t == null) { t = new AppThreshold(uid); sApps.put(uid, t); } if (t.incrementAndTest()) { t.reset(); incrementRescueLevel(t.uid); executeRescueLevel(context); }}
如果该进程是第一次崩溃,那么会建立一个新的AppThreshold对象保存起来,否则就调整它的救援级别并执行救援动作。除了会在AppErrors中调用外,AMS在installSystemProvider时也会调用一次。
public final void installSystemProviders() {
最后,借网上的一张图看一下常驻系统App崩溃的处理流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GXjKwIh6-1631256203329)(/home/yxd0000/图片/App崩溃流程.png)]
4、总结
之前就遇到过进程频繁崩溃系统进入recovery 模式的情况,但是找不到是什么原因导致的,最终一位大佬找到了是这个原因,那么如何修改和关闭该机制呢?其实很简单,调整一下初始化函数和isDisable()函数就可以了。
参考文件:
https://www.jianshu.com/p/a143f36b7fe7Android 9源码
|