安卓開(kāi)發(fā)使用 Gradle 插件管理依賴(lài)包確實(shí)非常方便,開(kāi)發(fā)中,你可能還會(huì)遇到一種情況,就是項(xiàng)目所引用的 AAR 、Library 等第三方庫(kù)所包含的 Manifest 清單文件與主 Module (默認(rèn)名為 app )中定義的 Manifest 內(nèi)容合并時(shí)發(fā)生沖突。
比如在項(xiàng)目中引用的某個(gè) Library 或者M(jìn)odule的 AndroidManifest 文件中,application 標(biāo)簽中內(nèi)容如下:
<application
android:theme="@android:style/Theme.Black"/>
其中的 android:theme 屬性在我們的 app module 主工程的 AndroidManifest 文件中也被定義:
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
并且二者所使用的值不同。這樣,在編譯的時(shí)候就會(huì)發(fā)生合并沖突,錯(cuò)誤信息如下:
Error:Execution failed for task ':app:processDebugManifest'.
> Manifest merger failed with multiple errors, see logs
通過(guò)點(diǎn)擊 Messages 菜單左側(cè)【Show Console Output】選項(xiàng)可以打開(kāi) Gradle Console 控制臺(tái)查看錯(cuò)誤日志和對(duì)應(yīng)的解決方案:


如上圖所示,Library 與 主 Module 在合并 Manifest 時(shí)發(fā)生錯(cuò)誤。其中還包含看到具體錯(cuò)誤信息,注明了是 android:theme 屬性發(fā)生沖突。并且給出了建議的解決方案,使用 tools:replace 方式解決沖突。
我們就按照錯(cuò)誤提示在主 Module 的 Manifest 文件中添加這行設(shè)置試試看:(注意需要聲明 tools 命名空間)
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.xxx.xxx">
并且在 app module 中的 Manifest 內(nèi)容的 tools:replace 屬性也做了一些修改
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:theme">
如此這樣,再次 Build 時(shí)便能編譯通過(guò)。其實(shí)原理就是,借助 tools 域名空間設(shè)置 Manifest 的合并優(yōu)先級(jí)問(wèn)題。這里 tools:place 表明合并時(shí)移除低優(yōu)先級(jí) Library 中的相關(guān)屬性,使用高優(yōu)先級(jí) app module 中定義的對(duì)應(yīng)屬性?xún)?nèi)容。
有關(guān) Manifest 合并相關(guān)的知識(shí),在開(kāi)發(fā)者官網(wǎng)上介紹得非常清楚,大家可以訪(fǎng)問(wèn)如下鏈接:
像上面這種情況,如果我們腦洞再開(kāi)大一點(diǎn),假設(shè)這個(gè) Library 也使用了 tools:replace 屬性會(huì)發(fā)生什么情況呢。我們不妨試驗(yàn)一下。修改 Library 的 Manifest 內(nèi)容:
<application
android:theme="@android:style/Theme.Black"
tools:replace="android:theme"/>
而此時(shí) app module 中的 Manifest 內(nèi)容的 tools:replace 屬性也做了一些修改:
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:allowBackup, android:theme">
這里,我故意設(shè)置二者的 tools:replace 屬性為不同值。Build 一下,看看結(jié)果:
Error:Execution failed for task ':app:processDebugManifest'.
> Multiple entries with same key: android:theme=REPLACE and android:theme=REPLACE
如我們所想,合并時(shí)發(fā)生沖突。然而,痛苦的是這種情況下,編譯器也無(wú)解,無(wú)法給出相應(yīng)解決方案!


當(dāng)然,這種情況很極端,但也不是沒(méi)有出現(xiàn)的可能。第三方庫(kù)和我們的 App Module 都想使用自己的屬性,也在情理之中。那么怎么辦呢,如果是本地 Library 依賴(lài)方式的話(huà),還可以手動(dòng)修改 Library 的 Manifest 內(nèi)容。但是如果是遠(yuǎn)程依賴(lài)的 AAR的話(huà),我們是改不了的啊。
這個(gè)時(shí)候,我們就得想辦法在合并的時(shí)候自動(dòng)刪除 Library 的 Manifest 內(nèi)容。但是很幸運(yùn) GitHub網(wǎng)站有一個(gè)插件,能夠幫助我們實(shí)現(xiàn)這個(gè)功能。先上地址:
https://github.com/2BAB/Seal
這個(gè)插件可以幫助我們做到這些:
1、刪除 Application 節(jié)點(diǎn)中的指定屬性;
2、刪除 Application 節(jié)點(diǎn)中 tools:replace 屬性的指定值。
這里我們還用上面的例子,介紹一下 Seal 插件的使用方式。
首先在項(xiàng)目根目錄下的 build.gradle 文件中設(shè)置 Seal 插件的地址:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath 'me.xx2bab.gradle:seal-manifest-precheck-plugin:1.0.0'
}
}
然后在 app module 的 build.gradle 文件中引用這個(gè)插件:
apply plugin: 'com.android.application'
apply plugin: 'seal'
接著還是修改這個(gè) build.gradle 文件,配置合并時(shí)的刪除規(guī)則:
def projectRoot = project.getRootProject().rootDir.absolutePath
// Folders may include AndroidManifest.xml files
// 1. For gradle plugin 2.3.0 or higher, build-cache is default choice,
// 2. But we should make sure snapshot-libs will be checked too.
// 3. Free to add your folders for more customization
def manifestPath = [
// for AAR of Release
// see note below
projectRoot + '/build-cache',
projectRoot + '/samplelibrary',
// for AAR of SNAPSHOT
projectRoot + '/app/build/intermediates/exploded-aar'
]
def removeAttrs = [
'android:theme'
]
def replaceValues = [
'android:theme'
]
seal {
enabled = true
manifests = manifestPath
appAttrs {
enabled = true
attrsShouldRemove = removeAttrs
}
appReplaceValues {
enabled = true
valuesShouldRemove = replaceValues
}
}
注意,在 manifestPath 配置下添加自己項(xiàng)目引入并發(fā)生沖突的 Library 名字,例子中使用的是 samplelibrary。同時(shí)在
removeAttrs 和 replaceValues 配置下添加對(duì)應(yīng)沖突的屬性名字,例子中沖突的是 android:theme 屬性。
還有一點(diǎn)需要注意的是,如果 Gradle 插件開(kāi)啟了 build-cache 功能(Gradle 插件 2.3 版本開(kāi)始默認(rèn)開(kāi)啟),還需要在項(xiàng)目根目錄下的 gradle.properties 文件中添加如下內(nèi)容:
android.buildCacheDir=./build-cache
我們?cè)俅?Build 工程,就能成功編譯通過(guò)啦。
當(dāng)然,真實(shí)項(xiàng)目中 Manifest 合并時(shí)能遇到 tools 規(guī)則沖突的情況并不多見(jiàn),而更多的是,普通屬性的使用沖突。不過(guò),還是值得注意一下,以備不時(shí)之需。