The Android Developer Challenge is back! Submit your idea before December 2.

アプリの圧縮、難読化、最適化

アプリのサイズをできる限り小さくするには、リリースビルドで圧縮を有効にして、使用されていないコードとリソースを削除する必要があります。圧縮を有効にすると、アプリのクラスとメンバーの名前を短くする難読化と、より積極的な戦略を適用してアプリのサイズをさらに小さくする最適化によるメリットも得られます。このページでは、R8 でプロジェクトのコンパイル時タスクを実行する方法と、それらのタスクをカスタマイズする方法について説明します。

Android Gradle プラグイン 3.4.0 以降を使用したプロジェクトのビルドでは、プラグインが ProGuard を使用してコンパイル時のコード最適化を実行することがなくなりました。その代わり、プラグインは R8 コンパイラと連携して以下のコンパイル時タスクを実行します。

  • コードの圧縮(ツリー シェイキング): 使用されていないクラス、フィールド、メソッド、属性を検出し、アプリとそのライブラリ依存関係から安全に削除します(64K 参照制限を回避するための有用なツールです)。たとえば、ライブラリ依存関係の API を数個だけ使用すると、アプリで使用されていないライブラリ コードを圧縮タスクで特定し、アプリからそのコードだけを削除できます。詳しくは、コードの圧縮をご覧ください。
  • リソースの圧縮: パッケージ化アプリから使用されていないリソース(アプリのライブラリ依存関係で使用されていないリソースなど)を削除します。リソースの圧縮はコードの圧縮と連携して機能するため、使用されていないコードが削除されると、参照されなくなったリソースも安全に削除することができます。詳しくは、リソースの圧縮をご覧ください。
  • 難読化: クラスとメンバーの名前を短くし、DEX ファイルのサイズを小さくします。詳しくは、コードの難読化をご覧ください。
  • 最適化: コードを検査して書き換えることで、アプリの DEX ファイルのサイズをさらに小さくします。たとえば、特定の if / else ステートメントの else {} 分岐に入ることが絶対にないことが R8 によって検出された場合、R8 はその else {} 分岐のコードを削除します。詳しくは、コードの最適化をご覧ください。

アプリのリリース バージョンをビルドするとき、R8 はデフォルトで、上記のコンパイル時タスクを自動的に実行します。ただし、特定のタスクを無効にしたり、ProGuard ルールファイルを介して R8 の動作をカスタマイズしたりできます。実際には、R8 は既存のすべての ProGuard ルールファイルと連携するため、R8 を使用するように Android Gradle プラグインを更新しても、既存のルールを変更する必要はありません。

圧縮、難読化、最適化の有効化

Android Studio 3.4 または Android Gradle プラグイン 3.4.0 以降を使用している場合、R8 がデフォルトのコンパイラになります。R8 はプロジェクトの Java バイトコードを Android プラットフォームで実行可能な DEX 形式に変換します。ただし、Android Studio を使用して新しいプロジェクトを作成した場合、圧縮、難読化、コードの最適化はデフォルトでは有効になりません。これは、こうしたコンパイル時の最適化によってプロジェクトのビルド時間が長くなり、保持するコードのカスタマイズが十分でない場合にはバグが発生する可能性があるためです。

そのため、これらのコンパイル時タスクは、公開前にテストするアプリの最終バージョンをビルドするときに有効にすることをおすすめします。圧縮、難読化、最適化を有効にするには、プロジェクト レベルの build.gradle ファイルに以下を含めます。

android {
        buildTypes {
            release {
                // Enables code shrinking, obfuscation, and optimization for only
                // your project's release build type.
                minifyEnabled true

                // Enables resource shrinking, which is performed by the
                // Android Gradle plugin.
                shrinkResources true

                // Includes the default ProGuard rules files that are packaged with
                // the Android Gradle plugin. To learn more, go to the section about
                // R8 configuration files.
                proguardFiles getDefaultProguardFile(
                        'proguard-android-optimize.txt'),
                        'proguard-rules.pro'
            }
        }
        ...
    }
    

R8 の設定ファイル

R8 では ProGuard ルールファイルを通じて、そのデフォルトの動作を変更したり、アプリの構造(アプリのコードへのエントリ ポイントとして機能するクラスなど)を正確に把握したりできます。これらのルールファイルの一部は変更できますが、ルールにはコンパイル時ツール(AAPT2 など)によって自動的に生成されるものや、アプリのライブラリ依存関係から継承されるものもあります。次の表で、R8 が使用する ProGuard ルールファイルのソースについて説明します。

ソース 場所 説明
Android Studio <module-dir>/proguard-rules.pro Android Studio を使用して新しいモジュールを作成すると、IDE がそのモジュールのルート ディレクトリに proguard-rules.pro ファイルを作成します。

デフォルトでは、このファイルはルールを適用しません。そのため、カスタムの保持ルールなどの独自の ProGuard ルールをこのファイルに含めます。

Android Gradle プラグイン コンパイル時に Android Gradle プラグインによって生成されます。 Android Gradle プラグインは proguard-android-optimize.txt を生成します。このファイルには多くの Android プロジェクトにとって有用なルールが記載されます。また、このファイルによって @Keep* アノテーションが有効化されます。

デフォルトでは、Android Studio を使用して新しいモジュールを作成すると、リリースビルドにおいてこのルールファイルがモジュール レベルの build.gradle ファイルに追加されます。

注: Android Gradle プラグインには定義済みの追加の ProGuard ルールファイルが含まれていますが、proguard-android-optimize.txt を使用することをおすすめします。

ライブラリ依存関係 AAR ライブラリ: <library-dir>/proguard.txt

JAR ライブラリ: <library-dir>/META-INF/proguard/

AAR ライブラリが独自の ProGuard ルールファイルで公開される場合、コンパイル時の依存関係としてその AAR を含めると、プロジェクトのコンパイル時にそのルールが R8 によって自動的に適用されます。

AAR ライブラリにパッケージ化されているルールファイルを使用すると、ライブラリが適切に機能するうえで特定の保持ルールが必要な場合(つまり、ライブラリのデベロッパーがトラブルシューティングの手順を行った場合)に便利です。

ただし、ProGuard ルールは付加的なルールであるため、AAR ライブラリの依存関係に含まれている特定のルールは削除できず、アプリの他の部分のコンパイルに影響する可能性があることに注意する必要があります。たとえば、コードの最適化を無効にするルールがライブラリに含まれている場合、そのルールによってプロジェクト全体の最適化が無効化されます。

Android Asset Package Tool 2(AAPT2) minifyEnabled true と指定してプロジェクトをビルドした後: <module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txt AAPT2 は、アプリのマニフェスト、レイアウト、その他のアプリのリソースでのクラスへの参照に基づいて保持ルールを生成します。たとえば AAPT2 には、アプリのマニフェストでエントリ ポイントとして登録する各アクティビティの保持ルールが含まれています。
カスタムの設定ファイル デフォルトでは、Android Studio を使用して新しいモジュールを作成すると、IDE が <module-dir>/proguard-rules.pro を作成して独自のルールを追加します。 追加の設定を含めることができ、R8 がコンパイル時にその設定を適用します。

minifyEnabled プロパティを true に設定すると、上記の使用可能なすべてのソースのルールが R8 によって結合されます。他のコンパイル時の依存関係(ライブラリの依存関係など)により、デベロッパーが把握していない R8 の動作変更が行われる可能性があるため、R8 でのトラブルシューティングの際にはこの点に注意してください。

R8 がプロジェクトのビルド時に適用するすべてのルールの完全なレポートを出力するには、モジュールの proguard-rules.pro ファイルに以下を追加します。

// You can specify any path and filename.
    -printconfiguration ~/tmp/full-r8-config.txt
    

設定の追加

Android Studio を使用して新しいプロジェクトまたはモジュールを作成すると、IDE によって <module-dir>/proguard-rules.pro ファイルが作成され、独自のルールを追加できます。また、他のファイルからルールを追加することもできます(モジュールの build.gradle ファイルで proguardFiles プロパティにルールを追加します)。

たとえば、対応する productFlavor ブロックに別の proguardFiles プロパティを追加することで、各ビルド バリアントに固有のルールを追加できます。次の Gradle ファイルでは、flavor2-rules.proflavor2 プロダクト フレーバーに追加しています。release ブロックのルールも適用されるため、flavor2 では 3 つの ProGuard ルールがすべて使用されます。

android {
        ...
        buildTypes {
            release {
                minifyEnabled true
                proguardFiles getDefaultProguardFile(
                  'proguard-android-optimize.txt'),
                  // List additional ProGuard rules for the given build type here. By default,
                  // Android Studio creates and includes an empty rules file for you (located
                  // at the root directory of each module).
                  'proguard-rules.pro'
            }
        }
        flavorDimensions "version"
        productFlavors {
            flavor1 {
              ...
            }
            flavor2 {
                proguardFile 'flavor2-rules.pro'
            }
        }
    }
    

コードの圧縮

minifyEnabled プロパティを true に設定すると、R8 によるコードの圧縮がデフォルトで有効になります。

コードの圧縮(ツリー シェイキングとも呼ばれています)とは、R8 が実行の際に不要と判断したコードを削除するプロセスです。たとえば、アプリに多数のライブラリ依存関係が含まれているものの、その機能のごく一部しか使用されていない場合、このプロセスによってアプリのサイズを大幅に縮小できます。

アプリのコードを圧縮するにあたり、R8 はまず、一連の設定ファイルを基に、アプリのコードへのエントリ ポイントをすべて特定します。これらのエントリ ポイントには、Android プラットフォームがアプリのアクティビティやサービスを開始するために使用する可能性があるクラスがすべて含まれています。R8 は各エントリ ポイントからアプリのコードを検査し、アプリが実行時にアクセスする可能性があるすべてのメソッド、メンバー変数、その他のクラスのグラフを作成します。このグラフに接続されないコードは到達不能とみなされ、アプリから削除される可能性があります。

図 1 は、ランタイム ライブラリの依存関係を持つアプリを示しています。R8 はアプリのコードの検査中に、foo()faz()bar() の各メソッドが MainActivity.class エントリ ポイントから到達可能かどうかを確認します。一方、クラス OkayApi.class またはそのメソッド baz() は実行時にアプリで使用されることがないため、アプリの圧縮時にそのコードが R8 によって削除されます。

図 1. R8 はコンパイル時に、プロジェクトに結合されている保持ルールに基づいてグラフを作成し、到達不能なコードを特定します。

R8 は、プロジェクトの R8 設定ファイル-keep ルールを通じてエントリ ポイントを特定します。つまり、アプリの圧縮時に R8 が破棄してはならないクラスを保持ルールで指定し、R8 はそれらのクラスをアプリへの潜在的なエントリ ポイントとみなします。Android Gradle プラグインと AAPT2 は、ほとんどのアプリ プロジェクト(アプリのアクティビティ、ビュー、サービスなど)で必要な保持ルールを自動的に生成します。ただし、追加の保持ルールを使用してこのデフォルトの動作をカスタマイズする必要がある場合は、保持するコードのカスタマイズをご覧ください。

アプリのリソースのサイズを小さくすることにのみ関心がある場合は、リソースの圧縮にお進みください。

保持するコードのカスタマイズ

使用されていないコードのみを R8 で削除する場合、通常はデフォルトの ProGuard ルールファイル(proguard-android- optimize.txt)で十分です。ただし、R8 で正確に分析することが困難な場合もあり、アプリが実際に必要とするコードが削除されてしまうこともあります。次のようなケースでは、R8 が誤ってコードを削除する可能性があります。

  • アプリが Java Native Interface(JNI)からメソッドを呼び出す場合
  • アプリが実行時にコードを検索する場合(リフレクションの使用時など)

アプリのテストでは、不適切に削除されたコードが原因で発生したエラーを明らかにする必要があります。また、削除されたコードのレポートを生成することで、どのコードが削除されたかを調べることもできます。

エラーを修正し、R8 が特定のコードを削除しないようにするには、ProGuard ルールファイルに -keep の行を追加します。以下に例を示します。

-keep public class MyClass
    

また、保持するコードに @Keep アノテーションを追加することもできます。クラスに @Keep を追加すると、クラス全体がそのまま保持されます。このアノテーションをメソッドまたはフィールドに追加すると、メソッドまたはフィールド(およびその名前)に加え、クラス名もそのまま保持されます。このアノテーションを使用できるのは、AndroidX Annotations Library を使用している場合と、圧縮の有効化についてのセクションで説明したように、Android Gradle プラグインにパッケージ化されている ProGuard ルールファイルを追加した場合だけです。

-keep オプションを使用する場合は、さまざまな点について考慮する必要があります。ルールファイルのカスタマイズについて詳しくは、ProGuard マニュアルをご覧ください。トラブルシューティングのセクションに、コードを削除したときに発生する可能性があるその他の一般的な問題の概要が記載されています。

リソースの圧縮

リソースの圧縮は、必ずコードの圧縮と連動して機能します。コード圧縮ツールによって未使用のコードがすべて削除された後、リソース圧縮ツールで、アプリがまだ使用しているリソースを特定できます。これは、リソースが含まれているコード ライブラリを追加した場合に特に当てはまります。使用されていないライブラリ コードを削除すると、ライブラリ リソースが参照されなくなり、リソース圧縮ツールでライブラリ リソースを削除できるようになります。

リソースの圧縮を有効にするには、build.gradle ファイルで shrinkResources プロパティ(コードを圧縮する場合は minifyEnabled も)を true に設定します。以下に例を示します。

android {
        ...
        buildTypes {
            release {
                shrinkResources true
                minifyEnabled true
                proguardFiles getDefaultProguardFile('proguard-android.txt'),
                        'proguard-rules.pro'
            }
        }
    }
    

リソースの削除を開始する前に、動的に作成または呼び出されるクラスまたはメソッドを保持するために proguard-rules.pro ファイルの編集が必要になることがあるため、コード圧縮用の minifyEnabled を使ってアプリをまだビルドしていない場合は、shrinkResources を有効にする前に試してみてください。

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

特定のリソースを保持または破棄したい場合は、<resources> タグを含む XML ファイルをプロジェクトで作成し、保持するリソースを 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" />
    

このファイルをプロジェクト リソースに保存します(例: res/raw/keep.xml)。このファイルは、ビルドで APK にパッケージ化されません。

リソースを削除できる場合、破棄するリソースを指定することは意味がないように思えますが、この作業はビルド バリアントを使用するときに役に立つ場合があります。たとえば、特定のリソースがコードで使用されているが(したがって、圧縮ツールによって削除されない)、特定のビルド バリアントに対しては実際に使用されないことがわかっている場合は、すべてのリソースを共通のプロジェクト ディレクトリに格納してから、各ビルド バリアントに対して別の keep.xml ファイルを作成します。また、ビルドツールが誤ってリソースを必要であると識別する可能性もあります。これは、コンパイラがリソース ID をインラインで追加したことで、元来参照されるリソースと、偶然同じ値になったコード内の整数値の違いをリソース アナライザが認識できないことが原因で発生する可能性があります。

厳密な参照チェックの有効化

通常、リソース圧縮ツールでは、リソースが使用されるかどうかを正確に特定することができます。ただし、コードが Resources.getIdentifier() への呼び出しを実行する場合(または、いずれかのライブラリ(AppCompat ライブラリなど)がこの呼び出しを実行する場合)、コードは、動的に生成された文字列に基づいてリソース名を検索します。この処理が実行されると、リソース圧縮ツールがデフォルトで防御的に動作し、名前の形式が一致するすべてのリソースを、使用される可能性があるリソースとしてマークするため、それらのリソースを削除できなくなります。

たとえば次のコードは、img_ 接頭辞が付いたすべてのリソースを使用されるリソースとしてマークします。

Kotlin

    val name = String.format("img_%1d", angle + 1)
    val res = resources.getIdentifier(name, "drawable", packageName)
    

Java

    String name = String.format("img_%1d", angle + 1);
    res = getResources().getIdentifier(name, "drawable", getPackageName());
    

また、リソース圧縮ツールはコード内のすべての文字列定数と各種の res/raw/ リソースを調べて、file:///android_res/drawable//ic_plus_anim_016.png と類似した形式のリソース URL を検索します。このツールでは、このような文字列またはこのような URL の作成に使用されそうな他の文字列を検出した場合、それらの文字列を削除しません。

これらは、デフォルトで有効になっている安全な圧縮モードの例です。ただし、この「安全第一」の処理を無効にして、リソース圧縮ツールで、確実に使用されるリソースのみを保持するように指定することができます。そのためには、次のように keep.xml ファイルで shrinkModestrict に設定します。

    <?xml version="1.0" encoding="utf-8"?>
    <resources xmlns:tools="http://schemas.android.com/tools"
        tools:shrinkMode="strict" />
    

厳密な圧縮モードを有効にしており、上記のように、動的に生成された文字列のリソースをコードが参照する場合は、tools:keep 属性を使用して、これらのリソースを手動で保持する必要があります。

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

Gradle のリソース圧縮ツールは、アプリのコードが参照しないリソースのみを削除します。これは、異なるデバイス設定の代替リソースは削除されないことを意味します。必要に応じて、Android Gradle プラグインの resConfigs プロパティを使用して、アプリが必要としない代替リソース ファイルを削除することができます。

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

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

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

同様に、それぞれが異なるデバイス設定を対象とする複数の APK をビルドすることで、APK に含める画面密度または ABI リソースをカスタマイズできます。

重複リソースの結合

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

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

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

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

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

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

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

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

コードの難読化

難読化の目的は、アプリのクラス、メソッド、フィールドの名前を短くすることで、アプリのサイズを小さくすることにあります。以下に、R8 を使用した難読化の例を示します。

androidx.appcompat.app.ActionBarDrawerToggle$DelegateProvider -> a.a.a.b:
    androidx.appcompat.app.AlertController -> androidx.appcompat.app.AlertController:
        android.content.Context mContext -> a
        int mListItemLayout -> O
        int mViewSpacingRight -> l
        android.widget.Button mButtonNeutral -> w
        int mMultiChoiceItemLayout -> M
        boolean mShowTitle -> P
        int mViewSpacingLeft -> j
        int mButtonPanelSideLayout -> K
    

難読化ではアプリからコードを削除しませんが、多数のクラス、メソッド、フィールドをインデックスに登録する DEX ファイルにより、アプリのサイズを大幅に縮小できます。ただし、難読化ではコードのさまざまな部分の名前を変更するため、スタック トレースの検査などの特定のタスクで追加のツールが必要になります。難読化後のスタック トレースについて詳しくは、次のセクションで難読化されたスタック トレースをデコードする方法をご確認ください。

また、アプリのメソッドやクラスに予測可能な命名規則を使用している場合(リフレクションを使用している場合など)、それらのシグネチャをエントリ ポイントとして扱い、保持するコードのカスタマイズ方法についてのセクションで説明したように保持ルールを指定する必要があります。保持ルールでは、アプリの最終的な DEX 内でそのコードを保持するだけでなく、その元の命名規則を維持するように R8 に指示します。

難読化されたスタック トレースのデコード

R8 でコードを難読化した後、クラスとメソッドの名前が変更されている可能性があるため、スタック トレースを理解することは(不可能ではないにしても)困難です。R8 は名前を変更するだけではありません。DEX ファイルを作成する際にスタック トレース内の行数を変更することで、アプリのサイズをさらに小さくすることができます。ありがたいことに、R8 は実行されるたびに mapping.txt ファイルを作成します。このファイルには、難読化されたクラス、メソッド、フィールドの名前と元の名前のマッピングが記載されます。また、行数を元のソースファイルの行数にマッピングし直すための情報も記載されます。R8 はこのファイルを <module- name>/build/outputs/mapping/<build-type>/ ディレクトリに保存します。

Google Play でアプリを公開するときには、各バージョンの APK の mapping.txt ファイルをアップロードできます。Google Play によって、ユーザーが報告した問題のスタック トレースが難読化解除されるため、Google Play Console で問題を調べることができます。詳しくは、クラッシュのスタック トレースを難読化解除する方法に関するヘルプセンターの記事をご覧ください。

難読化されたスタック トレースを読み取り可能なスタック トレースに自分で変換するには、ProGuard にパッケージ化されている ReTrace スクリプトを使用します。

コードの最適化

アプリのサイズをさらに小さくするために、R8 はコードをより深いレベルで検査して、使用されていないコードをさらに削除するか、可能であればコードを書き直して冗長性を減らします。以下に、こうした最適化の例を示します。

  • 特定の if / else ステートメントの else {} 分岐に入ることが絶対にない場合、R8 は else {} 分岐のコードを削除することがあります。
  • あるメソッドがコード内の 1 か所だけで呼び出されている場合、R8 はそのメソッドを削除して、1 つの呼び出しサイトにインライン化することがあります。
  • クラスに一意のサブクラスが 1 つだけあり、クラス自体はインスタンス化されないことが R8 で確認された場合(1 つの具象実装クラスでのみ使用される抽象型基本クラスなど)、R8 はその 2 つのクラスを結合して、1 つのクラスをアプリから削除することがあります。
  • 詳しくは、Jake Wharton 氏による R8 の最適化に関するブログ投稿をご覧ください。

R8 では、個々の最適化を有効または無効にしたり、最適化の動作を変更したりすることはできません。実際には、R8 はデフォルトの最適化を変更しようとする ProGuard ルール(-optimizations- optimizationpasses など)を無視します。この制限は重要です。なぜなら、R8 は改良が続けられていますが、最適化の標準的な動作を維持することで、デベロッパーが直面する可能性がある問題を Android Studio チームが容易にトラブルシューティングして解決できるようになるからです。

より積極的な最適化の有効化

R8 には、デフォルトでは有効になっていない追加の最適化セットが含まれています。プロジェクトの gradle.properties ファイルに以下の行を含めることで、それらの追加の最適化を有効にすることができます。

android.enableR8.fullMode=true
    

追加の最適化を有効にすることで R8 の動作が ProGuard とは異なる動作になるため、実行時に問題が発生しないようにするために ProGuard ルールの追加が必要になることがあります。たとえば、コードがあるクラスを Java Reflection API を介して参照するとします。R8 はデフォルトでは、デベロッパーがそのクラスのオブジェクトを実行時に確認して操作するつもりであると仮定し(コードが実際にそうなっていなくても)、クラスとその静的初期化子を自動的に保持します。

ただし、「完全モード」を使用している場合、R8 はこのような仮定を行いません。R8 は、コードが実行時にクラスを使用することが絶対にないと判断すると、アプリの最終的な DEX からそのクラスを削除します。つまり、クラスとその静的初期化子を保持したい場合は、ルールファイルに保持ルールを追加してそのように指定する必要があります。

R8 の「完全モード」の使用中に問題が発生した場合は、R8 FAQ ページで考えられる解決策を確認してください。問題を解決できない場合は、バグを報告してください。

R8 でのトラブルシューティング

このセクションでは、R8 による圧縮、難読化、最適化を有効にした場合のトラブルシューティング戦略について説明します。以下の問題に対する解決策が見つからない場合は、R8 FAQ ページおよび ProGuard のトラブルシューティング ガイドもご覧ください。

削除(または保持)されたコードのレポートの生成

特定の R8 の問題をトラブルシューティングする際に、R8 がアプリから削除したすべてのコードのレポートを見ると役に立つ場合があります。このレポートを生成するモジュールごとに、-printusage <output-dir>/usage.txt をカスタムのルールファイルに追加します。R8 を有効化してアプリをビルドすると、指定したファイル名のレポートが指定したパスに R8 から出力されます。削除されたコードのレポートは次のようになります。

androidx.drawerlayout.R$attr
    androidx.vectordrawable.R
    androidx.appcompat.app.AppCompatDelegateImpl
        public void setSupportActionBar(androidx.appcompat.widget.Toolbar)
        public boolean hasWindowFeature(int)
        public void setHandleNativeActionModesEnabled(boolean)
        android.view.ViewGroup getSubDecor()
        public void setLocalNightMode(int)
        final androidx.appcompat.app.AppCompatDelegateImpl$AutoNightModeManager getAutoNightModeManager()
        public final androidx.appcompat.app.ActionBarDrawerToggle$Delegate getDrawerToggleDelegate()
        private static final boolean DEBUG
        private static final java.lang.String KEY_LOCAL_NIGHT_MODE
        static final java.lang.String EXCEPTION_HANDLER_MESSAGE_SUFFIX
    ...
    

R8 がプロジェクトの保持ルールから特定したエントリ ポイントのレポートを表示する場合は、カスタムのルールファイルに -printseeds <output-dir>/seeds.txt を含めます。R8 を有効化してアプリをビルドすると、指定したファイル名のレポートが指定したパスに R8 から出力されます。保持されたエントリ ポイントのレポートは次のようになります。

com.example.myapplication.MainActivity
    androidx.appcompat.R$layout: int abc_action_menu_item_layout
    androidx.appcompat.R$attr: int activityChooserViewStyle
    androidx.appcompat.R$styleable: int MenuItem_android_id
    androidx.appcompat.R$styleable: int[] CoordinatorLayout_Layout
    androidx.lifecycle.FullLifecycleObserverAdapter
    ...
    

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

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

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

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

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

16:25:48.005 [QUIET] [system.out] &#64;drawable/add_schedule_fab_icon_anim : reachable=true
    16:25:48.009 [QUIET] [system.out]     &#64;drawable/ic_plus_anim_016
    

ここで、@drawable/add_schedule_fab_icon_anim が到達可能である理由を確認するため、上に向かって調べていくと、そのリソースが「The 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 it format-string matches string pool constant ic_plus_anim_%1$d.
    

これらのいずれかの文字列が表示され、特定のリソースの動的な読み込みにその文字列が使用されていないことが確実である場合、保持するリソースのカスタマイズに関するセクションで説明したように、tools:discard 属性を使用して、そのリソースを削除するようにビルドシステムに指示することができます。