目录

AGP BuildTools 之间的关系

​发现一个有趣的问题:

今天同事突然问我:“你看这个什么情况,我没使用 resGuard 相关的资源混淆的东西啊,打出来的包为什么资源被混淆了?”

因为他们在做 AGP7.0 的升级,AGP 的升级往往又带着各种 buildTools 的升级,而 aapt2 又是 buildTools 的一部分,那我肯定是往这方面怀疑。

因为我们之前有接入 resGuard,所以同事一直在怀疑是 resGuard 导致的

于是他表示,应该不能

/img/in-post/conversion.png

那只好拿出证据来实锤了!

一些线索

Android 的编译其实是 很复杂的,AGP 自带的各种 task,各种 buildtools 工具,加上 R8(D8 老早就已经开始计算 maindex,并且新版本的混淆路径更短了似乎) 等等,巨量源码肯定是无从查起的

如果涉及 native 层的编译,那就更复杂了,ndk 各个版本之间的区别,谁又知道呢···

这个时候,同事经过了一个简单的 AGP7.0 demo 发现了这肯定是个系统行为,于是开始搜索有关 resource 的一些配置

最终发现,android.enableResourceOptimizations 这样一个配置

那就有了切入点了!

源码探索

enableResourceOptimizations

初步尝试 enableResourceOptimizations = false,发现资源不会被混淆了

那么切入点就有了,在茫茫源码中寻找 enableResourceOptimizations

发现其来自于一个 BooleanOptions 的类,对应枚举值为 ENABLE_RESOURCE_OPTIMIZATIONS

ENABLE_RESOURCE_OPTIMIZATIONS

经过搜索 ENABLE_RESOURCE_OPTIMIZATIONS,发现一个 PackageApplication 的类

其中有部分代码如下(AGP 4.2.0):

// com.android.build.gradle.tasks.PackageApplication
 override fun handleProvider(
            taskProvider: TaskProvider<PackageApplication>
        ) {
            super.handleProvider(taskProvider)
            creationConfig.taskContainer.packageAndroidTask = taskProvider
            val useOptimizedResources = !creationConfig.debuggable &&
                    !creationConfig.variantType.isForTesting &&
                    creationConfig.services.projectOptions[BooleanOption.ENABLE_RESOURCE_OPTIMIZATIONS]
            val operationRequest = creationConfig.artifacts.use(taskProvider)
                    .wiredWithDirectories(
                            PackageAndroidArtifact::getResourceFiles,
                            PackageApplication::getOutputDirectory)

            transformationRequest = when {
                useOptimizedResources -> operationRequest.toTransformMany(
                        InternalArtifactType.OPTIMIZED_PROCESSED_RES,
                        ArtifactType.APK,
                        outputDirectory.absolutePath)
                useResourceShrinker -> operationRequest.toTransformMany(
                        InternalArtifactType.SHRUNK_PROCESSED_RES,
                        ArtifactType.APK,
                        outputDirectory.absolutePath)
                else -> operationRequest.toTransformMany(
                        InternalArtifactType.PROCESSED_RES,
                        ArtifactType.APK,
                        outputDirectory.absolutePath)
            }

于是发现了 ENABLE_RESOURCE_OPTIMIZATIONS 开关的值,决定了 OPTIMIZED_PROCESSED_RES 的值,那么继续搜索 OPTIMIZED_PROCESSED_RES

OPTIMIZED_PROCESSED_RES

一顿搜索后,发现一个强相关的类名 OptimizeResourcesTask

此类中还存在 aapt2 参数枚举

// com.android.build.gradle.internal.tasks.OptimizeResourcesTask
enum class AAPT2OptimizeFlags(val flag: String) {
    COLLAPSE_RESOURCE_NAMES("--collapse-resource-names"),
    SHORTEN_RESOURCE_PATHS("--shorten-resource-paths"),
    ENABLE_SPARSE_ENCODING("--enable-sparse-encoding")
}

再看 aapt2 源码

//frameworks/base/tools/aapt2/cmd/Optimize.cpp
 if (options_.shorten_resource_paths) {
      ResourcePathShortener shortener(options_.table_flattener_options.shortened_path_map);
      if (!shortener.Consume(context_, apk->GetResourceTable())) {
        context_->GetDiagnostics()->Error(DiagMessage() << "failed shortening resource paths");
        return 1;
      }
      if (options_.shortened_paths_map_path
          && !WriteShortenedPathsMap(options_.table_flattener_options.shortened_path_map,
                                      options_.shortened_paths_map_path.value())) {
        context_->GetDiagnostics()->Error(DiagMessage()
                                          << "failed to write shortened resource paths to file");
        return 1;
      }
    }

配上 commit 信息,应该就是这里了

Resource Path Obfuscation

This CL allows aapt2 to obfuscate resource paths within the output apk
and move resources to shorter obfuscated paths. This reduces apk size
when there is a large number of resources since the path metadata exists
in 4 places in the apk.

This CL adds two arguments to aapt2, one to enable resource path
obfuscation and one to point to a path to output the path map to (for
later debugging).

Test: make aapt2_tests
Bug: b/75965637

Change-Id: I9cacafe1d17800d673566b2d61b0b88f3fb8d60c

且搜索源码后发现,此 Task 只在 AGP >= 4.1.0 版本以上存在

且 AGP >= 4.2.0 以后,optimizeFlags 默认包含了 SHORTEN_RESOURCE_PATHS

那么到此,基本可以断定,在 AGP 4.2.0 时就有了资源混淆的功能

通过验证简单 demo 验证,也正是如此

AGP 和 AAPT2 的关系

通过上面的源码,我们也发现了 AAPT2 在某个版本以后才添加了 shorten_resources_paths 相关的代码

那么 AGP 肯定要和 AAPT2 的版本匹配,如果不匹配,假如 AGP 是 4.2.0 使用了混淆参数,但是 aapt2 中并没有处理相关参数的逻辑,这不是乱了套了?!

又因为 AAPT2 属于 BuildTools

那么转而就成了 AGP 和 BuildTools 的关系

查阅 Android Gradle Plugin Release Notes 如下:

/img/in-post/agp4.1.jpg

/img/in-post/agp4.2.jpg

/img/in-post/agp7.0.jpg

综合以上所有信息

AGP >= 4.2.0 版本,要求 BuildTools >= 30.0.2,此时资源默认情况下自动混淆

那么 AGP 4.1.0 行不行呢?我尝试指定 BuildTools = 30.0.2,确保 AAPT2 中的优化代码存在,但是最终依然没有混淆。

对比 4.1.0 和 4.2.0 的 OptimizeResourcesTask 发现略有区别,猜测可能 4.1.0 并不是默认开启,但是应该是可以通过一些参数达到混淆的目的的,因为代码层面上是有留接口在的

总结

Android 编译过程中,AGP 属于一个主控制流的角色,其中包含了许多 task,有些 task 它自己源码中直接搞定,而有些则需要对应的工具搞定,比如 aapt2 就是其中之一,当然还有 R8 等等,那么就需要做到各个工具的版本适配。

这么复杂的编译链,出点 bug 可太容易了。虽然确实也存在一些 bug,但是似乎没听过有致命 bug 的存在,所以 google 工程师还是相对严谨的?