2BAB's Engineering Blog

Gradle 日常崩(4)AAR 的 tools:replace 冲突

最近做一些 SDK 升级时,有些包引入后会有诸如此类的报错:

AndroidManifest.xml:22:9-40 Error:
Attribute application@theme value=(@style/AppTheme) from AndroidManifest.xml:22:9-40
is also present at [some:libraries:version] AndroidManifest.xml:9:18-62 value=(@style/AnotherTheme).
Suggestion: add ‘tools:replace=”android:theme”‘ to element at AndroidManifest.xml:18:5-65:19 to override.

这是一个很常见的错误了,照着提示做 replace 就 OK 了。但是当我加上 replace 的代码后,发现依旧报错:

Multiple entries with same key: @android:theme=REPLACE and android:theme=REPLACE.

百思不得其解,查看了一下依赖库的 AndroidManifest.xml 源码,发现它也设置了tools:replace="android:theme",而 Manifest Merger 把这个视为冲突抛了出来。

思考

如果只是跟着 官方的 Manifest Merge,这个问题恐怕无解。StackOverflow 上也有人问过这个问题,但是没有更多的解法回复。

为什么依赖库会想不开去设置 replace 属性呢?很大的一个可能是:他也碰到了他的依赖库和他的 Manifest 有冲突的情况。那么我们能做什么?我们始终还是想要把他的某些属性给替换掉的(theme/allowBackup/…),不管他是出于什么样的目的,都不能阻止我想打出包的心!

解法

通过简单的观察和源码查看,我们发现 merge 是发生在 process${variant}Manifest 这个 Task。那么就得想办法在执行这个任务之前 Precheck 一下所有依赖的 AndroidManifest.xml

Seal - A gradle plugin to do precheck of Android Manifest.

我写了一个简易的插件来做这件事,目前支持两个功能:

  1. 删除 Application 节点的某些属性,如 debuggablethemeallowBackup
  2. 删除 Application 节点中 tools:replace 属性的某些值,如 android:iconandroid:themeandroid:allowBackup

这个插件不仅能解决上述提到的问题,还能顺带修复诸如下面这种 Warning:

Warning: AndroidManifest.xml already defines debuggable (in http://schemas.android.com/apk/res/android); using existing value in manifest.

而我们所需要做的,仅仅是指定我们不需要 libraries 的那些属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def projectRoot = project.getRootProject().rootDir.absolutePath
// 依赖库的 Manifest 文件搜索路径
// 1. Gradle plugin 2.3.0 或者更高版本,会默认开启 build-cache 功能,Release 版本的库会解压到这里
// 2. 但是我们同样需要对 SNAPSHOT 的库做预检查,所以还需要加入 exploded-aar 的目录
// 3. 有更多自定义的目录或者 module,请自行添加
def manifestPath = [
// for AAR of Release
// see note below
projectRoot + '/build-cache',
// for AAR of SNAPSHOT
projectRoot + '/app/build/intermediates/exploded-aar'
]
def removeAttrs = [
'android:debuggable'
]
def replaceValues = [
'android:allowBackup'
]
seal {
enabled = true
manifests = manifestPath
appAttrs {
enabled = true
attrsShouldRemove = removeAttrs
}
appReplaceValues {
enabled = true
valuesShouldRemove = replaceValues
}
}

需要注意的是,如果开启了 build-cache, Seal 建议你把 build-cache 的文件夹放在工程目录内(就是上面配置里的 build-cache 位置)。

1
2
3
//gradle.properties
android.buildCacheDir=./build-cache
...

更多信息,请参考 Github 仓库内的说明,欢迎大家提 PR 和 ISSUE。

讨论请发邮件到 xx2bab@gmail.com
自由转载-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0