我的GitHub | 我的博客 | 我的微信 | 我的郵箱 |
---|---|---|---|
baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
目錄
Merge AndroidManifest 合並清單文件
合並多個清單文件
Merge multiple manifest files
APK 文件只能包含一個 AndroidManifest.xml
文件,但 Android Studio 項目可以包含多個文件[may contain several provided by the main source set
, build variants
, and imported libraries
]。因此,在構建應用時,Gradle 構建會將所有清單文件合並到一個封裝到 APK 的清單文件中[merges all manifest files into a single manifest file that's packaged into your APK]。
清單合並工具通過遵循某些合並啟發式算法[merge heuristics],並遵守您通過特殊 XML 屬性定義的合並首選項[merge preferences],來合並各個文件中的所有 XML 元素 。 本頁介紹清單合並的工作方式以及如何應用合並首選項來解決合並沖突[resolve merge conflicts]。
提示: 使用 Merged Manifest 視圖預覽合並清單的效果並找出沖突錯誤。
合並優先級
Merge priorities
合並工具根據每個清單文件的優先級將所有清單文件按順序
合並到一個文件中。 例如,如果您有 3 個清單文件,則會先將優先級最低的清單合並到優先級第 2 高的清單中,然后再將合並后的清單合並到優先級最高的清單中(如圖 1 所示)。
有 3 種基本的清單文件可以互相合並,它們的合並優先級如下(按優先級由高到低的順序):
1、清單文件構建變體 Manifest file for your build variant
如果您的變體有多個源集,則其manifest優先級如下:
Build variant
manifest (如 src/demoDebug/)Build type
manifest (如 src/debug/)Product flavor
manifest (如 src/demo/)
如果您使用的是 flavor dimensions,清單優先級將與每個維度在 flavorDimensions 屬性中的列示順序(按優先級由高到低的順序排列)對應。
2、app模塊的主清單文件 Main manifest file for the app module
3、所包括庫中的清單文件 Manifest file from an included library
如果您有多個庫,則其清單優先級與依賴順序(庫出現在 dependencies 塊中的順序)匹配。
重要說明: build.gradle 文件中的構建配置將替換合並清單文件中的任何對應屬性。 例如,build.gradle 文件中的minSdkVersion 將替換
清單元素中的匹配屬性。為了避免混淆,您只需省去 元素並在 build.gradle 文件中定義這些屬性。For more details, see Configure Your Build.
合並沖突啟發式算法
Merge conflict heuristics
合並工具可以在邏輯上將一個清單中的每個 XML 元素與另一個清單中的對應元素相匹配。(有關匹配如何進行的詳細信息,請參閱有關 合並策略 的附錄)。
如果優先級較低的清單中的元素與優先級較高的清單中的任何元素均不匹配,則該元素將被添加至合並清單。 但是,如果有匹配元素,則合並工具會嘗試將其中的所有屬性合並到相同元素中。如果工具發現兩個清單包含相同屬性,但值不相同
,則會出現合並沖突。
下表 1 描述了合並工具嘗試將所有屬性合並到同一元素時可能出現的結果。
高優先級屬性 | 低優先級屬性 | 屬性的合並結果 |
---|---|---|
沒有值 | 沒有值 | 沒有值(使用默認值) |
沒有值 | 值 B | 值 B |
值 A | 沒有值 | 值 A |
值 A | 值 A | 值 A |
值 A | 值 B | 沖突錯誤—必須添加一個 合並規則標記 |
但是,在某些情況下,合並工具會采取其他行為方式以避免合並沖突:
<manifest>
元素中的屬性不合並--僅使用優先級最高的清單中的屬性
。<uses-feature>
和<uses-library>
元素中的android:required
屬性使用 OR 合並,因此如果出現沖突(值不一致),系統將應用 "true" 並始終包括某個清單所需的功能或庫[the feature or library required by one manifest is always included]。<uses-sdk>
元素始終使用優先級較高的清單中的值
,但以下情況除外:- 如果低優先級清單的
minSdkVersion
值較高,您必須應用overrideLibrary
合並規則[an error occurs unless you apply the overrideLibrary merge rule]。 - 如果低優先級清單的
targetSdkVersion
值較低,合並工具將使用高優先級清單中的值,但也會添加任何必要的系統權限
,以確保所導入的庫繼續正常工作(適用於較高的 Android 版本具有更多權限限制的情況)。 如需了解有關此行為的詳細信息,請參閱有關 隱式系統權限[implicit system permissions] 的部分。
- 如果低優先級清單的
- 絕不會在清單之間匹配
<intent-filter>
元素。每個元素都被視為唯一元素,並添加至合並清單中的常用父元素[the common parent element in the merged manifest]。
對於屬性之間的所有其他沖突,您將收到一則錯誤,並且必須通過在高優先級清單文件中添加特殊屬性
來指示合並工具如何解決此錯誤。
不要依賴於默認屬性值 Do not depend on default attribute values
由於所有唯一屬性[unique attributes]都合並到相同元素中,如果高優先級清單實際上依賴於屬性的默認值而不需要聲明,則可能會導致意外結果。
例如,如果高優先級清單不聲明android:launchMod
e 屬性,則會使用 "standard" 的默認值;但如果低優先級清單聲明此屬性具有其他值,該值將應用於合並清單(替代默認值)。
因此,您應該按期望明確定義每個屬性。(每個屬性的默認值都會記錄在 Manifest reference 中)。
合並規則的標記
Merge rule markers
合並規則標記是一個 XML 屬性,可用於表達您對關於如何解決合並沖突或刪除不需要的元素和屬性的首選項。您可以對整個元素或只對元素中的特定屬性應用標記。
合並兩個清單文件時,合並工具會在高優先級清單文件中尋找這些標記
。
所有標記均屬於 Android tools 命名空間,因此您必須先在 <manifest>
元素中聲明此命名空間,如下文所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp"
xmlns:tools="http://schemas.android.com/tools">
節點標記
Node markers
merge 合並所有
要向整個 XML 元素(給定清單元素中的所有元素及其所有子標記)應用合並規則,請使用以下屬性:
tools:node="merge"
如果使用合並沖突啟發式算法
時沒有沖突,則合並
此標記中的所有屬性以及所有嵌套元素
。這是元素的默認行為。
低優先級清單:
<activity android:name=”com.example.ActivityOne”
android:windowSoftInputMode=”stateUnchanged”>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
高優先級清單:
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”portrait”
tools:node="merge”>
</activity>
合並的清單結果:
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”portrait”
android:windowSoftInputMode=”stateUnchanged”>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
merge-only-attributes 僅合並屬性
僅合並此標記中的屬性,不合並嵌套元素
。
低優先級清單:
<activity android:name=”com.example.ActivityOne”
android:windowSoftInputMode=”stateUnchanged”>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<data android:type="image/*" />
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
高優先級清單:
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”portrait”
tools:node="merge-only-attributes”/>
合並的清單結果:
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”portrait”
android:windowSoftInputMode=”stateUnchanged”/>
replace 替換【常用】
完全替換低優先級元素。 也就是說,如果低優先級清單中有匹配元素,請將其忽略並完全按照其在此清單中顯示樣子來使用該元素。
低優先級清單:
<activity-alias android:name=”com.example.alias”>
<meta-data android:name=”cow” android:value=”@string/moo”/>
<meta-data android:name=”duck” android:value=”@string/quack”/>
</activity-alias>
高優先級清單:
<activity-alias android:name=”com.example.alias” tools:node=”replace”>
<meta-data android:name=”fox” android:value=”@string/dingeringeding”/>
</activity-alias>
合並的清單結果:
<activity-alias android:name=”com.example.alias”>
<meta-data android:name=”fox” android:value=”@string/dingeringeding”/>
</activity-alias>
remove 移除
從合並清單中刪除此元素。 盡管您似乎應該僅刪除此元素,但如果您發現合並清單中有不需要的元素,則必須使用此選項。該選項由不受您控制的低優先級清單(如導入的庫)提供。
低優先級清單:
<activity-alias android:name=”com.example.alias”>
<meta-data android:name=”cow” android:value=”@string/moo”/>
<meta-data android:name=”duck” android:value=”@string/quack”/>
</activity-alias>
高優先級清單:
<activity-alias android:name=”com.example.alias”>
<meta-data android:name=”cow” tools:node=”remove”/>
</activity-alias>
合並的清單結果:
<activity-alias android:name=”com.example.alias”>
<meta-data android:name=”duck” android:value=”@string/quack”/>
</activity-alias>
removeAll 移除所有
與 tools:node="remove"
類似,但它會刪除同一父元素內與此元素類型相匹配
的所有元素。
低優先級清單:
<activity-alias android:name=”com.example.alias”>
<meta-data android:name=”cow” android:value=”@string/moo”/>
<meta-data android:name=”duck” android:value=”@string/quack”/>
</activity-alias>
高優先級清單:
<activity-alias android:name=”com.example.alias”>
<meta-data tools:node=”removeAll”/>
</activity-alias>
合並的清單結果:
<activity-alias android:name=”com.example.alias”/>
strict 嚴格模式
當此元素在低優先級清單中的情況與在高優先級清單中的情況不完全匹配時生成構建故障(除非已通過其他合並規則標記解決)。 這將替換默認的合並沖突啟發式算法。 例如,如果低優先級清單僅包括額外屬性[an extra attribute],則構建將會失敗(而默認行為會向合並清單添加額外屬性)。
低優先級清單:
<activity android:name=”com.example.ActivityOne”
android:windowSoftInputMode=”stateUnchanged”>
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
高優先級清單:
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”portrait”
tools:node="strict”/>
這會生成清單合並錯誤。兩個清單元素在嚴格模式下也完全無法區分。 因此,您必須應用其他合並規則標記來解決這些差異。
屬性標記 Attribute markers
要改為僅向清單標記中的 特定屬性[specific attributes] 應用合並規則,請使用屬性標記。每個屬性接受一個或多個屬性名稱(包括屬性命名空間),並以逗號分隔。
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”/>
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”>
strict 嚴格模式
當這些屬性在低優先級清單中的情況與在高優先級清單中的不完全匹配時生成構建故障。 這是所有屬性的默認行為,具有 合並沖突啟發式算法中介紹的特殊行為的屬性除外。
低優先級清單:
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”landscape”/>
高優先級清單:
<activity android:name=”com.example.ActivityOne”
android:screenOrientation=”portrait”
tools:strict="android:screenOrientation”/>
這會生成清單合並錯誤。 您必須應用其他合並規則標記來解決沖突。
請謹記:這是默認行為,因此如果您刪除
tools:strict="screenOrientation”
,上面的示例將具有相同的結果。
同時使用多個屬性標記
您也可以對一個元素同時應用多個標記,如下所示。
低優先級清單:
<activity android:name=”com.example.ActivityOne”
android:theme=”@oldtheme”
android:exported=”false”
android:allowTaskReparenting="true"
android:windowSoftInputMode=”stateUnchanged”>
高優先級清單:
<activity android:name=”com.example.ActivityOne”
android:theme=”@newtheme”
android:exported=”true”
android:screenOrientation=”portrait”
tools:replace=”android:theme,android:exported”
tools:remove=”android:windowSoftInputMode”>
合並的清單結果:
<activity android:name=”com.example.ActivityOne”
android:theme=”@newtheme”
android:exported=”true”
android:allowTaskReparenting="true"
android:screenOrientation=”portrait”>
標記選擇器
Marker selector
如果您想僅對某個特定的導入庫
應用合並規則標記,請添加具有庫包名稱的 tools:selector
屬性。
例如,對於下面的清單,僅在低優先級清單文件來自 com.example.lib1
庫時應用 remove
合並規則。
<permission android:name="permissionOne"
tools:node="remove"
tools:selector="com.example.lib1">
如果低優先級清單來自其他源,系統將會忽略 remove
合並規則。
Tips:如果您將此功能與其中一個屬性標記配合使用,它將應用至標記中指定的所有選項。
替換導入庫的 uses-sdk
默認情況下,導入 minSdkVersion 值高於主清單文件的庫時會出錯,而且無法導入該庫。 要使合並工具忽略此沖突並導入庫,同時保持應用的低 minSdkVersion 值,請將 overrideLibrary 屬性添加至 <uses-sdk>
標記。屬性值可以是一個或多個庫包名稱(以逗號分隔),指明可能替換主清單的 minSdkVersion 的庫。
例如,如果應用的主清單按如下所示應用 overrideLibrary:
<uses-sdk android:targetSdkVersion="22"
android:minSdkVersion="2"
tools:overrideLibrary="com.example.lib1, com.example.lib2"/>
則下面這個清單(來自com.example.lib1)可以合並,並且不會出現與 <uses-sdk>
標記相關的錯誤,且合並清單將保留應用清單中的 minSdkVersion="2"
。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.lib1">
<uses-sdk android:minSdkVersion="4"/>
隱式系統權限
Implicit system permissions
在最近的 Android 版本中,應用曾經可以自由訪問的某些 Android API 已受 系統權限 限制。為了避免中斷預期會訪問這些 API 的應用[To avoid breaking apps that expect access to these API],最近的 Android 版本允許應用在無權限的情況下繼續訪問這些 API,前提是它們已將 targetSdkVersion
設置為低於添加限制的版本的值。此行為有效地向應用授予了_隱式權限_,以允許訪問 API。 因此,這可能會對具有不同 targetSdkVersion
值的合並清單產生以下影響。
如果低優先級清單文件提供隱式權限的 `targetSdkVersion` 值較低,而且高優先級清單_沒有_相同的隱式權限(由於其 `targetSdkVersion` 等於或高於添加限制的版本),合並工具將向合並清單_顯式_添加系統權限。
例如,如果您的應用將 targetSdkVersion
設置為 4 或更高值,並且導入了將 targetSdkVersion
設置為 3 或更低值的庫,合並工具會將 WRITE_EXTERNAL_STORAGE 權限添加至合並清單。 表 2 列出了可以添加至合並清單的所有可能權限。
注:如果您將應用的
targetSdkVersion
設置為 23 或更高值,則必須在應用嘗試訪問受這些權限保護的 API 時為任何危險權限執行運行時權限請求。 如需獲得更多指導,請參閱 使用系統權限。
表 2. 合並工具可添加至合並清單的權限列表
低優先級清單聲明 | 添加至合並清單的權限 |
---|---|
targetSdkVersion 是 3 或更低值 | WRITE_EXTERNAL_STORAGE, READ_PHONE_STATE |
targetSdkVersion 是 15 或更低值,並且使用 READ_CONTACTS | READ_CALL_LOG |
targetSdkVersion 是 15 或更低值,並且使用 WRITE_CONTACTS | WRITE_CALL_LOG |
檢查合並清單並查找沖突
Inspect the merged manifest and find conflicts
即使在構建 APK 之前,也可以預覽合並清單
,具體方法是:在 Android Studio 中打開您的 AndroidManifest.xml 文件,然后單擊編輯器底部的 Merged Manifest
選項卡。
Merged Manifest 視圖在左側顯示合並清單的結果
,在右側顯示每個合並清單文件的相關信息
。
從低優先級文件中合並的元素在左側以不同顏色突出顯示,每種顏色的關鍵字在右側的 Manifest Sources 下方指定。
屬於構建的一部分但不構成元素或屬性的清單文件列在右側的 Other Manifest Files 下方。
要查看有關元素來源
的信息,請在左側單擊元素,詳細信息將顯示在右側的 Merging Log 下方。
如果發生任何沖突,它們將顯示在右側的 Merging Errors 下方,並且包含有關如何使用合並規則標記
解決沖突的建議。錯誤也會打印在 Event Log 窗口中(請選擇 View > Tool Windows > Event Log)。
如果您想要查看合並決策樹
的完整日志,則可以在【各個模塊】的 build/outputs/logs/manifest-merger-【buildVariant】-report.txt
中查找該日志文件。
附錄:合並策略
Appendix: Merge policies
清單合並工具可以在邏輯上將某個清單中的每個 XML 元素與其他清單中的對應元素相匹配。 合並工具會使用匹配關鍵字[match key]
匹配每個元素,匹配關鍵字可以是唯一的屬性值(如 android:name
或標記本身的天然唯一性(例如,只能有一個 <supports-screen>
元素)。 如果兩個清單具有相同的 XML 元素,工具將采用三種合並策略中的一種來合並這兩個元素:
合並[Merge]
:將所有非沖突屬性合並到同一標記中,然后按其各自的合並策略合並子元素。 如果任何屬性相互沖突,請使用合並規則標記將它們合並在一起。僅合並子項[Merge children only]
:不整合或合並屬性(僅保留優先級最高的清單文件提供的屬性),並按照其合並策略合並子項。保留[Keep]
:將元素“按原樣”保留,然后將其添加至合並文件中的同一父元素。此策略僅在可接受相同元素的多個聲明時使用。
表 3. 清單元素合並策略和合並關鍵字 Manifest element merge policies and match keys
元素 | 合並策略 | 合並關鍵字 |
---|---|---|
<action> |
合並 | android:name 屬性 |
<activity> |
合並 | android:name 屬性 |
<application> |
合並 | 每個 <manifest> 僅一個 |
<category> |
合並 | android:name 屬性 |
<data> |
合並 | 每個 <intent-filter> 僅 1 個 |
<grant-uri-permission> |
合並 | 每個 <provider> 僅 1 個 |
<instrumentation> |
合並 | android:name 屬性 |
<intent-filter> |
保留 | 不匹配;允許父元素內的多個聲明 |
<manifest> |
合並 | 每個文件僅 1 個 |
<meta-data> |
合並 | android:name 屬性 |
<path-permission> |
合並 | 每個 <provider> 僅 1 個 |
<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> 僅 1 個 |
<uses-configuration> |
合並 | 每個 <manifest> 僅 1 個 |
<uses-feature> |
合並 | android:name 屬性(如果不存在,則使用 android:glEsVersion 屬性) |
<uses-library> |
合並 | android:name 屬性 |
<uses-permission> |
合並 | android:name 屬性 |
<uses-sdk> |
合並 | 每個 <manifest> 僅 1 個 |
自定義元素 | 合並 | 無匹配;合並工具不了解這些信息,因此它們始終包括在合並清單中 |
2019-6-29