保持するリソースのカスタマイズ

アプリの最適化を有効にすると、オプティマイザーのデフォルトの動作は R8 のバージョンによって異なります。

  • AGP 9.3 以降で利用可能な更新された DSL では、最適化が有効になっている場合、リソースの縮小がデフォルトで有効になります。コードとリソースの最適化を個別に有効にする必要がある以前の DSL は、引き続きサポートされています。
  • AGP 9.3 より前のバージョンでは、isShrinkResources = true 設定により、未使用のリソースを削除するようオプティマイザーに指示し、アプリのサイズを縮小できます。リソースの圧縮はコードの圧縮と組み合わせてのみ機能するため、リソースを最適化する場合は isMinifyEnabled = true も設定してください。

AGP 9.3 以降(Kotlin)

buildTypes {
    release {
        optimization {
            enable = true // Enables code and resource optimizations.
        }
    }
}

AGP 9.3 以降(Groovy)

buildTypes {
    release {
        optimization {
            enable = true // Enables code and resource optimizations.
        }
    }
}

以前の DSL(Kotlin)

buildTypes {
    release {
        isMinifyEnabled = true
        isShrinkResources = true
        ...
    }
}

レガシー DSL(Groovy)

buildTypes {
    release {
        minifyEnabled = true
        shrinkResources = true
        ...
    }
}

特定のリソースを保持または破棄したい場合は、プロジェクト リソース(res/raw/my.package.keep.xml など)に XML keep ファイルを作成します。keep ファイルには次のコンポーネントがあります。

  • <resources> タグ - すべての子リソース要素と保持/破棄属性が含まれます。
  • tools:keep 属性 - 保持するリソースを識別するリソース名のカンマ区切りのリストを指定します
  • tools:discard 属性 - 破棄するリソースを識別するリソース名のカンマ区切りのリストを指定します。

アスタリスク文字をワイルドカードとして使用して、同じフォルダ内の複数のリソースを参照します。例:

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
    tools:discard="@layout/unused2" />

リソースを直接削除できるのに、そうせずに破棄するリソースを指定することは意味がないように思えますが、リソースの破棄はビルド バリアントを使用するときに役に立つ場合があります。

特定のビルド バリアントをターゲットにする

一部のビルド バリアントでのみリソースを削除するには、すべてのリソースを共通のプロジェクト ディレクトリに配置し、バリアントのリソース ディレクトリに各ビルド バリアント用の異なる my.package.build.variant.keep.xml ファイルを作成します。キープファイルでは、特定のリソースがコードで使用されている(そのため、シュリンカーによって削除されない)ように見えるが、実際には特定のビルド バリアントで使用されないことがわかっている場合に、削除するリソースを手動で指定します。

未使用の代替リソースの削除

オプティマイザーは、アプリのコードが参照しないリソースのみを削除します。つまり、さまざまなデバイス設定の代替リソースは削除されません。

アプリのモジュールの build.gradle ファイルで Android Gradle の resConfigs プロパティを使用して、アプリに不要な代替リソース ファイルを削除します。

たとえば、言語リソースを含むライブラリ(Google Play 開発者サービスなど)を使用している場合、アプリの残りの部分が同じ言語に翻訳されるかどうかに関係なく、アプリには、これらのライブラリにあるメッセージのすべての翻訳言語の文字列が含まれます。アプリが公式にサポートする言語のみを保持するには、resConfigs プロパティを使用してそれらの言語を指定します。指定されていない言語のリソースは削除されます。

次のスニペットは、言語リソースを英語とフランス語のみに制限する方法を示しています。

android {
    defaultConfig {
        ...
        resourceConfigurations.addAll(listOf("en", "fr"))
    }
}

または

android {
    defaultConfig {
        ...
        resConfigs "en", "fr"
    }
}

Android App Bundle(AAB)形式を使用してアプリを公開すると、ユーザーがアプリをインストールする際に、デフォルトではユーザーのデバイスで設定されている言語のみがダウンロードされます。同様に、デバイスの画面密度に一致するリソースと、デバイスの ABI に一致するネイティブ ライブラリのみがダウンロードに含まれます。詳しくは、設定 APK のタイプを無効にするまたは再度有効にするをご覧ください。

APK を使用して旧式アプリ(2021 年 8 月より前に作成)をリリースする場合は、それぞれが異なるデバイス設定を対象とする複数の APK をビルドすることで、APK に含める画面密度や ABI リソースをカスタマイズできます。

リソースをマージする際の競合を回避する

デフォルトでは、Android Gradle プラグイン(AGP)は、異なるリソース フォルダにある同じ名前のドローアブルなど、同一の名前を持つリソースを結合します。この動作は、shrinkResources プロパティによって制御することも、無効にすることもできません。これは、コードが参照している名前を持つリソースが複数あったときのエラーを回避するために必要な動作です。

リソースの結合は、2 つ以上のファイルが同一のリソース名、タイプ、修飾子を共有している場合にのみ行われます。AGP は、(以下に説明する優先順位に基づいて)重複ファイルのうち最適であると判断したファイルを選択し、最終的なビルド アーティファクトでの配布用にその 1 つのリソースのみを AAPT に渡します。

AGP は次の場所で重複リソースを検索します。

  • メインリソース。メイン ソースセットと関連付けられていて、通常は src/main/res/ にあります。
  • ビルドタイプとビルド フレーバーからなるバリアント オーバーレイ
  • ライブラリ プロジェクトの依存関係

AGP は、次の優先順位に従って重複リソースを結合します。

依存関係 → メイン → ビルド フレーバー → ビルドタイプ

たとえば、メインリソースとビルド フレーバーの両方に重複リソースがある場合、Gradle はビルド フレーバー内のリソースを選択します。

同じソースセットに同一のリソースがある場合、Gradle はこれらのリソースを結合できないため、リソース結合エラーを出力します。このエラーは、モジュールの build.gradle ファイルの sourceSet プロパティで複数のソースセットが定義されている場合に発生する可能性があります(src/main/res/src/main/res2/ の両方に同じリソースが格納されている場合など)。

リソース圧縮のトラブルシューティング

リソースを圧縮すると、アプリから削除されたリソースの概要が [Build] ウィンドウに表示されます(ウィンドウの左側にある [Toggle view] をクリックして、Gradle からの詳細なテキスト出力を表示する必要があります)。次に例を示します。

:android:shrinkDebugResources
Removed unused resources: Resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning

また、Gradle は <module-name>/build/outputs/mapping/release/(ProGuard の出力ファイルと同じフォルダ)に resources.txt という名前の診断ファイルを作成します。このファイルには、どのリソースが他のリソースを参照したか、どのリソースが使用または削除されたかなどの詳細情報が記載されます。

たとえば、アプリに @drawable/ic_plus_anim_016 が引き続き存在している理由を特定するには、resources.txt ファイルを開き、そのファイル名を検索します。別のリソースから参照されていることが確認できる場合があります。

16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out] @drawable/ic_plus_anim_016

ここで、@drawable/add_schedule_fab_icon_anim が到達可能である理由を確認するため、上に向かって調べていくと、そのリソースが resources.txtThe root reachable resources are: という見出しの下に記載されていることがわかります。

つまり、add_schedule_fab_icon_anim に対するコード参照があります(到達可能なコード内にその R.drawable ID があります)。

厳密なチェックを使用していない場合は、動的に読み込まれるリソースの名前の作成に使用されそうな文字列定数があると、リソース ID が到達可能であるとマークされる可能性があります。この場合、ビルド出力でそのリソース名を探すと、次のようなメッセージが見つかることがあります。

10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506
    used because its format-string matches string pool constant ic_plus_anim_%1$d.

これらのいずれかの文字列が表示され、特定のリソースの動的な読み込みにその文字列が使用されていないことが確実である場合、キープファイルで tools:discard 属性を使用して、そのリソースを削除するようにビルドシステムに指示します。