AGP 的日常 #7 - debuggable 属性无效

问题回顾

不久前,在接手手头上这个老工程时,发现 build.gradle 中设置 debuggable 属性是无效的,只能手动在 AndroidManifest.xmlapplication 节点写死该属性(之前团队里就这样默默干了两年,发版前去掉这个属性,发版后再加上…),最近得空花了两三个周末研究了下缘由。

默认看此文章的人已经知道这点:

Android 系统判断一个 App 是否可 Debug 的标准是,AndroidManifest.xml 中的 application 节点是否存在 debuggable 属性,并且其值为 true。(可参考官方文档

问题分析 & 源码重现

0x01

buildTypedebuggable 属性开始分析追踪,既然我们知道 debuggable 其实会作用于 Manifest 的属性,那就找找跟 Manifest 相关的流程。先后看了 Android Gradle Plugin 中 process{variant}Manifestprocess{variant}Resources package{variant} 等 task 的流程,没有发现什么异常(此处不一一展开)。不过倒是对他们的中间生成产物有了些了解:

除此之外,没看到有直接对 AndroidManifest.xml 中写入 debuggable 的操作(merge 除外)。

0x02

拿一个新建的工程和问题工程作比对发现:

看来还得再翻翻 process{variant}Resources,这其中主要是调用 aapt 相关的操作,仔细查看发现如下关键代码:


// AaptV1.java 

@Override
@NonNull
protected ProcessInfoBuilder makePackageProcessBuilder(@NonNull AaptPackageConfig config)
        throws AaptException {
    ProcessInfoBuilder builder = new ProcessInfoBuilder();

    /*
     * AaptPackageProcessBuilder had this code below, but nothing was ever added to
     * mEnvironment.
     */
    //builder.addEnvironments(mEnvironment);

    builder.setExecutable(getAaptExecutablePath());
    builder.addArgs("package");

    ...(省略无关紧要的一部分代码)
    
    // 在此处注入了 buildType 的 debuggable 属性
    if (config.isDebuggable()) {
        builder.addArgs("--debug-mode");
    }

    ...

    return builder;
}

可以看到,在处理 aapt 的输入参数时带上了 debug 参数,随即便是调用 aapt 的外部过程,不在 Android Gradle Plugin 的控制范围内。

0x03

找出 Android SDK 源码,编译一个自己的 aapt(具体可以参考这里),别忘了在关键地方加上一些 log:

if (strcmp(cp, "-debug-mode") == 0) {
    fprintf(stderr, "AAPTDEBUG: set debug == true");
    bundle.setDebugMode(true);
}
status_t massageManifest(Bundle* bundle, sp<XMLNode> root)
    {
    root = root->searchElement(String16(), String16("manifest"));
    if (root == NULL) {
        fprintf(stderr, "No <manifest> tag.\n");
        return UNKNOWN_ERROR;
    }
    
    // ...
    
    fprintf(stderr, "AAPTDEBUG: bundle->getDebugMode()");
    if (bundle->getDebugMode()) {
        fprintf(stderr, "AAPTDEBUG: bundle->getDebugMode() - true");
        sp<XMLNode> application = root->getChildElement(String16(), String16("application"));
        if (application == NULL) {
            fprintf(stderr, "AAPTDEBUG: application == NULL"); // 问题出处,打印出了这行 log
        }
        if (application != NULL) {
            fprintf(stderr, "AAPTDEBUG: application != NULL");
            if (!addTagAttribute(application, RESOURCES_ANDROID_NAMESPACE, "debuggable", "true",
                        errorOnFailedInsert)) {
                    fprintf(stderr, "AAPTDEBUG: error on insert");
                return UNKNOWN_ERROR;
            }
        }
    }
    
    // ...
}
sp<XMLNode> XMLNode::getChildElement(const String16& tagNamespace, const String16& tagName)
    {
    printf("AAPTDEBUG: root -> %s\n", String8(mElementName).string());
    for (size_t i=0; i<mChildren.size(); i++) {
        sp<XMLNode> child = mChildren.itemAt(i);
        if (child->getType() == XMLNode::TYPE_ELEMENT) {
            printf("AAPTDEBUG: getChildElement-> %s\n", String8(child->mElementName).string());
        }
    
        if (child->getType() == XMLNode::TYPE_ELEMENT
                && child->mNamespaceUri == tagNamespace
                && child->mElementName == tagName) {
            return child;
        }
    }
    
    return NULL;
}

0x04

查看各类依赖库的 Manifest(build-cache/exploded-aar),查看 Manifest 的合并日志(app/build/outputs/logs/manifest-merger-{variant}-report),结合之前写的一个 Manifest Precheck 插件 Seal,发现会碰到这个问题,有两种可能:

  1. 依赖库本身对 namespace 的声明不只在 manifest 节点,例如在 application 节点声明了 android 的 namespace,可以参考我 pub 的这个 DebuggableTest 工程;
  2. 对 Manifest 合并前(即执行 process{variant}Manifest 前),如果有例如 Seal 这种对依赖库的 Manifest 做清洗工作,并且很巧你也用了 groovy.util 下的 XML 类库做 XML 解析和输出,那么恭喜你,这个库有几率会导致你的各种节点出现重复的 namespace,比如 uses-sdk 、application,不过我还没认真去排查到底什么情况下会出现这样的问题,目前实验中也只有少量的样本会这样,暂时没发现他们的共通点。

解决方案

结论

这大概是我今年研究过的最麻烦的问题了,链路长,大坑小坑不断,有 Google 挖的,有 Groovy 挖的,目前正在去给他们提 issue 的路上…

铛!更新 issue 地址: https://issuetracker.google.com/issues/66074488

· AGP 日常, AGP Daily, Android Gradle Plugin, 安卓打包插件, 安卓, Android, 中文