通过调试framework代码来解决奇怪的问题

前言

因为对于应用层来说,很多的API等于是黑盒,所以往往会发生一些始料未及的,甚至是十分奇怪的问题。如果只是在应用侧进行尝试,无疑是大海捞针,十分费力。这个时候如果能够灵活地把Android的开源特性利用起来,在Framework层面,在Server层面调查问题,就可以快速地定位问题,解决问题,我认为这可以说是一种“降维打击”的战术。下面用一个例子来说明我的实践方法。文章有点长,文字比较朴实,希望你能耐心读完,我认为对你应该会有一些启发。

问题描述

在进行项目的测试时发现一个切换Display Size的case无法通过。经过分析是因为Activity重建导致的,但原来已经指定了如下属性:

android:configChanges="orientation|screenSize|screenLayout|density"

这个并没有任何修改,于是猜测是osv变更导致的,因为我们修改了应用的target sdk到Q,于是写TP测试。
奇怪的事情发生了!TP表现出了跟原来O版本相同的行为,即Activity没有重建,而是调用了onConfigurationChanged方法。

调查过程

首先调试有问题的重建的Activity的onDestory方法,通过方法调用栈。看到了这个方法:

    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
            }

虽然没有详细研究过代码,但猜测framework需要根据系统真实的configChange来跟Activity设定的configChange的值进行比较来决定是否重建Activity。所以就需要找到比较的地方。

通过对上述代码中的configChanges的追踪(反复ctrl+左键,查找引用),找到了如下代码:

com/android/server/wm/ActivityRecord.java
//一度以为是forceNewConfig导致的,最终发现不是。
if (shouldRelaunchLocked(changes, mTmpConfig) || forceNewConfig) {
            // Aha, the activity isn't handling the change, so DIE DIE DIE.
            configChangeFlags |= changes;
            startFreezingScreenLocked(app, globalChanges);
            forceNewConfig = false;
            preserveWindow &= isResizeOnlyChange(changes);
            final boolean hasResizeChange = hasResizeChange(changes & ~info.getRealConfigChanged());
            if (hasResizeChange) {
                final boolean isDragResizing =
                        getTaskRecord().getTask().isDragResizing();
                mRelaunchReason = isDragResizing ? RELAUNCH_REASON_FREE_RESIZE
                        : RELAUNCH_REASON_WINDOWING_MODE_RESIZE;
            } else {
                mRelaunchReason = RELAUNCH_REASON_NONE;
            }
            //省略部分代码,下面就是执行重建Activity的调用
}
    private boolean shouldRelaunchLocked(int changes, Configuration changesConfig) {
        int configChanged = info.getRealConfigChanged();
        boolean onlyVrUiModeChanged = onlyVrUiModeChanged(changes, changesConfig);
        if (appInfo.targetSdkVersion < O
                && requestedVrComponent != null
                && onlyVrUiModeChanged) {
            configChanged |= CONFIG_UI_MODE;
        }
		//很明显比较是否需要重建,当任意一个FLAG,Activity不包含就执行重建。
        return (changes&(~configChanged)) != 0;
    }

于是在上面的位置Debug,之后发现下面的数据:

realchange
重建的App5507 CONFIG_DENSITY CONFIG_SCREEN_SIZE CONFIG_SCREEN_LAYOUT CONFIG_ORIENTATION CONFIG_MCC CONFIG_MNC6400 CONFIG_DENSITY CONFIG_SMALLEST_SCREEN_SIZE CONFIG_SCREEN_LAYOUT
没有重建的App55074352 CONFIG_DENSITY CONFIG_SCREEN_LAYOUT

这证明就是changes不同导致的,重建的情形多了一个CONFIG_SMALLEST_SCREEN_SIZE,那么为什么两个Activity的changes会不同呢,注意这里不是RealConfigChanged不同,RealConfigChanged是Activity自己通过Manifest定义的,changes是WMS根据实际的变化生产的。
这时继续向上追溯,看是什么导致了两的Activity的changes不同。然后就发现了下面的代码:

    private int getConfigurationChanges(Configuration lastReportedConfig) {
        final Configuration currentConfig = getConfiguration();
        int changes = lastReportedConfig.diff(currentConfig);
        // We don't want to use size changes if they don't cross boundaries that are important to
        // the app.
        if ((changes & CONFIG_SCREEN_SIZE) != 0) {
            final boolean crosses = crossesHorizontalSizeThreshold(lastReportedConfig.screenWidthDp,
                    currentConfig.screenWidthDp)
                    || crossesVerticalSizeThreshold(lastReportedConfig.screenHeightDp,
                    currentConfig.screenHeightDp);
            if (!crosses) {
                changes &= ~CONFIG_SCREEN_SIZE;
            }
        }
        if ((changes & CONFIG_SMALLEST_SCREEN_SIZE) != 0) {
            final int oldSmallest = lastReportedConfig.smallestScreenWidthDp;
            final int newSmallest = currentConfig.smallestScreenWidthDp;
            if (!crossesSmallestSizeThreshold(oldSmallest, newSmallest)) {
            //这里看到changes去除了CONFIG_SMALLEST_SCREEN_SIZE属性
                changes &= ~CONFIG_SMALLEST_SCREEN_SIZE;
            }
        }
        // We don't want window configuration to cause relaunches.
        if ((changes & CONFIG_WINDOW_CONFIGURATION) != 0) {
            changes &= ~CONFIG_WINDOW_CONFIGURATION;
        }

        return changes;
    }
   private boolean crossesSmallestSizeThreshold(int firstDp, int secondDp) {
        return crossesSizeThreshold(mSmallestSizeConfigurations, firstDp, secondDp);
    }
    //这个方法由上个方法调用,判断是否需要去除CONFIG_SMALLEST_SCREEN_SIZE
    private static boolean crossesSizeThreshold(int[] thresholds, int firstDp,
            int secondDp) {
        if (thresholds == null) {
            return false;
        }
        for (int i = thresholds.length - 1; i >= 0; i--) {
            final int threshold = thresholds[i];
            //判断是否在两个值之间,本例中firstDp和secondDp是320,376
            if ((firstDp < threshold && secondDp >= threshold)
                    || (firstDp >= threshold && secondDp < threshold)) {
        		//返回true表示不去除,返回false表示去除
                return true;
            }
        }
        return false;
    }

经过debug两个AcitvityRecord的mSmallestSizeConfigurations不同,重建的包含两个数据360、600,而没有重建的只有600。因为360在320和376之间,所以返回了true,不去除属性CONFIG_SMALLEST_SCREEN_SIZE。
这个时候我就有了一个猜测,这个360、600对应资源文件夹的sw360,sw600,经过反编译两个apk证实了我的猜测。然后我想到了本次版本升级加入了androidx.preference,原来没有使用support库,多出来的sw360和sw600就是这个库引入的。

后记

至此问题得解,可以看出这个问题很怪,怪到我会写一篇博文记录下来。如果只是在App侧进行分析测试无疑会非常困难,但从framework侧进行debug和分析却能比较简单的找到答案。当然工作中这样的问题有很多,记录下这个,希望对看到这篇博文的人有一些帮助或者启发。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值