複数のマニフェスト ファイルをマージする

APK または Android App Bundle ファイルには AndroidManifest.xml ファイルが 1 つだけ含まれていますが、Android Studio プロジェクトには、メイン ソースセット、ビルド バリアント、インポート済みライブラリによって提供される複数のマニフェスト ファイルが含まれる場合があります。そのため、アプリをビルドする際に、Gradle ビルドはすべてのマニフェスト ファイルを単一のマニフェスト ファイルにマージして、アプリ内にパッケージ化します。

マニフェスト マージツールは、下記のマージ ヒューリスティックを用いて、特別な XML 属性で定義したマージ優先順位に従って、各ファイルのすべての XML 要素を結合します。このページでは、マニフェスト マージの仕組みと、マージ優先順位を適用してマージ競合を解決する方法について説明します。

ヒント: [Merged Manifest] ビューを使用すると、マージ マニフェストの結果をプレビューしたり、競合エラーを確認したりできます。

マージ優先順位

マージツールは、各マニフェスト ファイルの優先順位に基づいて、すべてのマニフェスト ファイルを順次マージし、1 つのファイルに結合します。たとえば、3 つのマニフェスト ファイルがある場合、図 1 に示すように、優先度第 3 位のマニフェストが優先度第 2 位のマニフェストにマージされ、そのマージ結果が優先度第 1 位のマニフェストにマージされます。

図 1. 3 つのマニフェスト ファイルのマージ処理(最も優先度が低いファイル(左)から最も優先度が高いファイル(右)に順次マージ)

相互にマージ可能なマニフェスト ファイルには 3 つの基本タイプがあります。以下に、マージ優先順位が最も高いものから順に示します。

  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 属性です。 マーカーは、要素全体または要素内の特定の属性に対して適用できます。

2 つのマニフェスト ファイルをマージする際、マージツールは、優先度の高いマニフェスト ファイル内にマーカーがないか検索します。

すべてのマーカーは 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>

この場合、マニフェスト マージエラーが発生します。strict モードでは、2 つのマニフェスト要素は完全に一致している必要があります。そのため、不一致を解決するには、別のマージルール マーカーを適用する必要があります(通常は、完全に一致していなくても 2 つの要素は問題なくマージされます。tools:node="merge" の例をご覧ください)。

属性マーカー

マニフェスト タグ内の特定の属性だけにマージルールを適用するには、下記の属性を使用します。各属性には、1 つまたは複数の属性名(属性名前空間を含む)を指定できます(複数の場合はカンマ区切りで指定します)。

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" を削除した場合でも、上記の例の結果は同じになります)。

次のように、1 つの要素に対して複数のマーカーを適用できます。

優先度の低いマニフェスト:

<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 値がメイン マニフェスト ファイルよりも大きいライブラリをインポートしようとすると、デフォルトではエラーが発生し、ライブラリはインポートできません。<uses-sdk> タグに overrideLibrary 属性を追加すると、アプリの小さい方の minSdkVersion 値をアプリレベルで維持したままで、マージツールが競合を無視してライブラリをインポートできるようになります。この属性値には、1 つまたは複数のライブラリ パッケージ名を指定できます(複数の場合はカンマ区切りで指定します)。これにより、メイン マニフェストの 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 バージョンでは、アプリの targetSdkVersion 値が、制限が追加されたバージョンよりも小さい値に設定されていれば、パーミッションがなくても API に引き続きアクセスできるようになっています。この動作により、事実上 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] タブをクリックします。

[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 など)か、タグ自体が元々備えている一意性(例: <supports-screen> 要素は 1 つに限られる)のいずれかです。2 つのマニフェスト内に同じ XML 要素が含まれている場合、マージツールは、以下の 3 つのマージポリシーのいずれかを使用して 2 つの要素をマージします。

マージ
競合していないすべての属性を 1 つのタグに結合し、子要素をそれぞれのマージポリシーに従ってマージします。競合が発生している属性の場合は、マージルール マーカーを使用してマージします。
子だけをマージ
属性のマージ(結合)は行いません(最も優先度の高いマニフェスト ファイルが提供する属性だけを維持します)。子要素は、それぞれのマージポリシーに従ってマージします。
維持
各要素を維持したまま、マージファイル内の共通親要素に追加します。このポリシーは、1 つの要素に対して複数の宣言が可能な場合に限り使用されます。

各要素のタイプ、使用されるマージポリシーのタイプ、2 つのマニフェスト間の要素のマッチングを判断する際に使用されるキーを表 3 に示します。

表 3. マニフェスト要素のマージポリシーとマッチングキー

要素 マージポリシー マッチングキー
<action> マージ android:name 属性
<activity> マージ android:name 属性
<application> マージ <manifest> ごとに 1 つ
<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 つ
カスタム要素 マージ マッチングなし。マージツールでは認識できないため、常にマージ マニフェストに結合