SickWorm的博客

Android Target API 35 + AGP 7.2.2 编译适配

Android, Android 编译  ·  

1. 背景

海外应用需要及时适配 Google Play 的升级要求。Google Play 要求 2025 年 8 月 31 日后上架和更新的应用必须声明 target API 35(可延期至 11 月 1 日)。

对于 target API 35 的适配,无论是内置 build-tools 版本还是 native 库 16KB Page size 的最佳支持,都是 AGP 8.X 才适配的。但 AGP 8.X 升级需要迁移所有的插桩实现到新 API,本身工作量也不小。叠加 native 库 16KB 适配工作量,就有点扛不住了。

所以我决定先尝试在 AGP 7.2.2 上快速适配 target API 35,如果行得通就不升级了。

最终花了大半天完成,下面记录了 AGP 7.2.2 版本适配 Target API 35 遇到的问题和解决方案。

2. 编译问题和解决方案

2.1 Bitmap.getConfig() 注解变更导致编译失败

Android 35 中 Bitmap.getConfig() 的注解从 @NonNull 变为 @Nullable。注释提到:

If the bitmap's internal config is in one of the public formats, return that config, otherwise return null.

意思就是如果是不识别的 config 值就返回 null。

这里也没去调研哪些值是不识别的,简单兜底了一个 ARGB_8888。

Bitmap newBitmap = Bitmap.createBitmap(width, height, bitmap.getConfig() == null ? Bitmap.Config.ARGB_8888 : bitmap.getConfig());

2.2 AAPT2 资源链接失败

AGP 7.2.2 升级 Target API 到 35 后,编译资源会出现以下错误:

Android resource linking failed
aapt2 E 07-11 12:37:36  5974 11940609 LoadedArsc.cpp:94] RES_TABLE_TYPE_TYPE entry offsets overlap actual entry data.
aapt2 E 07-11 12:37:36  5974 11940609 ApkAssets.cpp:149] Failed to load resources table in APK '/Users/sickworm/Library/Android/sdk/platforms/android-35/android.jar'.
error: failed to load include path /Users/sickworm/Library/Android/sdk/platforms/android-35/android.jar.

原因是 AGP 7.2.2 内置的 build-tools 是 30.0.3,升级到 AGP 8.8 以上内置 35 版本就不会报错了。但升级成本又高,能不能直接设置呢?

AGP 和 build-tools 对应版本见:https://developer.android.com/build/releases/past-releases/agp-7-2-0-release-notes

image-1.png

解决方案

首先尝试设置 buildToolsVersion "35.0.1",发现不行。从网上找到的解决方案是,通过 gradle.properties 强制指定使用 SDK 中的 AAPT2 版本:

# gradle.properties
android.aapt2FromMavenOverride=/Users/sickworm/Library/Android/sdk/build-tools/35.0.1/aapt2

实测可解决。但: 这个是本地路径,且需要写在 gradle.properties。每个人的路径不一样,对 git 提交非常不友好。

官方提供了几个方式设置 gradle.properties:

Pasted Graphic 4.png 比较通用的就是在开发机的 GRADLE_HOME 或者 GRADLE_USER_HOME 配置一个 gradle.properites。 但还是需要让每个开发都配置一次。

那么有没有无需手动配置的方案呢?(经典的自问自答)

有,鼓捣出来了:fixTarget35.gradle。整体思路是,先找到 AGP 引用 android.aapt2FromMavenOverride 的地方,然后分析整个数据传递流程哪里可以 hook 这个值。下面额外分享一下找到这个解决方案的过程,不感兴趣的同学看可以直接跳过。

实现 fixTarget35.gradle 的思路

  1. 找到 com.android.tools.build:gradle:7.2.2 的 jar 包。如果工程有 buildSrc 并且声明了依赖,就可以在 External Libraries 里找到: 
  2. 右键 gradle-7.2.2.jar 跳转到文件浏览器,找到这个 jar 包。然后用 jadx 反编译,全局搜索 android.aapt2FromMavenOverride 找到引用的地方: image-3.png
  3. 继续定位,最终可以找到 Aapt2FromMaven 这个类引用了这个属性 image-4.png
  4. 这个时候不要继续在 jadx 忙活了,切回工程,双击 Shift 找到 Aapt2FromMaven 的源码,打上断点: image-5.png
  5. 右上角运行配置不要选 app,选择 gradle 命令,然后点击 debug 调试运行,就可以成功断点到我们设置的地方: image-6.png
  6. 可以看到数据是从 ProjectOptions 读的,此时看整个调用链,分析 ProjectOptions 是怎么创建和读写的。发现它是从 ProjectOptionService 读取的,而 ProjectOptionService 是一个全局单例:
    ProjectOptionService optionService = new ProjectOptionService.RegistrationAction(project).execute().get();
    
    ProjectOptions projectOptions = optionService.getProjectOptions();
    
  7. 能拿到同一个对象就好办了。不同角度找一下姿势,找到一个方便运行时 hook 的地方反射设置一下就行。细节都在代码里了 fixTarget35.gradle

2.3 R8 编译失败

适配以上两个问题后,Android Studio 可以运行起来了。但如果工程有命令行编译的场景,如 CI 编译,云编译,Jugg 增量编译,那么会遇到 R8 编译失败,类似这样的错误:

com.android.tools.r8.CompilationFailedException: Compilation failed to complete, origin: /Users/sickworm/Library/Android/sdk/platforms/android-35/android.jar:java/lang/Boolean.class

A failure occurred while executing com.android.build gradle.internal. tasks.DexFileDẹpendençiesTask$DexFileDependenÇjesWorkerAction
> Error while dexing.

ERROR: /Users/sickwom/Library/Android/sdk/platforms/android-35/android.jar: D8: java.lang.NullPointerException

这里应该是 AGP 7.2.2 内置的 R8 版本 3.2.74 与 Android 35 SDK 存在兼容性问题,然后 Android Studio 运行可能用了某些魔法适配了正确的 R8 版本。

针对这个问题,网上的解决方案是指定 R8 版本:

buildscript {
    repositories {
        maven {
            url = uri("https://storage.googleapis.com/r8-releases/raw")
        }
    }
    dependencies {
        classpath 'com.android.tools:r8:8.4.27'
    }
}

实测可行。重要:如果项目使用了 buildSrc,也需要在 buildSrc/build.gradle 中声明:

// buildSrc/build.gradle
repositories {
    maven {
        url = uri("https://storage.googleapis.com/r8-releases/raw")
    }
}
dependencies {
    implementation 'com.android.tools:r8:8.4.27'
}

附:验证 R8 版本的方法

在 build.gradle 中添加以下任务来验证当前使用的 R8 版本:

tasks.register("r8Version") {
    doLast {
        println(com.android.tools.r8.Version.getVersionString())
    }
}

运行验证:

./gradlew r8Version

4. 参考资料

# # #