AndroidManifest 合併衝突處理

0. 前言

AndroidStuido 採用模塊化構建工程的方式,每一個模塊配置一個 AndroidManifest.xml ,甚至每一個構建類型、產品特性均可以配置一個 AndroidManifest.xml。最終生成 apk 的時候,按照下圖指定的優先級進行合併處理。(圖片來源:Google 官方文檔)node

1. 優先級定義

上圖介紹了三種清單文件的合併流程,從左到右,優先級由低到高。android

1.1 構建變體清單文件

構建變體清單,主要包含兩種:buildType 和 productFlavor 。git

優先級由高到低依次是:github

  1. 產品特性(productFlavors)。
  2. 構建類型 (buildType)。
  3. 編譯變體(前二者組合)。
buildTypes {
        // 測試版本
        debug {
        }
        // 發行版本
        release {
        }
    }

    flavorDimensions 'stage', 'api'

    productFlavors {
        // 開發階段
        dev {
            dimension 'stage'
        }
        // 生產階段
        pro {
            dimension 'stage'
        }

        minApi21 {
            dimension 'api'
        }
        minApi23 {
            dimension 'api'
        }
        minApi26 {
            dimension 'api'
        }
    }
複製代碼

分別創建對應的編譯變體目錄和 AndroidManifest.xml 文件。api

以上面的配置爲例,優先級依次是(高到低的順序):app

dev -> minApi23 -> devMinApi23 -> debug -> devMinApi23Debugmaven

  • dev 高於 minApi23 是由於 flavorDimensions 聲明 stage 優先於 api 。
  • 不存在 devDebug 和 minApi23Debug 這樣的組合。

1.2 應用主模塊清單文件

主模塊中的清單文件,即 main 目錄下的 AndroidManifest.xml 文件。ide

1.3 庫模塊及依賴庫清單文件

依賴庫指依賴本地的 aar 文件或依賴遠程 maven 倉庫的 aar 文件。它們一般都包含一個 AndroidManifest.xml 文件。模塊化

jar 類型的三方庫,只有 class 文件,不在此討論範圍。工具

與主模塊相對立,庫模塊也能夠包含多個構建類型和產品特性。首先,它們先按照 構建變體清單文件 定義的優先級,合併出自身的一個 AndroidManifest.xml ,再做爲庫模塊的清單文件與主模塊的清單文件合併。

2. 隱式系統權限

早期 Android 版本中,應用能夠自由訪問的 API ,在新版本中受到系統權限的限制。爲兼容這些應用,新版本中會容許這些應用在無權限的狀況下訪問這些受限的 API 。

WRITE_EXTERNAL_STORAGE 最先出如今 Android API 4 中。那麼,當你應用的 targetSdkVersion 設置成小於 4 時,也能夠在無權限的狀況下訪問 外部存儲

在 AndroidManifest 合併中,若是優先級低的 Manifest 中 targetSdkVersion 小於 4,那麼在 合併後的 AndroidManifest 中,會自動添加這些隱式權限。

下面兩張圖是合併先後,app/AndrodManifest.xmllibrary/AndroidManifest.xml 的對比。

發現 合併前 app/AndrodManifest.xml 中未申明權限,可是合併後多了三條權限記錄。

優先級較低的清單聲明 向合併後的的清單添加的權限
targetSdkVersion <= 3 WRITE_EXTERNAL_STORAGE
READ_EXTERNAL_STORAGE
READ_PHONE_STATE
targetSdkVersion <= 15
且使用 READ_CONTACTS
READ_CALL_LOG
targetSdkVersion <= 15
且使用 WRITE_CONTACTS
WRITE_CALL_LOG

隱式系統權限主要爲兼容 Android 早起版本。在 9102 年及之後的開發中並很少見。所以,瞭解便可。

3. 合併規則

借用官方的一張圖,介紹了在合併過程當中,默認的一些合併方式。

以 dev 是高優先級,debug 是低優先級爲例:

<!-- dev 的 Manifest-->
<activity android:name="com.flueky.lib.TestActivity" android:exported="false" android:windowSoftInputMode="adjustPan">
    <meta-data android:name="dev_index" android:value="dev" />
</activity>
<!-- debug 的 Manifest-->
<activity android:name="com.flueky.lib.TestActivity" android:exported="false" android:screenOrientation="portrait">
    <meta-data android:name="debug_index" android:value="debug" />
</activity>   
複製代碼
  1. dev 沒有值,debug 值 B,合併後值 B,android:screenOrientation="portrait"
  2. dev 值 A,debug 沒有值,合併後值 A,android:windowSoftInputMode="adjustPan"
  3. dev 值 A,debug 值 A,合併後值 A,android:exported="false"

所以最終合併結果以下:

<!-- 合併後devDebug 的 Manifest-->
<activity android:name="com.flueky.lib.TestActivity" android:screenOrientation="portrait" android:exported="false" android:windowSoftInputMode="adjustPan">
    <meta-data android:name="dev_index" android:value="dev" />
    <meta-data android:name="debug_index" android:value="debug" />
</activity>  
複製代碼

上述提到的都屬性合併,可是 meta-data 標籤最終卻也合併了。標籤的合併,你們都熟悉的是 Activity 的合併。每一個模塊的 Activity 只須要在該模塊的 AndroidManifst.xml 註冊,不須要在主模塊中再次註冊,由於最終會將它們合併在一塊兒。

標籤合併使用匹配鍵做爲依據,activity 使用 android:name 做爲匹配件,meta-data 也是。匹配鍵的屬性值不一樣,便可無衝突合併。

下表列出,manifest 所有子標籤的匹配鍵,作解決合併衝突的參考。

標籤 合併策略 匹配鍵
action 合併 android:name 屬性
activity 合併 android:name 屬性
application 合併 每一個 manifest 只有一個
category 合併 android:name 屬性
data 合併 每一個intent-filter 只有一個
grant-uri-permission 合併 每一個 provider 只有一個
instrumentation 合併 android:name 屬性
intent-filter 保留 不匹配;容許父元素內的多個聲明
manifest 僅合併子元素 每一個文件只有一個
meta-data 合併 android:name 屬性
path-permission 合併 每一個 provider 只有一個
permission-group 合併 android:name 屬性
permission 合併 android:name 屬性
permission-tree 合併 android:name 屬性
provider 合併 android:name 屬性
receiver 合併 android:name 屬性
screen 合併 android:screenSize 屬性
service 合併 android:name 屬性
supports-gl-texture 合併 android:name 屬性
supports-screen 合併 每一個 manifest 只有一個
uses-configuration 合併 每一個 manifest 只有一個
uses-feature 合併 android:name 屬性
(若是不存在,則使用 android:glEsVersion 屬性)
uses-library 合併 android:name 屬性
uses-permission 合併 android:name 屬性
uses-sdk 合併 每一個 manifest 只有一個
自定義元素 合併 不匹配;合併工具並不知曉這些元素,
所以它們始終包含在合併後的清單中

Android 中經過標記的方式,在合併過程當中解決衝突。按照前面介紹的屬性合併和標籤合併,對應採用屬性標記和節點標記(標籤做爲節點)的方式人爲解決衝突。

須要使用命名空間 Android tools

xmlns:tools="http://schemas.android.com/tools"
複製代碼

4. 屬性標記

屬性標記有四個。默認標記、移除、替換、選擇器。

標記 做用
tools:strict 默認標記,無實際意義,須要使用 replace、remove 解決衝突
tools:replace 指定替換的屬性名字
tools:remove 指定移除的屬性名字
tools:selector 選擇指定模塊進行合併,結合 replace、remove 使用

這些例子比較簡單 ,借用下官方的示例代碼。

  1. replace 使用示例。
<!-- 低優先級 -->
<activity android:name="com.example.ActivityOne" android:theme="@oldtheme" android:exported="false" android:windowSoftInputMode="stateUnchanged">
<!-- 高優先級 -->    
<activity android:name="com.example.ActivityOne" android:theme="@newtheme" android:exported="true" android:screenOrientation="portrait" tools:replace="android:theme,android:exported">
<!-- 合併後 -->    
<activity android:name="com.example.ActivityOne" android:theme="@newtheme" android:exported="true" android:screenOrientation="portrait" android:windowSoftInputMode="stateUnchanged">            
複製代碼
  1. remove 使用示例。
<!-- 低優先級 -->
<activity android:name="com.example.ActivityOne" android:windowSoftInputMode="stateUnchanged">
<!-- 高優先級 -->
<activity android:name="com.example.ActivityOne" android:screenOrientation="portrait" tools:remove="android:windowSoftInputMode">
<!-- 合併後 -->    
<activity android:name="com.example.ActivityOne" android:screenOrientation="portrait">            
複製代碼
  1. selector 表示,只針對指定來源的低優先級 AndroidManifest.xml 進行合併處理。
<!-- 來源於 com.example.lib1 的低優先級 -->
<activity android:name="com.example.ActivityOne" android:windowSoftInputMode="stateUnchanged">
<!-- 高優先級 -->
<activity android:name="com.example.ActivityOne" android:screenOrientation="portrait" tools:remove="android:windowSoftInputMode" tools:selector="com.example.lib1">
<!-- 合併後 -->    
<activity android:name="com.example.ActivityOne" android:screenOrientation="portrait">            
複製代碼

5. 節點標記

節點標記使用 tools:node

屬性值 做用
merge 沒有發生衝突時合併標籤中全部屬性和全部嵌套元素,便是默認方式
merge-only-attributes 未正確驗證,估計已失效。
remove 合併後移除此標籤
removeAll 同 remove 相似,可是移除所有此標籤
replace 替換低優先級中相同的標籤
strict 使用時,遇到不匹配的標籤都致使合併失敗,須要使用上述屬性值解決

1. 下面的結果,即便默認方式合併後。
<!-- 合併後devDebug 的 Manifest-->
<activity android:name="com.flueky.lib.TestActivity" android:screenOrientation="portrait" android:exported="false" android:windowSoftInputMode="adjustPan">
    <meta-data android:name="dev_index" android:value="dev" />
    <meta-data android:name="debug_index" android:value="debug" />
</activity>  
複製代碼
  1. merge-only-attributes 與資料說明的不太同樣,未發現正確用途 。

  2. 使用 remove 後,移除匹配鍵相同的標籤。

<!-- 低優先級 -->
<activity
    android:name="com.flueky.lib.TestActivity"
    android:exported="false"
    android:screenOrientation="portrait">
    <meta-data
        android:name="debug_index"
        android:value="debug" />
</activity>
<!-- 高優先級 -->
<activity
<activity
    android:name="com.flueky.lib.TestActivity"
    android:windowSoftInputMode="adjustPan">
    <meta-data
        android:name="debug_index"
        tools:node="remove" />
</activity>
<!-- 合併後 -->
<activity
    android:name="com.flueky.lib.TestActivity"
    android:exported="false"
    android:windowSoftInputMode="adjustPan"
    android:screenOrientation="portrait">
</activity>    
複製代碼
  1. 使用 removeAll 後:
<!-- 低優先級 -->
<activity android:name="com.flueky.lib.TestActivity" android:exported="false" android:screenOrientation="portrait">
    <meta-data android:name="debug_index" android:value="debug" />
    <meta-data android:name="dev_index" android:value="dev" />    
</activity>
<!-- 高優先級 -->
<activity android:name="com.flueky.lib.TestActivity" android:windowSoftInputMode="adjustPan">
    <meta-data tools:node="removeAll" />
</activity>
<!-- 合併後 -->
<activity android:name="com.flueky.lib.TestActivity" android:exported="false" android:windowSoftInputMode="adjustPan" android:screenOrientation="portrait">
</activity>    
複製代碼

removeAll 移除所有相同標籤。

  1. 使用 replace 後,結果比較直觀。
<!-- 低優先級 -->
<activity android:name="com.flueky.lib.TestActivity" android:screenOrientation="portrait">
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
<!-- 高優先級 -->
<activity android:name="com.flueky.lib.TestActivity" android:windowSoftInputMode="adjustPan" tools:node="replace">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>   
<!-- 合併後 --> 
<activity android:name="com.flueky.lib.TestActivity" android:windowSoftInputMode="adjustPan">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>       
複製代碼

使用 replace 合併後,直接按照高優先級中的配置。

6. 常見問題

集成第三方庫時,遇到過最多的問題是:庫的最小 sdk 版本小於項目的最小 sdk 版本。此時集成第三方庫時一定出現問題。

解決方法:

<!-- com.flueky.library 便是第三方庫的 packageName -->
<uses-sdk tools:overrideLibrary="com.flueky.library" />
複製代碼

最後,偷偷的說下,AndroidStudio 3.5.2 版本支持直接看合併後的 AndroidManifest.xml 文件。

源碼地址

以爲有用?那打賞一個唄。去打賞

我的主頁已經更新 ,歡迎收藏flueky.github.io/

相關文章
相關標籤/搜索