管理資訊清單檔案

本頁說明資訊清單合併作業的運作方式,以及如何套用合併偏好設定來解決合併衝突。如需應用程式資訊清單檔案的簡介,請參閱「應用程式資訊清單總覽」。

合併多個資訊清單檔案

您的 APK 或 Android App Bundle 檔案只能含有一個 AndroidManifest.xml 檔案,但您的 Android Studio 專案可能會包含多個資訊清單檔案,這些檔案來自主要來源集、建構變數和匯入的程式庫。建構應用程式時,Gradle 建構作業會將所有資訊清單檔案合併為一個資訊清單檔案,並封裝到應用程式套件中。

資訊清單合併工具會依照合併經驗法則運作,並遵循您以特殊 XML 屬性定義的合併偏好設定,結合各個檔案中的所有 XML 元素。

提示:使用下方這一節說明的「Merged Manifest」檢視畫面,即可預覽合併的資訊清單結果,並找出衝突錯誤。

合併優先順序

合併工具會根據每個資訊清單檔案的優先順序,依序將所有資訊清單檔案結合為單一檔案。舉例來說,假設您有三個資訊清單檔案,系統會將優先順序最低的資訊清單合併至優先順序第二高的資訊清單,然後再合併到優先順序最高的資訊清單,如圖 1 所示。

圖 1 合併三個資訊清單檔案的過程,由最低優先順序合併至最高優先順序。

可能互相合併的資訊清單檔案有三種基本類型,以下是這些類型的合併優先順序 (第一個列出者優先順序最高):

  1. 建構變數的資訊清單檔案

    如果變數有多個來源集,資訊清單優先順序如下:

    • 建構變數資訊清單 (例如 src/demoDebug/)
    • 建構類型資訊清單 (例如 src/debug/)
    • 變種版本資訊清單 (例如 src/demo/)

      如果您使用版本維度,則資訊清單的優先順序會與每個維度在 flavorDimensions 屬性中列出的順序對應 (第一個列出者優先順序最高)。

  2. 應用程式模組的主要資訊清單檔案
  3. 所含程式庫中的資訊清單檔案

    如有多個程式庫,當中的資訊清單優先順序會與 Gradle dependencies 區塊內的顯示順序相符。

舉例來說,程式庫資訊清單會合併至主要資訊清單,然後主要資訊清單會合併至建構變數資訊清單。請注意,根據「使用來源集進行建構」中的說明,所有來源集的合併優先順序與上述合併優先順序都相同。

重要事項:build.gradle 檔案中的建構設定會覆寫合併的資訊清單檔案內任何對應的屬性。舉例來說,build.gradlebuild.gradle.kts 檔案中的 minSdk 會覆寫 <uses-sdk> 資訊清單元素中相符的屬性。為避免造成混淆,請捨棄 <uses-sdk> 元素,並且只在 build.gradle 檔案中定義這些屬性。詳情請參閱「建構設定」。

合併衝突經驗法則

合併工具可以按照邏輯,比對某份資訊清單中的每個 XML 元素與另一份資訊清單中的對應元素。如要進一步瞭解比對運作方式,請參閱上一節的「合併優先順序」。

如果較低優先順序資訊清單中的元素並未與較高優先順序資訊清單中的任何元素相符,那麼該元素會新增至合併的資訊清單。不過,如果有相符的元素,合併工具會嘗試將這兩個元素的所有屬性合併到同一個元素中。如果這個工具發現兩份資訊清單都包含相同屬性,但屬性值不同,就會發生合併衝突。

表 1 說明合併工具嘗試將所有屬性結合到相同元素中的可能結果。

表 1:屬性值的預設合併行為

高優先順序屬性 低優先順序屬性 屬性合併結果
沒有任何值 沒有任何值 沒有任何值 (使用預設值)
值 B 值 B
值 A 沒有任何值 值 A
值 A 值 A
值 B 衝突錯誤:您必須新增合併規則標記

不過,為了避免合併衝突,合併工具在某些情況下會出現不同的行為:

  • <manifest> 元素中的屬性絕不會合併,系統只會使用最高優先順序資訊清單中的屬性。
  • <uses-feature> <uses-library> 元素中的 android:required 屬性會使用 OR 合併。如果發生衝突,系統會套用 "true",並一律納入資訊清單所需的功能或程式庫。
  • <uses-sdk> 元素中的屬性一律會使用較高優先順序資訊清單的值,但下列情況除外:
    • 如果較低優先順序資訊清單的 minSdk 值較高,除非您套用 overrideLibrary 合併規則,否則就會發生錯誤。
    • 如果較低優先順序資訊清單的 targetSdkVersion 值較低,則合併工具會使用較高優先順序資訊清單的值,也會新增任何必要的系統權限,確保匯入的程式庫持續正常運作 (因應較新 Android 版本具有更多權限限制的情況)。如要進一步瞭解這項行為,請參閱「隱性系統權限」一節。
  • 資訊清單之間的 <intent-filter> 元素一律不相符。系統會將每個元素都視為不重複,並新增至合併資訊清單中的共同父項元素。

如果屬性之間發生任何其他衝突,您會收到錯誤訊息,而且必須在優先順序較高的資訊清單檔案中加入特殊屬性,指示合併工具如何解決問題。請參閱下一節「合併規則標記」一節。

請勿依附於預設屬性值。由於所有不重複屬性都會結合到同一個元素中,因此如果優先順序較高的資訊清單確實依附於某個屬性的預設值,但未宣告該屬性,就可能導致非預期的結果。舉例來說,優先順序較高的資訊清單如未宣告 android:launchMode 屬性,會使用 "standard" 的預設值,但如果優先順序較低的資訊清單以其他值宣告此屬性,那麼這個值會套用到合併的資訊清單,並覆寫預設值。因此,請務必依需求明確定義每個屬性。如要瞭解各項屬性的預設值,請參閱資訊清單參考資料

合併規則標記

「合併規則標記」是一個 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">

節點標記

如要將合併規則套用至整個 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>
tools:node="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>

合併的資訊清單結果:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    android:windowSoftInputMode="stateUnchanged">
</activity>
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 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>
tools:node="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">
</activity-alias>
tools:node="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>
tools:node="strict"
只要較低優先順序資訊清單中的這個元素與較高優先順序資訊清單中的元素不完全相符,除非透過其他合併規則標記解決這項問題,否則就會產生建構失敗的錯誤。這會覆寫合併衝突經驗法則。舉例來說,如果優先順序較低的資訊清單包含額外屬性,建構作業就會失敗,不過預設行為是將額外屬性加入合併的資訊清單。

低優先順序資訊清單:

<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">
</activity>

這會造成資訊清單合併錯誤。在嚴格模式中,兩個資訊清單元素必須完全相同。您必須套用其他合併規則標記才能解決這些差異 (沒有 tools:node="strict" 時,這兩個檔案可以合併,而且不會發生錯誤,如 tools:node="merge" 的例子所示)。

屬性標記

如果只想將合併規則套用至資訊清單標記中的特定屬性,請使用下列屬性。每個屬性都接受一或多個屬性名稱 (包括屬性命名空間),並以半形逗號分隔。

tools:remove="attr, ..."
從合併的資訊清單中移除指定屬性。如果優先順序較低的資訊清單檔案包含這些屬性,而且您想確保這些屬性不會併入合併的資訊清單,即可使用這個標記。

低優先順序資訊清單:

<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">
tools:replace="attr, ..."
將較低優先順序資訊清單中指定的屬性替換成這份資訊清單中的屬性。換句話說,一律保留較高優先順序資訊清單的值。

低優先順序資訊清單:

<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">
tools:strict="attr, ..."
只要較低優先順序資訊清單中的這些屬性與較高優先順序資訊清單中的屬性不完全相符,就會產生建構失敗的錯誤。這是所有屬性的預設行為,但「合併衝突經驗法則」一節中所述具有特殊行為的屬性除外。

低優先順序資訊清單:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="landscape">
</activity>

高優先順序資訊清單:

<activity android:name="com.example.ActivityOne"
    android:screenOrientation="portrait"
    tools:strict="android:screenOrientation">
</activity>

這會造成資訊清單合併錯誤。您必須套用其他合併規則標記才能解決衝突。這是預設行為,因此即使明確加入 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">

標記選取器

如果只想將合併規則標記套用至匯入的特定程式庫,請新增含有程式庫套件名稱的 tools:selector 屬性。

舉例來說,根據下列資訊清單,只有在優先順序較低的資訊清單檔案來自 com.example.lib1 程式庫時,系統才會套用 remove 合併規則:

<permission android:name="permissionOne"
    tools:node="remove"
    tools:selector="com.example.lib1">

如果優先順序較低的資訊清單來自任何其他來源,系統會忽略 remove 合併規則。

注意:如果您搭配其中一個屬性標記使用這項屬性,這項屬性會套用至標記中指定的所有屬性。

針對匯入的程式庫覆寫 <uses-sdk>

根據預設,所匯入程式庫的 minSdk 值大於主要資訊清單檔案的值時,就會發生錯誤,導致無法匯入程式庫。

如要讓合併工具忽略這項衝突並匯入程式庫,同時保留應用程式的較低 minSdk 值,請將 overrideLibrary 屬性新增至 <uses-sdk> 標記。屬性值可以是一或多個程式庫套件名稱 (以半形逗號分隔),指出哪些程式庫可以覆寫主要資訊清單的 minSdk

舉例來說,如果應用程式的主要資訊清單套用 overrideLibrary,如下所示:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.app"
          xmlns:tools="http://schemas.android.com/tools">
  <uses-sdk tools:overrideLibrary="com.example.lib1, com.example.lib2"/>
...

然後,系統就可以合併下列資訊清單,而不會出現與 <uses-sdk> 標記有關的錯誤,而合併的資訊清單會保留應用程式資訊清單中的 minSdk="2"

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.lib1">
   <uses-sdk android:minSdk="4" />
...

隱性系統權限

有些 Android API 原本開放應用程式自由存取,但在較新 Android 版本中受到系統權限限制。

為避免造成預期存取這些 API 的應用程式無法運作,較新 Android 版本允許應用程式在未獲得權限的情況下繼續存取這些 API,前提是必須將 targetSdkVersion 的值設為低於已添加限制的版本。這項行為可讓應用程式獲得隱性權限,進而存取 API。如果合併的資訊清單包含不同的 targetSdkVersion 值,就可能受到影響。

若優先順序較低的資訊清單檔案含有較低的 targetSdkVersion 值,因而獲得隱性權限,但優先順序較高的資訊清單沒有相同的隱性權限 (因為 targetSdkVersion 值等於或高於已添加限制的版本),那麼合併工具會明確將系統權限新增至合併的資訊清單。

舉例來說,如果應用程式將 targetSdkVersion 設為 4 以上,並匯入 targetSdkVersion 設為 3 以下的程式庫,合併工具就會將 WRITE_EXTERNAL_STORAGE 權限新增至合併的資訊清單。

表 2 列出了可新增至合併資訊清單的所有可能權限:

表 2:這份清單列出合併工具可能新增至合併資訊清單的權限

優先順序較低的資訊清單宣告 新增至合併資訊清單的權限
targetSdkVersion 為 3 以下 WRITE_EXTERNAL_STORAGEREAD_PHONE_STATE
targetSdkVersion 為 15 以下,且使用 READ_CONTACTS READ_CALL_LOG
targetSdkVersion 為 15 以下,且使用 WRITE_CONTACTS WRITE_CALL_LOG

檢查合併的資訊清單並找出衝突

即使尚未建構應用程式,還是可以預覽合併的資訊清單,步驟如下:

  1. 在 Android Studio 中開啟 AndroidManifest.xml 檔案。
  2. 按一下編輯器底部的「Merged Manifest」分頁標籤。

「Merged Manifest」檢視畫面左側會顯示合併的資訊清單結果,右側則會列出每個合併資訊清單檔案的資訊,如圖 2 所示。

如有元素是從優先順序較低的資訊清單檔案併入這個檔案,畫面左側會以不同顏色醒目顯示這些元素。「Manifest Source」下方則會明確提供各種顏色的圖例。

圖 2. 「Merged Manifest」檢視畫面。

「Other Manifest Files」下方會列出這個版本中未提供元素或屬性的資訊清單檔案。

如要查看元素來源,請在左側窗格中點選該元素,「Merging Log」下方就會顯示詳細資料。

如果發生衝突,「Merging Errors」下方會顯示錯誤,並提出如何使用合併規則標記解決衝突的建議。

這些錯誤也會顯示在「Event Log」視窗中,依序選取「View」>「Tool Windows」>「Event Log」即可查看錯誤。

如要瀏覽合併決策樹狀結構的完整記錄,您可以在模組的 build/outputs/logs/ 目錄中找出名為 manifest-merger-buildVariant-report.txt 的記錄檔。

合併政策

資訊清單合併工具可以按照邏輯,比對某個資訊清單檔案中的每個 XML 元素與另一個檔案中的對應元素。合併工具會使用「比對鍵」比對各元素,比對鍵可以是不重複的屬性值 (例如 android:name),或是標記本身固有的不重複性 (例如只能有一個 <supports-screen> 元素)。

如果兩份資訊清單含有相同 XML 元素,這項工具就會採用下列三種合併政策中的任一政策,合併這兩個元素:

合併
將所有非衝突的屬性結合至相同的標記,然後根據各自的合併政策合併子項元素。如果有任何屬性互相衝突,請使用合併規則標記合併這些屬性。
僅合併子項
不結合或合併屬性 (僅保留最高優先順序資訊清單檔案提供的屬性),並根據各自的合併政策合併子項元素。
Keep
將元素保留原樣,並新增至合併檔案中共同的父項元素。只有在允許多次宣告相同元素時,才適用這項政策。

表 3 列出了各種元素類型、所用合併政策的類型,以及決定兩份資訊清單之間元素比對方式時使用的鍵:

表 3:資訊清單元素合併政策和比對鍵

元素 合併政策 比對鍵
<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> 只有一個此元素。
自訂元素 合併 不比對;這是合併工具無法辨識的元素,因此一律會包含在合併的資訊清單中。

將建構變數插入資訊清單

如果需要將變數插入 build.gradle 檔案中所定義的 AndroidManifest.xml 檔案,可以使用 manifestPlaceholders 屬性。這個屬性會採用鍵/值組合的對應關係,如下所示:

Groovy

android {
    defaultConfig {
        manifestPlaceholders = [hostName:"www.example.com"]
    }
    ...
}

Kotlin

android {
    defaultConfig {
        manifestPlaceholders["hostName"] = "www.example.com"
    }
    ...
}

接著,就能在資訊清單檔案中插入其中一個預留位置做為屬性值:

<intent-filter ... >
    <data android:scheme="https" android:host="${hostName}" ... />
    ...
</intent-filter>

根據預設,建構工具也會在 ${applicationId} 預留位置中提供應用程式的應用程式 ID。這個值一律與目前版本 (包括建構變數所做的變更) 的最終應用程式 ID 相符。如果想為意圖動作等 ID 使用不重複的命名空間,這種做法就相當實用,即使在不同的建構變數之間也能派上用場。

舉例來說,假設您的 build.gradle 檔案如下:

Groovy

android {
    defaultConfig {
        applicationId "com.example.myapp"
    }
    flavorDimensions "type"
    productFlavors {
        free {
            applicationIdSuffix ".free"
            dimension "type"
        }
        pro {
            applicationIdSuffix ".pro"
            dimension "type"
        }
    }
}

Kotlin

android {
    defaultConfig {
        applicationId = "com.example.myapp"
    }
    flavorDimensions += "type"
    productFlavors {
        create("free") {
            applicationIdSuffix = ".free"
            dimension = "type"
        }
        create("pro") {
            applicationIdSuffix = ".pro"
            dimension = "type"
        }
    }
}

接著,您可以在資訊清單中插入應用程式 ID,如下所示:

<intent-filter ... >
    <action android:name="${applicationId}.TRANSMOGRIFY" />
    ...
</intent-filter>

當您建構「免費」變種版本時,資訊清單結果會是:

<intent-filter ... >
   <action android:name="com.example.myapp.free.TRANSMOGRIFY" />
    ...
</intent-filter>

詳情請參閱「設定應用程式 ID」。