0. 前言
AndroidStuido 採用模塊化構建工程的方式,每個模塊配置一個 AndroidManifest.xml ,甚至每個構建類型、產品特性都可以配置一個 AndroidManifest.xml。最終生成 apk 的時候,按照下圖指定的優先級進行合併處理。(圖片來源:Google 官方文檔)
1. 優先級定義
上圖介紹了三種清單文件的合併流程,從左到右,優先級由低到高。
1.1 構建變體清單文件
構建變體清單,主要包含兩種:buildType 和 productFlavor 。
優先級由高到低依次是:
- 產品特性(productFlavors)。
- 構建類型 (buildType)。
- 編譯變體(前兩者組合)。
buildTypes {
// 測試版本
debug {
}
// 發行版本
release {
}
}
flavorDimensions 'stage', 'api'
productFlavors {
// 開發階段
dev {
dimension 'stage'
}
// 生產階段
pro {
dimension 'stage'
}
minApi21 {
dimension 'api'
}
minApi23 {
dimension 'api'
}
minApi26 {
dimension 'api'
}
}
分別建立對應的編譯變體目錄和 AndroidManifest.xml 文件。
以上面的配置爲例,優先級依次是(高到低的順序):
dev -> minApi23 -> devMinApi23 -> debug -> devMinApi23Debug
- dev 高於 minApi23 是因爲 flavorDimensions 聲明 stage 優先於 api 。
- 不存在 devDebug 和 minApi23Debug 這樣的組合。
1.2 應用主模塊清單文件
主模塊中的清單文件,即 main
目錄下的 AndroidManifest.xml 文件。
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.xml 和 library/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>
- dev 沒有值,debug 值 B,合併後值 B,android:screenOrientation=“portrait”。
- dev 值 A,debug 沒有值,合併後值 A,android:windowSoftInputMode=“adjustPan”。
- 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 使用 |
這些例子比較簡單 ,借用下官方的示例代碼。
- 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">
- 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">
- 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>
-
merge-only-attributes 與資料說明的不太一樣,未發現正確用途 。
-
使用 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>
- 使用 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 移除全部相同標籤。
- 使用 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 文件。
覺得有用?那打賞一個唄。去打賞
個人主頁已經更新 ,歡迎收藏https://flueky.github.io/。