翻译自谷歌官方开发文档:
许多用户严重依赖他们的手机,并且始终需要一个可以工作的设备。 但是,有时设备最终会出现重启循环,这会导致用户提交支持票证或保修查询。
这个过程对用户来说是令人沮丧的,对设备制造商和运营商来说是昂贵的。Android 8.0 包含一项功能,当它注意到核心系统组件陷入崩溃循环时,它会启动“救援模式”,即Rescue Party。
然后通过一系列操作升级以恢复设备。 作为最后的手段,Rescue Party 将设备重新启动到恢复模式并提示用户执行恢复出厂设置。
而在进行framework开发时,很容易涉及到SystemServer或者系统应用,当这些应用或者系统连续发生崩溃时,系统可能会重启进入recovery。所以,当我们发现系统应用如SystemUI在连续crash多次后,系统重启进入recovery,我们就要联想到是不是救援模式导致的了。
救援模式是否禁用,主要受到两个persist属性的控制,即
persist.sys.enable_rescue //为1时enable救援模式
persist.sys.disable_rescue //为1时disable救援模式
从代码逻辑上来看,这两者是存在一些差别的,如果这两者都没有设置属性,那么救援模式isDisabled()的判断会跳过1,然后判断2 3 4 5,如果都没有进入里面,则会默认返回false,即救援模式是启动状态的。因此,如果需要保证救援模式启动,最好的方法是直接设置persist.sys.enable_rescue
为1,而要禁止救援模式则不能通过设置persist.sys.enable_rescue
为0,而是设置persist.sys.disable_rescue
为1.
private static boolean isDisabled() {
// Check if we're explicitly enabled for testing
1 if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) {
return false;
}
// We're disabled if the DeviceConfig disable flag is set to true.
// This is in case that an emergency rollback of the feature is needed.
2 if (SystemProperties.getBoolean(PROP_DEVICE_CONFIG_DISABLE_FLAG, false)) {
Slog.v(TAG, "Disabled because of DeviceConfig flag");
return true;
}
// We're disabled on all engineering devices
3 if (Build.IS_ENG) {
Slog.v(TAG, "Disabled because of eng build");
return true;
}
// We're disabled on userdebug devices connected over USB, since that's
// a decent signal that someone is actively trying to debug the device,
// or that it's in a lab environment.
4 if (Build.IS_USERDEBUG && isUsbActive()) {
Slog.v(TAG, "Disabled because of active USB connection");
return true;
}
// One last-ditch check
5 if (SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false)) {
Slog.v(TAG, "Disabled because of manual property");
return true;
}
return false;
}
而debug救援模式的方法,主要有SystemServer和SystemUI两个属性,分别代表系统崩溃和应用crash,即
adb shell setprop debug.crash_system 1 //使SystemServer循环崩溃
//当SystemServer在5分钟内重启5次以上,则触发一次救援模式
adb shell setprop debug.crash_sysui 1 //使SystemUI循环崩溃
//当SystemUI30秒内崩溃5次,则触发一次救援模式
救援模式每触发一次,就会将救援模式等级上升一级,当达到4级时,便会进入recovery。
在安卓11上救援模式的逻辑和之前有所不同,对于应用的crash监听引发的救援模式原理和流程,具体可以参考这篇文章,说的很清楚了https://blog.csdn.net/ChaoY1116/article/details/109642564
这里说明一下,救援模式中对应用崩溃的处理入口在RescueParty.java中的这一块代码,当我们在开发阶段时,可能并不希望救援模式随意就被开发中的应用崩溃触发,所以我们可以在入口处把isDisabled()的判断注释掉,使其永远不往下执行,这样就能在不影响SystemServer的前提下禁止掉对应用的救援模式触发。
@Override
public boolean execute(@Nullable VersionedPackage failedPackage,
@FailureReasons int failureReason) {
if (true/*isDisabled()*/) {//禁止对应用连续崩溃的救援模式执行
return false;
}
if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
|| failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
int triggerUid = getPackageUid(mContext, failedPackage.getPackageName());
incrementRescueLevel(triggerUid);
executeRescueLevel(mContext,
failedPackage == null ? null : failedPackage.getPackageName());
return true;
} else {
return false;
}
}
这里补充一下SystemServer的救援模式触发机制,
如果看了应用的崩溃触发救援模式的工作机制后,应该能知道,当应用崩溃后会调用PackageWatchdog中的onPackageFailure,从而执行由RescueParty实现的PackageHealthObserver的execute()方法,而SystemServer的救援模式机制也有些类似。
在SystemServer的startBootstrapServices()方法中,会在启动了RecoverySystemService后调用PackageWatchdog中的noteboot()方法,告诉它SystemServer启动了一次,当mBootThreshold.incrementAndTest()检测到SystemServer在5分钟内启动了5次,便会触发一次救援模式的逻辑。
SystemServer.java
// Now that we have the bare essentials of the OS up and running, take
// note that we just booted, which might send out a rescue party if
// we're stuck in a runtime restart loop.
RescueParty.registerHealthObserver(mSystemContext);
PackageWatchdog.getInstance(mSystemContext).noteBoot();
PackageWatchdog.java
/**
* Called when the system server boots. If the system server is detected to be in a boot loop,
* query each observer and perform the mitigation action with the lowest user impact.
*/
public void noteBoot() {
synchronized (mLock) {
if (mBootThreshold.incrementAndTest()) {
//如果5分钟内SystemServer重启了5次,便开始往下执行,并重置mBootThreshold
mBootThreshold.reset();
PackageHealthObserver currentObserverToNotify = null;
int currentObserverImpact = Integer.MAX_VALUE;
for (int i = 0; i < mAllObservers.size(); i++) {
final ObserverInternal observer = mAllObservers.valueAt(i);
PackageHealthObserver registeredObserver = observer.registeredObserver;
if (registeredObserver != null) {
int impact = registeredObserver.onBootLoop();
//调用RescueParty中的onBootLoop,如impact不为0且为整型,则调用RescueParty中的
//executeBootLoopMitigation()
if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
&& impact < currentObserverImpact) {
currentObserverToNotify = registeredObserver;
currentObserverImpact = impact;
}
}
}
if (currentObserverToNotify != null) {
currentObserverToNotify.executeBootLoopMitigation();
}
}
}
}
RescueParty.java
@Override
public int onBootLoop() {
if (isDisabled()) {
return PackageHealthObserverImpact.USER_IMPACT_NONE;
}
return mapRescueLevelToUserImpact(getNextRescueLevel());
}
/**
* Get the next rescue level. This indicates the next level of mitigation that may be taken.
*/
private static int getNextRescueLevel() {
//PROP_RESCUE_LEVEL默认为0,执行一次该方法,会返回PROP_RESCUE_LEVEL+1,但是取值在0到4之间
return MathUtils.constrain(SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1,
LEVEL_NONE, LEVEL_FACTORY_RESET);
}
private static int mapRescueLevelToUserImpact(int rescueLevel) {
switch(rescueLevel) {
case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
//0或者1都返回1
return PackageHealthObserverImpact.USER_IMPACT_LOW;
case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
case LEVEL_FACTORY_RESET:
return PackageHealthObserverImpact.USER_IMPACT_HIGH;
default:
return PackageHealthObserverImpact.USER_IMPACT_NONE;
}
}
此时,由于PackageWatchdog中的noteBoot中的判断满足了,所以会执行RescueParty.java中的executeBootLoopMitigation()
RescueParty.java
@Override
public boolean executeBootLoopMitigation() {
if (isDisabled()) {
return false;
}
incrementRescueLevel(Process.ROOT_UID);
executeRescueLevel(mContext, /*failedPackage=*/ null);
return true;
}
再后续的工作机制就和应用崩溃触发救援模式一样了,参考上面那篇文章即可。