アプリをできるだけ小さくして高速にするには、
isMinifyEnabled = true
でリリースビルドを作成します。
それによって圧縮が有効になり、それによって使用されていないコードとリソースが削除されます。 難読化: アプリのクラスとメンバーの名前を短縮します。および 最適化: より積極的な戦略を適用して、 アプリのパフォーマンスを改善できますこのページでは、R8 が がコンパイル時のタスクを実行します。また、カスタマイズ方法の できます。
Android Gradle プラグイン 3.4.0 以上を使用したプロジェクトのビルドでは、ProGuard によるコンパイル時のコード最適化が行われません。その代わり、R8 コンパイラとの連携により、コンパイル時に以下のタスクが実行されます。
- コードの圧縮(ツリー シェイキング): 使用されていないクラス、フィールド、メソッド、属性を検出し、アプリとそのライブラリ依存関係から安全に削除します(64K 参照制限を回避する有用なツールです)。たとえば、ライブラリ依存関係の API を数個だけ使用している場合、圧縮タスクはアプリが使用していないライブラリ コードを特定し、アプリからそのコードだけを削除できます。詳しくは、コードの圧縮をご覧ください。
- リソースの圧縮: パッケージ化アプリから使用されていないリソース(アプリのライブラリ依存関係で使用されていないリソースなど)を削除します。リソースの圧縮はコードの圧縮と連携して機能するため、使用されないコードが削除されると、参照されなくなったリソースも安全に削除できます。詳しくは、リソースの圧縮をご覧ください。
- 最適化: コードを検査して書き換え、ランタイムを改善する
アプリの DEX ファイルのサイズをさらに小さくすることができます。この
コードの実行時のパフォーマンスが最大 30% 向上し、
フレーム タイミングに関するものです。たとえば、特定の if / else ステートメントの
else {}
分岐を絶対に通らないことが R8 によって検出された場合、R8 はそのelse {}
分岐のコードを削除します。詳しくは、コードの最適化をご覧ください。 - 難読化(識別子の短縮): クラスの名前を短縮します。 DEX ファイルのサイズを縮小できます。詳しくは、 コードを難読化する方法についてのセクションをご覧ください。
アプリのリリース バージョンをビルドする場合、R8 は次の動作を行うように構成できます。 上記のコンパイル時タスクを自動的に実行します。特定のルールを無効にしたり ProGuard ルールファイルを使用して R8 の動作をカスタマイズすることもできます。 実際には、R8 は既存のすべての ProGuard ルールファイルと連携するため、R8 を使用するように Android Gradle プラグインを更新しても、既存のルールを変更する必要はありません。
圧縮、難読化、最適化の有効化
Android Studio 3.4 または Android Gradle プラグイン 3.4.0 以上を使用する場合、R8 がデフォルトのコンパイラになります。R8 はプロジェクトの Java バイトコードを Android プラットフォームで実行可能な DEX 形式に変換します。ただし、Android Studio を使用して新しいプロジェクトを作成する場合、圧縮、難読化、コードの最適化はデフォルトでは有効になりません。これは、こうしたコンパイル時の最適化によってプロジェクトのビルド時間が長くなり、保持するコードのカスタマイズが十分でない場合にバグが発生する可能性があるためです。
そのため、これらのコンパイル時のタスクは、公開前にテストするアプリの最終版をビルドするときに有効にすることをおすすめします。圧縮、難読化、 プロジェクト レベルのビルド スクリプトに以下を含めます。
Kotlin
android { buildTypes { getByName("release") { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `isDebuggable=false`. isMinifyEnabled = true // Enables resource shrinking, which is performed by the // Android Gradle plugin. isShrinkResources = true proguardFiles( // 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. getDefaultProguardFile("proguard-android-optimize.txt"), // Includes a local, custom Proguard rules file "proguard-rules.pro" ) } } ... }
Groovy
android { buildTypes { release { // Enables code shrinking, obfuscation, and optimization for only // your project's release build type. Make sure to use a build // variant with `debuggable false`. 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 ファイルを作成します。
デフォルトでは、このファイルはルールを適用しません。そのため、カスタムの keep ルールなどの独自の ProGuard ルールをこのファイルに含めます。 |
Android Gradle プラグイン | コンパイル時に Android Gradle プラグインによって生成されます。 | Android Gradle プラグインは proguard-android-optimize.txt を生成します。このファイルには多くの Android プロジェクトにとって有用なルールが記載されます。また、このファイルによって @Keep* アノテーションが有効になります。デフォルトでは、Android Studio を使用して新しいモジュールを作成すると、モジュール レベルの ビルド スクリプトがこのルールファイルをリリースビルドに含める できます。
注: Android Gradle プラグインには定義済みの追加の ProGuard ルールファイルが含まれていますが、 |
ライブラリ依存関係 |
AAR ライブラリの場合:
JAR ライブラリの場合: これらの場所に加えて、Android Gradle プラグイン 3.6 以降も ターゲットの圧縮ルールをサポートしている。 |
AAR ライブラリまたは JAR ライブラリが独自のルールファイルで公開されていて、かつ そのライブラリをコンパイル時の依存関係として含めると、R8 が自動的に は、プロジェクトをコンパイルするときに、これらのルールを適用します。 従来の ProGuard ルールに加えて、Android Gradle プラグインは バージョン 3.6 以降では、 ターゲット圧縮ルール。これらはルールです。 特定の圧縮ツール(R8 または ProGuard)を対象とする SDK と、 アップグレードされます。 ライブラリにパッケージ化されているルールファイルを使用すると、 ルールが必要です。つまり、ライブラリは、ライブラリが正常に デベロッパーがトラブルシューティングの手順を実施しました。 ただし、ルールは加算的であるため、 削除できない特定のルールと、 アプリの他の部分のコンパイルに影響することがあります。たとえば、 ライブラリには、コードの最適化を無効にするルールが含まれており、 プロジェクト全体の最適化 について説明します |
Android Asset Package Tool 2(AAPT2) | minifyEnabled true と指定してプロジェクトをビルドした後: <module-dir>/build/intermediates/aapt_proguard_file/.../aapt_rules.txt |
AAPT2 は、アプリのマニフェスト、レイアウト、その他のアプリのリソースでのクラスへの参照に基づいて keep ルールを生成します。たとえば AAPT2 には、デベロッパーがアプリのマニフェストでエントリ ポイントとして登録した各アクティビティの keep ルールが含まれます。 |
カスタム構成ファイル | 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 Gradle プラグイン 3.6 以降がライブラリをサポートしている場合は、ターゲットとなる 特定の圧縮ツール(R8 または ProGuard)と特定の圧縮ツール バージョンです。この プロジェクトで最適に機能するよう、ライブラリ デベロッパーがルールを調整できます 新しい圧縮ツール バージョンを使用する一方で、既存のルールは引き続き 古い圧縮ツール バージョンを使用しているプロジェクトで使用されています。
対象となる圧縮ルールを指定するには、ライブラリのデベロッパーがそれらを含める必要があります 特定の場所に配置する必要があります(下記をご覧ください)。
In an AAR library:
proguard.txt (legacy location)
classes.jar
└── META-INF
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
In a JAR library:
META-INF
├── proguard/<ProGuard-rules-file> (legacy location)
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
つまり、対象となる圧縮ルールは META-INF/com.android.tools
(JAR の META-INF/com.android.tools
ディレクトリに配置)または
AAR の classes.jar
。
そのディレクトリの下には、次の形式の名前を持つ複数のディレクトリを作成できます。
r8-from-<X>-upto-<Y>
または proguard-from-<X>-upto-<Y>
を
ディレクトリ内のルールの記述対象になります。
-from-<X>
部分と -upto-<Y>
部分は省略可能です(<Y>
バージョン)。
そのバージョン範囲は排他的であり、バージョン範囲は連続している必要があります。
たとえば、r8-upto-8.0.0
、r8-from-8.0.0-upto-8.2.0
、r8-from-8.2.0
です。
有効な圧縮ルールのセットを形成します。[Rules] には、
r8-from-8.0.0-upto-8.2.0
ディレクトリは、バージョン 8.0.0 から
ただし、バージョン 8.2.0 は含まれません。
この情報に基づき、Android Gradle プラグイン 3.6 以上では、
ルールを取得します。ライブラリがターゲット
圧縮ルールがある場合、Android Gradle プラグインは従来の圧縮ルールの
地域(AAR の場合は proguard.txt
、
JAR の場合は META-INF/proguard/<ProGuard-rules-file>
)。
ライブラリのデベロッパーは、ターゲット圧縮ルールまたは従来の圧縮ルールのいずれかを選択できます ライブラリ内の ProGuard ルール、または維持する場合は両方のタイプ 3.6 より古い Android Gradle プラグインやその他のツールとの互換性がある。
構成の追加
Android Studio を使用して新しいプロジェクトまたはモジュールを作成すると、IDE によって作成される <module-dir>/proguard-rules.pro
に独自のルールを追加できるようになります。マイページ
また、他のファイルのルールをファイルに追加して、
proguardFiles
プロパティ。
たとえば、対応する productFlavor
ブロックに別の proguardFiles
プロパティを追加することで、各ビルド バリアントに固有のルールを追加できます。次の Gradle ファイルで、flavor2-rules.pro
を flavor2
プロダクト フレーバーに追加します。release
ブロックのルールも適用されるため、flavor2
で 3 つの ProGuard ルールがすべて使用されます。
さらに、testProguardFiles
プロパティを追加して、
テスト APK のみに含まれる ProGuard ファイルのリスト。
Kotlin
android { ... buildTypes { getByName("release") { isMinifyEnabled = 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" ) testProguardFiles( // The proguard files listed here are included in the // test APK only. "test-proguard-rules.pro" ) } } flavorDimensions.add("version") productFlavors { create("flavor1") { ... } create("flavor2") { proguardFile("flavor2-rules.pro") } } }
Groovy
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' testProguardFiles // The proguard files listed here are included in the // test APK only. 'test-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 によって削除されます。
R8 は、プロジェクトの R8 構成ファイルの -keep
ルールを通じてエントリ ポイントを特定します。つまり、アプリの圧縮時に R8 が破棄してはならないクラスを keep ルールで指定すると、R8 はそれらのクラスをアプリへの想定されるエントリ ポイントとみなします。Android Gradle プラグインと AAPT2 は、ほとんどのアプリ プロジェクト(アプリのアクティビティ、ビュー、サービスなど)について必要な keep ルールを自動的に生成します。ただし、他にも keep ルールを追加してこのデフォルトの動作をカスタマイズする必要がある場合は、保持するコードのカスタマイズをご覧ください。
カスタマイズではなく、アプリのリソースのサイズを小さくすることにのみ関心がある場合は、リソースの圧縮にお進みください。
ライブラリ プロジェクトが縮小されると、そのライブラリに依存するアプリが Shrunk ライブラリ クラスが含まれます。次の場合は、ライブラリの Keep ルールの調整が必要になることがあります。 ライブラリ APK 内に不足しているクラスがある。ニュースメディアとして AAR 形式のライブラリ、ライブラリが依存するローカル JAR ファイルは AAR ファイルで縮小できました
保持するコードのカスタマイズ
ほとんどの場合、デフォルトの ProGuard ルールファイル(proguard-android-optimize.txt
)
R8 が未使用のコードのみを削除するのに十分です。ただし、R8 で正確に分析することが困難な場合もあり、アプリが実際に必要とするコードが削除されてしまうこともあります。以下のようなケースでは、R8 が誤ってコードを削除する可能性があります。
- アプリが Java Native Interface(JNI)からメソッドを呼び出す場合
- アプリが実行時にコードを検索する場合(リフレクションの使用時など)
アプリをテストすることで不適切に削除されたコードに起因するエラーが明らかになりますが、削除されたコードのレポートを生成すると、どのコードが削除されたかを調べることができます。
エラーを修正し、R8 が特定のコードを削除しないようにするには、ProGuard ルールファイルに -keep
の行を追加します。次に例を示します。
-keep public class MyClass
また、保持するコードに @Keep
アノテーションを追加することもできます。クラスに @Keep
を追加すると、クラス全体がそのまま保持されます。このアノテーションをメソッドまたはフィールドに追加すると、メソッドまたはフィールド(およびその名前)に加え、クラス名もそのまま保持されます。このアノテーションを使用できるのは、AndroidX Annotations Library を使用している場合と、圧縮の有効化で説明したように、Android Gradle プラグインにパッケージ化されている ProGuard ルールファイルを追加した場合だけです。
-keep
オプションを使用する場合は、さまざまな点について考慮する必要があります。ルールファイルのカスタマイズについて詳しくは、ProGuard マニュアルをご覧ください。トラブルシューティングのセクションに、コードを削除したときに発生する可能性があるその他の一般的な問題について概説されています。
ネイティブ ライブラリのストリップ
アプリのリリースビルド内のネイティブ コード ライブラリは、デフォルトでストリップされます。これにより、アプリで使用されるネイティブ ライブラリに含まれるシンボル テーブルとデバッグ情報が削除されます。ネイティブ コード ライブラリをストリップすると、サイズが大幅に削減されますが、情報(クラス名、関数名など)がないため、Google Play Console でクラッシュを診断できなくなります。
ネイティブ コードでのクラッシュのサポート
Google Play Console の Android Vitals で、ネイティブ コードでのクラッシュが報告されます。簡単な手順で、アプリのネイティブ デバッグ シンボル ファイルを生成してアップロードできます。このファイルがあると、ネイティブ コードでのクラッシュのスタック トレースに対して Android Vitals でシンボリケーション(クラス名や関数名を含む)が可能になり、本番環境でアプリをデバッグできます。この手順は、プロジェクトで使用される Android Gradle プラグインのバージョンと、プロジェクトのビルド出力によって異なります。
Android Gradle プラグイン バージョン 4.1 以降
プロジェクトで Android App Bundle をビルドする場合は、その中にネイティブ デバッグ シンボル ファイルを自動的に含めることができます。このファイルをリリースビルドに含めるには、アプリの build.gradle.kts
ファイルに次の行を追加します。
android.buildTypes.release.ndk.debugSymbolLevel = { SYMBOL_TABLE | FULL }
以下から、デバッグ シンボルのレベルを選択します。
SYMBOL_TABLE
を使用して Play Console のシンボリケーション スタック トレース内の関数名を取得します。 このレベルは Tombstone に対応しています。FULL
を使用して Play Console のシンボリケーション スタック トレース内の関数名、ファイル、行番号を取得します。
プロジェクトで APK をビルドする場合は、前述の build.gradle.kts
ビルド設定を使用して、ネイティブ デバッグ シンボル ファイルを別個に生成します。手動で Google Play Console にネイティブ デバッグ シンボル ファイルをアップロードします。Android Gradle プラグインはビルドプロセスの一環として、このファイルをプロジェクトの次の場所に出力します。
app/build/outputs/native-debug-symbols/variant-name/native-debug-symbols.zip
Android Gradle プラグイン バージョン 4.0 以前(とその他のビルドシステム)
Android Gradle プラグインはビルドプロセスの一環として、ストリップしていないライブラリのコピーをプロジェクト ディレクトリに保持します。 このディレクトリ構造は次のようになります。
app/build/intermediates/cmake/universal/release/obj/
├── armeabi-v7a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── arm64-v8a/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
├── x86/
│ ├── libgameengine.so
│ ├── libothercode.so
│ └── libvideocodec.so
└── x86_64/
├── libgameengine.so
├── libothercode.so
└── libvideocodec.so
次のようにこのディレクトリの内容を圧縮します。
cd app/build/intermediates/cmake/universal/release/obj
zip -r symbols.zip .
Google Play Console に手動で
symbols.zip
ファイルをアップロードします。
リソースの圧縮
リソースの圧縮は、必ずコードの圧縮と連動して機能します。コード圧縮ツールによって未使用のコードがすべて削除された後、リソース圧縮ツールで、アプリがまだ使用するリソースを特定できます。これは、リソースが含まれるコード ライブラリを追加した場合に特に当てはまります。使用されないライブラリ コードを削除すると、ライブラリ リソースが参照されなくなり、リソース圧縮ツールでライブラリ リソースを削除できるようになります。
リソースの圧縮を有効にするには、shrinkResources
プロパティを設定します。
ビルド スクリプト内の true
に(
minifyEnabled
(コードの圧縮の場合)例:
Kotlin
android { ... buildTypes { getByName("release") { isShrinkResources = true isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" ) } } }
Groovy
android { ... buildTypes { release { shrinkResources true minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
コード圧縮用の minifyEnabled
を使ってアプリをまだビルドしていない場合は、先にビルドを試してから shrinkResources
を有効にしてください。これは、リソースの削除を開始する前に、動的に作成または呼び出されるクラスまたはメソッドを保持するために proguard-rules.pro
ファイルの編集が必要になることがあるからです。
保持するリソースのカスタマイズ
特定のリソースを保持または破棄したい場合、<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/my.package.keep.xml
)。このファイルは、ビルドでアプリにパッケージ化されません。
注: keep
ファイルには一意の名前を使用してください。日時
リンクされると、Keep のルールが競合する
その結果、ルールの無視や不要な保持の問題が発生する可能性があります。
説明します。
リソースを直接削除できるのに、そうせずに破棄するリソースを指定することは意味がないように思えますが、この作業はビルド バリアントを使用するときに役に立つ場合があります。対象
たとえば、すべてのリソースを共通のプロジェクト ディレクトリに配置し、
次に、それぞれ別の my.package.build.variant.keep.xml
ファイルを作成します。
特定のリソースがコードで使用されていると考えられる場合に、ビルド バリアントとして認識されます。
(そのため圧縮ツールによって削除されません)が、実際には
使用されているため。また、ビルドツールが Cloud Logging に
誤ってリソースが特定された可能性があります。これは、リソースの
コンパイラがリソース 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
ファイルで shrinkMode
を strict
に設定します。
<?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 開発者サービスなど)を使用している場合、アプリの残りの部分が同じ言語に翻訳されるかどうかに関係なく、アプリには、これらのライブラリにあるメッセージのすべての翻訳言語の文字列が含まれます。アプリが公式にサポートする言語のみを保持する場合は、resConfig
プロパティを使用してそれらの言語を指定できます。指定されていない言語のリソースは削除されます。
次のスニペットは、言語リソースを英語とフランス語のみに限定する方法を示します。
Kotlin
android { defaultConfig { ... resourceConfigurations.addAll(listOf("en", "fr")) } }
Groovy
android { defaultConfig { ... resConfigs "en", "fr" } }
Android App Bundle 形式でアプリをリリースする場合、デフォルトでは、ユーザーのデバイスに設定されている言語だけがアプリのインストール時にダウンロードされます。同様に、デバイスの画面密度に一致するリソースと、デバイスの ABI に一致するネイティブ ライブラリだけがダウンロードに含まれます。詳しくは、Android App Bundle の構成をご覧ください。
APK を使用して旧式アプリ(2021 年 8 月より前に作成)をリリースする場合は、それぞれが異なるデバイス設定を対象とする複数の APK をビルドすることで、APK に含める画面密度や ABI リソースをカスタマイズできます。
重複リソースの結合
Gradle はさらにデフォルトで、異なるリソース フォルダに存在する同じ名前のドローアブルなど、同一の名前を持つリソースを結合します。この動作は、shrinkResources
プロパティによって制御することも、無効にすることもできません。これは、コードが検索している名前に一致するリソースが複数あったときのエラーを回避するために必要な動作です。
リソースの結合は、2 つ以上のファイルが同一のリソース名、タイプ、修飾子を共有している場合にのみ行われます。Gradle は(下記の優先順位に基づいて)、重複ファイルのうち最適と判断したファイルを選択し、最終的なアーティファクトでの配布用にそのリソースひとつだけを AAPT に渡します。
Gradle は次の場所で重複リソースを検索します。
- メインリソース。メイン ソースセットと関連付けられていて、通常は
src/main/res/
にあります。 - ビルドタイプとビルド フレーバーからなるバリアント オーバーレイ。
- ライブラリ プロジェクトの依存関係。
Gradle は、次の優先順位に従って重複リソースを結合します。
依存関係 → メイン → ビルド フレーバー → ビルドタイプ
たとえば、メインリソースとビルド フレーバーの両方に重複リソースがある場合、Gradle はビルド フレーバー内のリソースを選択します。
同じソースセットに同一のリソースがある場合、Gradle はこれらのリソースを結合できないため、リソース結合エラーを出力します。このエラーは、build.gradle.kts
ファイルの 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 ファイルを持つアプリのサイズを大幅に縮小できます。ただし、難読化によりコードのさまざまな部分の名前が変更されるので、スタック トレースの検査などの特定のタスクで追加のツールが必要になります。難読化後のスタック トレースについて詳しくは、難読化されたスタック トレースのデコード方法についてのセクションをご確認ください。
また、アプリのメソッドやクラスに予測可能な命名規則を使用している場合(リフレクションを使用している場合など)、それらのシグネチャをエントリ ポイントとして扱い、保持するコードのカスタマイズ方法で説明したように keep ルールを指定する必要があります。keep ルールは、アプリの最終的な DEX 内でそのコードだけでなく、その元の命名規則も維持するように R8 に指示します。
難読化されたスタック トレースのデコード
R8 でコードを難読化すると、クラス名とメソッド名が変更される可能性があるため、スタック トレースを理解することは(不可能ではないにしても)困難になります。元のスタック トレースを取得するには、スタック トレースの再トレースが必要です。
コードの最適化
アプリをさらに最適化するために、R8 はコードをより深いレベルで検査します。 未使用のコードを削除するか、可能であれば、コードを書き直して 簡潔にします。以下に、こうした最適化の例を示します。
- コードが特定の if / else ステートメントの
else {}
分岐を絶対に通らない場合、R8 はelse {}
分岐のコードを削除することがあります。 - コードが少数の場所でのみメソッドを呼び出す場合、R8 によってメソッドが削除されることがあります。 それを少数のコールサイトにインラインで記述します。
- あるクラスに固有のサブクラスが 1 つだけあり、クラス自体はインスタンス化されないことが R8 で確認された場合(1 つの具象実装クラスでのみ使用される抽象型基本クラスなど)、R8 はその 2 つのクラスを結合して、1 つのクラスをアプリから削除することがあります。
- 詳しくは、Jake Wharton 氏による R8 の最適化に関するブログ投稿をご覧ください。
R8 に対して、個々の最適化を有効または無効にしたり、最適化の動作を変更したりすることはできません。実際、R8 は、変更を試みる ProGuard ルールを無視します。
-optimizations
や
-optimizationpasses
。この制限は重要です。なぜなら、R8 は改良が続けられていますが、最適化の標準的な動作を維持することで、デベロッパーが直面する可能性がある問題を Android Studio チームが容易にトラブルシューティングして解決できるようになるからです。
なお、最適化を有効にすると、アプリのスタック トレースが変更されます。たとえば、インライン化によりスタック フレームが削除されます。元のスタック トレースを取得する方法については、再トレースに関するセクションをご覧ください。
ランタイム パフォーマンスへの影響
圧縮、難読化、最適化をすべて有効にした場合、R8 は改善します。 コードのランタイム パフォーマンス(UI スレッドでの起動時間やフレーム時間など) 最大 30%削減できますこれらのいずれかを無効にすると、一連の最適化が大幅に制限されます。 R8 が使用する。
R8 が有効になっている場合は、 起動プロファイルを作成する 起動のパフォーマンスをさらに高めることができます
より積極的な最適化の有効化
R8 には、一連の追加の最適化(「完全モード」と呼ばれます)が備わっています。 ProGuard の動作とは異なります。こうした最適化は デフォルト Android Gradle プラグイン バージョン 8.0.0。
これらの追加の最適化を無効にするには、次の行を
プロジェクトの gradle.properties
ファイルに追加します。
android.enableR8.fullMode=false
この追加の最適化により R8 の動作が ProGuard と異なるため、 ランタイムを回避するために、追加の ProGuard ルールをインクルードする必要がある ProGuard 用に設計されたルールを使用している場合に発生する問題です。たとえば、 Java Reflection API を通じてクラスを参照します。使用しない場合 「フルモード」R8 では、ユーザーが Cloud Storage、BigQuery、 (コードで実際にはそうでなくても)実行時にそのクラスを クラスとその静的イニシャライザを保持します。
ただし、「フルモード」を使用する場合、R8 はこの前提にはなりません。R8 が 他の方法ではコードが実行時にクラスを使用しないことをアサートすると、 クラスを削除します。つまり、クラスとそのクラスの変数を 静的イニシャライザを使用するには、ルールファイルに keep ルールを できます。
R8 の「フルモード」の使用中に問題が発生した場合は、 R8 に関するよくある質問のページ いくつか見てみましょう問題を解決できない場合は、バグを報告してください。
スタック トレースの再トレース
R8 で処理されるコードがさまざまな方法で変更されると、スタック トレースがソースコードと完全には一致しなくなるため、スタック トレースを理解することがより困難になります。デバッグ情報が保持されていない場合の行番号の変更もこれに当てはまることがあります。インライン化やアウトライン化など、最適化が原因となる場合もあります。最も大きい原因は、クラスやメソッドでも名前が変更される難読化です。
元のスタック トレースを復元するには、R8 の retrace コマンドライン ツールを利用します。このツールはコマンドライン ツール パッケージにバンドルされています。
アプリのスタック トレースの再トレースに対応するには、モジュールの proguard-rules.pro
ファイルに次のルールを追加して、ビルドに再トレースのための十分な情報が保持されるようにする必要があります。
-keepattributes LineNumberTable,SourceFile
-renamesourcefileattribute SourceFile
LineNumberTable
属性は、それぞれの位置がスタック トレースに出力されるようメソッドに位置情報を保持します。SourceFile
属性は、可能性のあるすべてのランタイムで実際に位置情報が出力されるようにします。-renamesourcefileattribute
は、スタック トレース内のソースファイル名を単に SourceFile
に設定するディレクティブです。マッピング ファイルには元のソースファイルが含まれているため、追跡する際に実際の元のファイル名は必要ありません。
R8 は実行されるたびに mapping.txt
ファイルを作成します。このファイルに、スタック トレースを元のスタック トレースにマッピングし戻すために必要な情報が含まれます。Android Studio はこのファイルを <module-name>/build/outputs/mapping/<build-type>/
ディレクトリに保存します。
Google Play でアプリを公開するときに、各バージョンのアプリの mapping.txt
ファイルをアップロードできます。Android App Bundle を使用して公開する場合は、このファイルが App Bundle のコンテンツに自動的に含まれます。ユーザーから報告された問題のスタック トレースについて、Google Play が再トレースするので、Google Play Console で問題を調べることができます。詳しくは、クラッシュのスタック トレースの難読化を解除する方法に関するヘルプセンターの記事をご覧ください。
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
...
プロジェクトの keep ルールから 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
...
リソース圧縮のトラブルシューティング
リソースを圧縮すると、アプリから削除されたリソースの概要が [Build] ウィンドウに表示されます(最初にウィンドウの左側にあるビューの切り替えアイコン をクリックして、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
が到達可能である理由を確認するため、上に向かって調べていくと、そのリソースが「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
属性を使用して、そのリソースを削除するようビルドシステムに指示できます。