管理資訊清單檔案

本頁說明我們的建構工具如何擷取資訊清單檔案,進而決定應用程式的最終樣式和提供的體驗。

如需應用程式資訊清單檔案的說明,請參閱「應用程式資訊清單總覽」。

合併多個資訊清單檔案

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

資訊清單合併工具會依照某些合併經驗法則運作,並遵循您以特殊 XML 屬性定義的合併偏好設定,結合各個檔案中的所有 XML 元素。本頁說明資訊清單合併作業的運作方式,以及如何套用合併偏好設定以解決合併衝突。

提示:請使用「合併的資訊清單」檢視畫面預覽合併資訊清單的結果,並找出衝突錯誤。

合併優先順序

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

圖 1. 三個資訊清單檔案的合併程序,由最低優先順序 (左) 合併至最高優先順序 (右)

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

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

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

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

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

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

    如果您有多個程式庫,其中的資訊清單優先順序會與依附元件順序相同 (也就是這些項目在 Gradle dependencies 區塊中的顯示順序)。

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

重要事項:build.gradle 檔案中的建構設定會覆寫合併資訊清單檔案中任何對應的屬性。舉例來說,build.gradle 檔案中的 minSdkVersion 會覆寫 <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> 元素中的屬性一律會使用較高優先順序資訊清單的值,但下列情況除外:
    • 如果較低優先順序資訊清單的 minSdkVersion 值「較高」,則會發生錯誤;除非您套用 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="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>

根據預設,所匯入程式庫的 minSdkVersion 值「大於」主要資訊清單檔案的值時,系統就會發生錯誤,導致無法匯入程式庫。如要讓合併工具忽略這項衝突,並在保留應用程式較低 minSdkVersion 值的情況下匯入程式庫,請將 overrideLibrary 屬性新增至 <uses-sdk> 標記。屬性值可以是一或多個程式庫套件名稱 (以半形逗號分隔),指出哪些程式庫可以覆寫主要資訊清單的 minSdkVersion

舉例來說,如果應用程式的主要資訊清單套用 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> 標記有關的錯誤,而合併的資訊清單會保留應用程式資訊清單中的 minSdkVersion="2"

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.lib1">
   <uses-sdk android:minSdkVersion="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

檢查合併的資訊清單並發現衝突

即使您尚未建構應用程式,還是可以在 Android Studio 中開啟 AndroidManifest.xml 檔案,然後按一下編輯器底部的「Merged Manifest」分頁標籤,預覽合併的資訊清單。

合併的資訊清單檢視畫面左側會顯示合併資訊清單的結果,右側則是每個合併資訊清單檔案的資訊,如圖 2 所示。對於合併時來自較低優先順序資訊清單檔案的元素,畫面左側會以不同的顏色標明這些元素。右側「Manifest Source」(資訊清單來源) 下方會顯示每個顏色的說明。

圖 2. 合併的資訊清單檢視畫面

右側的「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 元素,這項工具就會使用下列三種合併政策的其中一項政策,合併這兩個元素:

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

表 1 列出各元素類型、使用的合併政策類型,以及用於決定兩個資訊清單之間元素比對作業所需的比對鍵。

表 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 的變更)。如果您想使用唯一的命名空間做為 ID (例如設定意圖動作),這就很實用;即使在不同的建構變數之間也可以使用。

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

Groovy

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

Kotlin

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

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

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

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

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

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