AndroidStuido 採用模塊化構建工程的方式,每一個模塊配置一個 AndroidManifest.xml ,甚至每一個構建類型、產品特性均可以配置一個 AndroidManifest.xml。最終生成 apk 的時候,按照下圖指定的優先級進行合併處理。(圖片來源:Google 官方文檔)node
上圖介紹了三種清單文件的合併流程,從左到右,優先級由低到高。android
構建變體清單,主要包含兩種:buildType 和 productFlavor 。git
優先級由高到低依次是:github
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
主模塊中的清單文件,即 main
目錄下的 AndroidManifest.xml 文件。ide
依賴庫指依賴本地的 aar
文件或依賴遠程 maven 倉庫的 aar
文件。它們一般都包含一個 AndroidManifest.xml 文件。模塊化
jar 類型的三方庫,只有 class 文件,不在此討論範圍。工具
與主模塊相對立,庫模塊也能夠包含多個構建類型和產品特性。首先,它們先按照 構建變體清單文件 定義的優先級,合併出自身的一個 AndroidManifest.xml ,再做爲庫模塊的清單文件與主模塊的清單文件合併。
早期 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 年及之後的開發中並很少見。所以,瞭解便可。
借用官方的一張圖,介紹了在合併過程當中,默認的一些合併方式。
以 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>
複製代碼
所以最終合併結果以下:
<!-- 合併後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"
複製代碼
屬性標記有四個。默認標記、移除、替換、選擇器。
標記 | 做用 |
---|---|
tools:strict | 默認標記,無實際意義,須要使用 replace、remove 解決衝突 |
tools:replace | 指定替換的屬性名字 |
tools:remove | 指定移除的屬性名字 |
tools:selector | 選擇指定模塊進行合併,結合 replace、remove 使用 |
這些例子比較簡單 ,借用下官方的示例代碼。
<!-- 低優先級 -->
<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">
複製代碼
<!-- 低優先級 -->
<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">
複製代碼
<!-- 來源於 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">
複製代碼
節點標記使用 tools:node 。
屬性值 | 做用 |
---|---|
merge | 沒有發生衝突時合併標籤中全部屬性和全部嵌套元素,便是默認方式 |
merge-only-attributes | 未正確驗證,估計已失效。 |
remove | 合併後移除此標籤 |
removeAll | 同 remove 相似,可是移除所有此標籤 |
replace | 替換低優先級中相同的標籤 |
strict | 使用時,遇到不匹配的標籤都致使合併失敗,須要使用上述屬性值解決 |
<!-- 合併後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>
複製代碼
<!-- 低優先級 -->
<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 移除所有相同標籤。
<!-- 低優先級 -->
<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 合併後,直接按照高優先級中的配置。
集成第三方庫時,遇到過最多的問題是:庫的最小 sdk 版本小於項目的最小 sdk 版本。此時集成第三方庫時一定出現問題。
解決方法:
<!-- com.flueky.library 便是第三方庫的 packageName -->
<uses-sdk tools:overrideLibrary="com.flueky.library" />
複製代碼
最後,偷偷的說下,AndroidStudio 3.5.2 版本支持直接看合併後的 AndroidManifest.xml 文件。
以爲有用?那打賞一個唄。去打賞
我的主頁已經更新 ,歡迎收藏flueky.github.io/。