有關專案依賴包發生 Manifest Merge 衝突的詳細解決方案

亦楓發表於2017-07-11

安卓開發使用 Gradle 外掛管理依賴包確實非常方便,尤其是在解決一些依賴衝突的問題上。比如,重複依賴的問題,具體內容請我之前寫的一篇文章:

開發中,你可能還會遇到一種情況,就是專案所引用的 AAR 、Library 等第三方庫所包含的 Manifest 清單檔案與主 Module (預設名為 app )中定義的 Manifest 內容合併時發生衝突。

舉個例子。比如在專案中引用的某個 Library 的 AndroidManifest 檔案中,application 標籤中內容如下:

<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">複製程式碼

並且二者所使用的值不同。這樣,在編譯的時候就會發生合併衝突,錯誤資訊如下:

Error:Execution failed for task ':app:processDebugManifest'.
> Manifest merger failed with multiple errors, see logs複製程式碼

通過點選 Messages 選單左側【Show Console Output】選項可以開啟 Gradle Console 控制檯檢視錯誤日誌和對應的解決方案:

如上圖所示,Library 與 主 Module 在合併 Manifest 時發生錯誤。其中還包含看到具體錯誤資訊,註明了是 android:theme 屬性發生衝突。並且給出了建議的解決方案,使用 tools:replace 方式解決衝突。

我們就按照錯誤提示在主 Module 的 Manifest 檔案中新增這行設定試試看:(注意需要宣告 tools 名稱空間)

<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 時便能編譯通過。其實原理就是,藉助 tools 域名空間設定 Manifest 的合併優先順序問題。這裡 tools:place 表明合併時移除低優先順序 Library 中的相關屬性,使用高優先順序 app module 中定義的對應屬性內容。

有關 Manifest 合併相關的知識,在開發者官網上介紹得非常清楚,大家可以訪問如下連結:

你以為這樣就結束了嗎,非也,還有一種極端情況。像上面這種情況,如果我們腦洞再開大一點,假設這個 Library 也使用了 tools:replace 屬性會發生什麼情況呢。我們不妨試驗一下。修改 Library 的 Manifest 內容:

<application 
    android:theme="@android:style/Theme.Black"
    tools:replace="android:theme"/>複製程式碼

而此時 app module 中的 Manifest 內容的 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">複製程式碼

這裡,我故意設定二者的 tools:replace 屬性為不同值。Build 一下,看看結果:

Error:Execution failed for task ':app:processDebugManifest'.
> Multiple entries with same key: android:theme=REPLACE and android:theme=REPLACE複製程式碼

如我們所想,合併時發生衝突。然而,痛苦的是這種情況下,編譯器也無解,無法給出相應解決方案!

當然,這種情況很極端,但也不是沒有出現的可能。第三方庫和我們的 App Module 都想使用自己的屬性,也在情理之中。那麼怎麼辦呢,如果是本地 Library 依賴方式的話,還可以手動修改 Library 的 Manifest 內容。但是如果是遠端依賴的 AAR的話,我們是改不了的啊。

這個時候,我們就得想辦法在合併的時候自動刪除 Library 的 Manifest 內容。偉大的 GitHub網站有一個外掛,能夠幫助我們實現這個功能。先上地址:

這個外掛可以幫助我們做到這些:

  1. 刪除 Application 節點中的指定屬性;
  2. 刪除 Application 節點中 tools:replace 屬性的指定值。

這裡我們還用上面的例子,介紹一下 Seal 外掛的使用方式。

首先在專案根目錄下的 build.gradle 檔案中設定 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 檔案中引用這個外掛:

apply plugin: 'com.android.application'
apply plugin: 'seal'複製程式碼

接著還是修改這個 build.gradle 檔案,配置合併時的刪除規則:

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 配置下新增自己專案引入併發生衝突的 Library 名字,例子中使用的是 samplelibrary。同時在
removeAttrs 和 replaceValues 配置下新增對應衝突的屬性名字,例子中衝突的是 android:theme 屬性。

還有一點需要注意的是,如果 Gradle 外掛開啟了 build-cache 功能(Gradle 外掛 2.3 版本開始預設開啟),還需要在專案根目錄下的 gradle.properties 檔案中新增如下內容:

android.buildCacheDir=./build-cache複製程式碼

這些工作都做完之後,我們再次 Build 工程,就能成功編譯通過啦。

當然,真實專案中 Manifest 合併時能遇到 tools 規則衝突的情況並不多見,而更多的是,普通屬性的使用衝突。不過,還是值得注意一下,以備不時之需。

關於我:亦楓,部落格地址:yifeng.studio/,新浪微博:IT亦楓

微信掃描二維碼,歡迎關注我的個人公眾號:安卓筆記俠

不僅分享我的原創技術文章,還有程式設計師的職場遐想

相關文章