[Android Framework]Android 11RescueParty(救援模式)工作机制

1.救援模式简介

翻译自谷歌官方开发文档:

许多用户严重依赖他们的手机,并且始终需要一个可以工作的设备。 但是,有时设备最终会出现重启循环,这会导致用户提交支持票证或保修查询。
这个过程对用户来说是令人沮丧的,对设备制造商和运营商来说是昂贵的。

Android 8.0 包含一项功能,当它注意到核心系统组件陷入崩溃循环时,它会启动“救援模式”,即Rescue Party。
然后通过一系列操作升级以恢复设备。 作为最后的手段,Rescue Party 将设备重新启动到恢复模式并提示用户执行恢复出厂设置。

而在进行framework开发时,很容易涉及到SystemServer或者系统应用,当这些应用或者系统连续发生崩溃时,系统可能会重启进入recovery。所以,当我们发现系统应用如SystemUI在连续crash多次后,系统重启进入recovery,我们就要联想到是不是救援模式导致的了。

2.救援模式机制

救援模式是否禁用,主要受到两个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的救援模式触发机制,

3.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;
        }

再后续的工作机制就和应用崩溃触发救援模式一样了,参考上面那篇文章即可。

  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值