1. 程式人生 > >Merged Manifest合併清單的效果並找出衝突錯誤

Merged Manifest合併清單的效果並找出衝突錯誤

合併多個清單檔案

APK 檔案只能包含一個 AndroidManifest.xml 檔案,但 Android Studio 專案可以包含多個檔案(通過主源集、構建變體和匯入的庫提供)。因此,在構建應用時,Gradle 構建會將所有清單檔案合併到一個封裝到 APK 的清單檔案中。

清單合併工具通過遵循某些合併啟發式演算法,並遵守您通過特殊 XML 屬性定義的合併首選項,來合併各個檔案中的所有 XML 元素 。 本頁介紹清單合併的工作方式以及如何應用合併首選項來解決合併衝突。

合併優先順序

合併工具根據每個清單檔案的優先順序將所有清單檔案按順序合併到一個檔案中。 例如,如果您有 3 個清單檔案,則會先將優先順序最低的清單合併到優先順序第 2 高的清單中,然後再將合併後的清單合併到優先順序最高的清單中(如圖 1 所示)。

這裡寫圖片描述

圖 1. 合併 3 個清單 檔案(從優先順序最低的檔案(左)合併至優先順序最高的檔案(右))的流程

有 3 種基本的清單檔案可以互相合並,它們的合併優先順序如下(按優先順序由高到低的順序):

  • 清單檔案構建

    • 如果您的變體有多個源集,則其清單優先順序如下:
    • 構建變體清單(如 src/demoDebug/)
    • 構建型別清單(如 src/debug/)
    • 產品定製清單(如 src/demo/)

      如果您使用的是定製維度,清單優先順序將與每個維度在 flavorDimensions 屬性中的列示順序(按優先順序由高到低的順序排列)對應。

  • 應用模組的主清單檔案

  • 所包括庫中的清單檔案

    • 如果您有多個庫,則其清單優先順序與依賴順序(庫出現在 Gradle dependencies 塊中的順序)匹配。

例如,庫清單合併至主清單,然後主清單合併至構建變體清單。

重要說明:build.gradle 檔案中的構建配置將替換合併清單檔案中的任何對應屬性。 例如,build.gradle 檔案中的minSdkVersion 將替換< uses-sdk > 清單元素中的匹配屬性。 為了避免混淆,您只需省去 < uses-sdk> 元素並在 build.gradle 檔案中定義這些屬性。 For more details, see Configure Your Build.

合併衝突啟發式演算法

合併工具可以在邏輯上將一個清單中的每個 XML 元素與另一個清單中的對應元素相匹配。 (有關匹配如何進行的詳細資訊,請參閱有關合並策略的附錄)。

如果優先順序較低的清單中的元素與優先順序較高的清單中的任何元素均不匹配,則該元素將被新增至合併清單。 但是,如果有匹配元素,則合併工具會嘗試將其中的所有屬性合併到相同元素中。如果工具發現兩個清單包含相同屬性,但值不相同,則會出現合併衝突。

表 1 描述合併工具嘗試將所有屬性合併到同一元素時可能出現的結果。

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


這裡寫圖片描述

但是,在某些情況下,合併工具會採取其他行為方式以避免合併衝突:

  • < manifest> 元素中的屬性絕不合並—僅使用優先順序最高的清單中的屬性。

  • android:required 屬性 >< uses-feature> and < uses-library> 元素使用 OR 合併,因此如果出現衝突,系統將應用 “true” 並始終包括某個清單所需的功能或庫。

  • < uses-sdk> 元素始終使用優先順序較高的清單中的值,但以下情況除外:

    • 如果低優先順序清單的 minSdkVersion 值較高,除非您應用 overrideLibrary 合併規則。

    • 如果低優先順序清單的 targetSdkVersion 值較低,合併工具將使用高優先順序清單中的值,但也會新增任何必要的系統許可權,以確保所匯入的庫繼續正常工作(適用於較高的 Android 版本具有更多許可權限制的情況)。 如需瞭解有關此行為的詳細資訊,請參閱有關隱式系統許可權的部分。

  • 絕不會在清單之間匹配 元素。 每個元素都被視為唯一元素,並新增至合併清單中的常用父元素。

對於屬性之間的所有其他衝突,您將收到一則錯誤,並且必須通過在高優先順序清單檔案中新增特殊屬性來指示合併工具如何解決此錯誤(請參閱下一節,其中介紹了有關合並規則標記的內容)。

不依賴於預設屬性值:由於所有唯一屬性都合併到相同元素中,如果高優先順序清單實際上依賴於屬性的預設值而不需要宣告,則可能會導致意外結果。例如,如果高優先順序清單不宣告android:launchMode 屬性,則會使用 “standard” 的預設值;但如果低優先順序清單宣告此屬性具有其他值,該值將應用於合併清單(替代預設值)。因此,您應該按期望明確定義每個屬性。(每個屬性的預設值都會記錄在 Manifest reference 中)。

合併衝突啟發式演算法

合併規則標記是一個 XML 屬性,可用於表達您對關於如何解決合併衝突或刪除不需要的元素和屬性的首選項。 您可以對整個元素或只對元素中的特定屬性應用標記。

合併兩個清單檔案時,合併工具會在高優先順序清單檔案中尋找這些標記。

所有標記均屬於 Android tools 名稱空間,因此您必須先在 元素中宣告此名稱空間,如下文所示:

<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-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 的庫。

例如,如果應用的主清單按如下所示應用 < uses-sdk>overrideLibrary

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.example.app"
     xmlns:tools="http://schemas.android.com/tools">
    <uses-sdk
     android:targetSdkVersion="22"    
     android:minSdkVersion="2"                               
     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 版本中,應用曾經可以自由訪問的某些 Android API 已受 系統 許可權限制。 為了避免中斷預期會訪問這些 API 的應用,最近的 Android 版本允許應用在無許可權的情況下繼續訪問這些 API,前提是它們已將 targetSdkVersion 設定為低於新增限制的版本的值。此行為有效地嚮應用授予了隱式 許可權,以允許訪問 API。 因此,這可能會對具有不同 targetSdkVersion 值的合併清單產生以下影響

如果低優先順序清單檔案提供隱式許可權的 targetSdkVersion 值較低,而且高優先順序清單沒有相同的隱式許可權(由於其 targetSdkVersion 等於或高於新增限制的版本),合併工具將向合併清單顯式新增系統許可權。

例如,如果您的應用將 targetSdkVersion 設定為 4 或更高值,並且匯入了將 targetSdkVersion 設定為 3 或更低值的庫,合併工具會將 WRITE_EXTERNAL_STORAGE 許可權新增至合併清單。 表 2 列出了可以新增至合併清單的所有可能許可權。

注:如果您將應用的 targetSdkVersion 設定為 23 或更高值,則必須在應用嘗試訪問受這些許可權保護的 API 時為任何危險許可權執行執行時許可權請求。 如需獲得更多指導,請參閱使用系統許可權。

表 2. 合併工具可新增至合併清單的許可權列表
這裡寫圖片描述

會檢查合併清單並查詢衝突

即使在構建 APK 之前,也可以預覽合併清單,具體方法是:在 Android Studio 中開啟您的 AndroidManifest.xml 檔案,然後單擊編輯器底部的 Merged Manifest 選項卡。

Merged Manifest 檢視在左側顯示合併清單的結果,在右側顯示每個合併清單檔案的相關資訊,如圖 2 所示。從低優先順序檔案中合併的元素在左側以不同顏色突出顯示。 每種顏色的關鍵字在右側的 Manifest Sources 下方指定。

這裡寫圖片描述

圖 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或標記本身的天然唯一性(例如,只能有一個 元素)。 如果兩個清單具有相同的 XML 元素,工具將採用三種合併策略中的一種來合併這兩個元素:

  • 合併
    將所有非衝突屬性合併到同一標記中, 然後按其各自的合併策略合併子元素。 如果任何屬性 相互衝突,請使用合併規則標記將它們合併在一起。

  • 僅合併子項
    不整合或合併屬性(僅保留 優先順序最高的清單檔案提供的屬性)並按照其合併策略 合併子項。

  • 列表內容

保留
將元素“按原樣”保留,然後將其新增至 合併檔案中的常用父元素。 此策略僅在可接受相同元素的多個 宣告時使用。
表 1 列出了每種元素型別、使用的合併策略型別以及用於確定兩個清單之間的元素匹配的關鍵字。

表 3. 清單元素合併策略和 合併關鍵字
這裡寫圖片描述