複数のマニフェスト ファイルの統合

APK ファイルには AndroidManifest.xml ファイルを 1 つのみ含めることができますが、Android Studio プロジェクトには、メイン ソースセット、ビルド バリアント、およびインポートされたライブラリにより提供される複数のマニフェスト ファイルを含めることができます。 したがって、アプリをビルドするときに、Gradle ビルドによりすべてのマニフェスト ファイルが単一のマニフェスト ファイルに統合され、これが APK にパッケージ化されます。

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

ヒント: 統合した後のマニフェストのプレビューや競合エラーの特定には、[Merged Manifest] ビューを使用してください。

統合の優先度

統合ツールでは、各マニフェスト ファイルの優先度に基づいてすべてのマニフェスト ファイルを順次統合して、1 つのファイルにまとめます。 たとえば 3 つのマニフェスト ファイルがある場合、図 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> 要素の属性は統合されることはありません。最も高い優先度のマニフェストの属性のみが使用されます。
  • android:required 属性(<uses-feature> 要素と <uses-library> 要素内)には、OR 統合が使用されます。この場合、競合が発生すると "true" が適用されて、1 つのマニフェストで必須の機能またはライブラリが必ず含まれるようになります。
  • <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 属性を追加します。

たとえば次のマニフェストでは、remove 統合ルールは、優先度の低いマニフェスト ファイルが com.example.lib1 ライブラリに含まれている場合のみ適用されます。

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

優先度の低いマニフェストが他のソースのものである場合、remove 統合ルールは無視されます。

注: マーカー セレクターをいずれかの属性マーカーとともに使用する場合、マーカー セレクターは該当マーカーで指定されたすべての属性に適用されます。

インポートされたライブラリの <uses-sdk> のオーバーライド

デフォルトでは、メインのマニフェストファイルより高い minSdkVersion 値を持つライブラリをインポートすると、エラーが発生してライブラリをインポートできません。 統合ツールでこの競合を無視してライブラリをインポートし、かつアプリの低い minSdkVersion 値を保持するには、overrideLibrary 属性を <uses-sdk> タグに追加します。

この属性値に 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 android:targetSdkVersion="22" android:minSdkVersion="2"
            tools:overrideLibrary="com.example.lib1, com.example.lib2"/>
...

次のマニフェストは、&lt;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 へのアクセスが想定されるアプリが正常に動作するように、アプリで targetSdkVersion の値を制限が追加されたバージョンより低く設定している場合、最近のバージョンの Android ではパーミッションがなくてもこれらの API に引き続きアクセスできるようになっています。 この動作により、事実上 API へのアクセスを許可する暗黙的なパーミッションがアプリに付与されます。 これは、異なる targetSdkVersion の値を持つマニフェストの統合に次のように影響します。

優先度の低いマニフェスト ファイルに低い値の targetSdkVersion が含まれ、これが暗黙的なパーミッションを付与しており、優先度の高いマニフェストが同じ暗黙的パーミッションを持たない場合(その targetSdkVersion が制限が追加されたバージョン以上であるため)、統合ツールはこのシステム パーミッションを統合されたマニフェストに明示的に追加します。

たとえば、アプリで targetSdkVersion が 4 以上に設定されており、targetSdkVersion が 3 以下に設定されたライブラリをインポートする場合、統合ツールにより WRITE_EXTERNAL_STORAGE パーミッションが統合されたマニフェストに追加されます。 表 2 は、統合されたマニフェストに追加される可能性があるすべてのパーミッションを示しています。

注:アプリの targetSdkVersion を 23 以上に設定している場合は、Dangerous パーミッションで保護されている API にアプリがアクセスしようとした際に、それらのパーミッションに対する実行時パーミッション リクエストを行う必要があります。 詳細については、システム パーミッションの使用をご覧ください。

表 2. 統合ツールが統合されたマニフェストに追加する可能性があるパーミッションのリスト

優先度の低いマニフェストでの宣言 統合されたマニフェストに追加されるパーミッション
targetSdkVersion が 3 以下 WRITE_EXTERNAL_STORAGEREAD_PHONE_STATE
targetSdkVersion が 15 以下で READ_CONTACTS を使用 READ_CALL_LOG
targetSdkVersion が 15 以下で WRITE_CONTACTS を使用 WRITE_CALL_LOG

統合されたマニフェストを調査して競合を特定する

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 などの一意の属性値、またはタグが本来持っている一意性(例: 複数の <supports-screen> 要素を持つことができない)です。 2 つのマニフェストに同じ XML 要素が含まれる場合、このツールでは次の 3 つの統合ポリシーのいずれかを使用して 2 つの要素を統合します。

統合
競合しないすべての属性を同じタグに結合して、子要素をそれぞれの統合ポリシーに従って統合します。 いずれかの属性で競合が発生している場合は、統合ルール マーカーを使用してこれらを統合します。
子のみを統合
属性を結合または統合しません(最も優先度が高いマニフェスト ファイルで提供されている属性のみが維持されます)。また、子要素をそれぞれの統合ポリシーに従って統合します。
維持
既存の要素を保持したまま統合されたファイルの共通親要素に追加します。 これは、同一の要素で複数の宣言が可能な場合のみ使用されます。

表 1 に、各要素タイプ、使用される統合ポリシーのタイプ、および 2 つのマニフェスト間の要素の一致の判断に使用されるキーを示します。

表 3. マニフェスト要素の統合ポリシーと一致キー

要素 統合ポリシー 照合キー
<action> 統合 android:name 属性
<activity> 統合 android:name 属性
<application> 統合 1 つの <manifest> あたり 1 つのみ存在
<category> 統合 android:name 属性
<data> 統合 1 つの <intent-filter> あたり 1 つのみ存在
<grant-uri-permission> 統合 1 つの <provider> あたり 1 つのみ存在
<instrumentation> 統合 android:name 属性
<intent-filter> 維持 一致なし、親要素内の複数の宣言が可能
<manifest> 子のみ統合 1 つのファイルあたり 1 つのみ存在
<meta-data> 統合 android:name 属性
<path-permission> 統合 1 つの <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> 統合 1 つの <manifest> あたり 1 つのみ存在
<uses-configuration> 統合 1 つの <manifest> あたり 1 つのみ存在
<uses-feature> 統合 android:name 属性(存在しない場合は android:glEsVersion 属性)
<uses-library> 統合 android:name 属性
<uses-permission> 統合 android:name 属性
<uses-sdk> 統合 1 つの <manifest> あたり 1 つのみ存在
カスタム要素 統合 一致なし。これらは統合ツールでは認識できないため、常に統合されたマニフェストに含まれます。