APK ファイルを可能な限り小さくするには、圧縮を有効にして、リリースビルドから未使用のコードとリソースを削除する必要があります。このページでは、APK ファイルを小さくする方法と、ビルド時に保持または破棄するコードとリソースを指定する方法について説明しています。
ProGuard ではコードの圧縮が使用できます。ProGuard は、含まれているコード ライブラリなどにある未使用のクラス、フィールド、メソッド、および属性をパッケージ化されたアプリから検出して削除します(64K 参照制限を回避するための有用なツールです)。また、ProGuard はバイトコードを最適化して、未使用のコード指示を削除し、残りのクラス、フィールド、メソッドを短い名前で難読化します。コードを難読化すると、APK をリバース エンジニアリングすることが難しくなるため、アプリでライセンス認証などの高度なセキュリティ機能を使用している場合は特に有用になります。
Android Plugin for Gradle でリソースの圧縮を行うと、コード ライブラリにある未使用のリソースを含め、使用されていないリソースをパッケージ化されたアプリから削除することができます。Android Plugin for Gradle はコードの圧縮と連携して機能するため、未使用のコードが削除されると、参照されなくなったリソースも安全に削除することができます。
このドキュメントで説明している機能は次のツールで提供されます。
- SDK ツール 25.0.10 以降
- Android Plugin for Gradle 2.0.0 以降
コードの圧縮
ProGuard でコードの圧縮を有効にするには、build.gradle ファイルで適切なビルドタイプに minifyEnabled true を追加します。
コードを圧縮すると、ビルド時間が長くなることに注意してください。可能な限り、デバッグビルドでのコードの圧縮は避けてください。ただし、テストに使用する最終 APK ではコードの圧縮を有効にすることが重要になります。保持するコードのカスタマイズが不十分であると、コードの圧縮がバグの原因になる可能性があるためです。
たとえば、build.gradle ファイルの次のスニペットは、リリースビルドのコード圧縮を有効にします。
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
...
}
注: Instant Run を使用している場合、Android Studio は ProGuard を無効にします。増分ビルドにおいてコード圧縮をする必要がある場合は、試験運用版の Gradle 圧縮ツールを試用してください。
minifyEnabled プロパティに加えて、proguardFiles プロパティにより ProGuard のルールが定義されます。
getDefaultProguardFile('proguard-android.txt')メソッドは、Android SDK のtools/proguard/フォルダからデフォルトの ProGuard 設定を取得します。ヒント: コードをさらに圧縮する場合は、同じ場所にある
proguard-android-optimize.txtファイルを使用してみてください。このファイルには同じ ProGuard ルールが含まれていますが、メソッドの内部およびメソッドを横断したバイトコード レベルの分析を実行し、APK サイズをさらに削減して実行速度を上げる最適化手法を採用しています。proguard-rules.proファイルにカスタム ProGuard ルールを追加することができます。デフォルトでは、このファイルはモジュールのルートにあります(build.gradlebuild.gradle ファイルの次)。
各ビルド バリアント固有の ProGuard ルールを追加するには、対応する productFlavor ブロックに別の proguardFiles プロパティを追加します。たとえば、次の Gradle ファイルは、flavor2-rules.pro を flavor2 プロダクト フレーバーに追加します。release ブロックのルールも適用されるため、flavor2 は 3 つの ProGuard ルールをすべて使用しています。
android {
...
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
productFlavors {
flavor1 {
}
flavor2 {
proguardFile 'flavor2-rules.pro'
}
}
}
各ビルドで ProGuard は次のファイルを出力します。
dump.txt- APK のすべてのクラスファイルの内部構造を説明しています。
mapping.txt- クラス、メソッド、およびフィールドの元の名前と難読化後の名前の対応関係を提供します。
seeds.txt- 難読化さていないクラスとメンバーを一覧にしています。
usage.txt- APK から削除されたコードを一覧にしています。
これらのファイルは <module-name>/build/outputs/mapping/release/ に保存されます。
保持するコードのカスタマイズ
状況によっては、デフォルトの ProGuard 設定ファイル(proguard-android.txt)で十分であるため、ProGuard は未使用のコードに限ってすべて削除します。ただし、多くの場合は、ProGuard による正確な分析が困難であり、アプリが実際に必要としているコードが削除される可能性があります。次のようなケースでは、ProGuard が誤ってコードを削除する可能性があります。
- アプリが
AndroidManifest.xmlファイルからのみクラスを参照するとき - アプリが Java Native Interface(JNI)からメソッドを呼び出すとき
- アプリが実行時にコードを操作したとき(リフレクションまたはイントロスペクションなどで)
アプリをテストすると、不適切に削除されたコードが原因で発生したエラーが明らかになります。または、<module-name>/build/outputs/mapping/release/ に保存されている usage.txt 出力ファイルを調べることで、削除されたコードを特定することもできます。
エラーを修正して、ProGuard で特定のコードを保持させるには、ProGuard の設定ファイルに -keep 行を追加します。次に例を示します。
-keep public class MyClass
または、保持するコードに @Keep アノテーションを追加できます。クラスに @Keep を追加すると、クラス全体がそのまま保持されます。このアノテーションをメソッドまたはフィールドに追加すると、メソッド / フィールド(および、その名前)に加えて、クラス名がそのまま保持されます。このアノテーションは、Annotations Support Library を使用しているときにのみ利用できることに注意してください。
-keep オプションを使用する場合は、考慮すべき事項が多くあります。設定ファイルのカスタマイズに関する詳細については、ProGuard マニュアルをご覧ください。トラブルシューティングのセクションには、コードを削除したときに発生する可能性のあるその他の問題の概要が記載されています。
難読化されたスタックトレースのデコード
ProGuard がコードを圧縮すると、メソッド名が難読化されるため、スタックトレースの読み取りが(不可能ではなくとも)難しくなります。幸いにも、ProGuard では実行のたびに mapping.txt ファイルを作成します。このファイルには、難読化された名前にマッピングされたクラス、メソッド、およびフィールドの元の名前が載っています。ProGuard ではこのファイルがアプリの <module-name>/build/outputs/mapping/release/ ディレクトリに保存されます。
ProGuard でリリースビルドを作成するたびに、mapping.txt ファイルが上書きされることに注意してください。つまり、新しいリリースを公開するたびに、このファイルのコピーを保存する必要があります。各リリースビルドの mapping.txt ファイルのコピーを保持しておくと、ユーザーが旧バージョンのアプリから難読化されたスタックトレースを送信した場合でも、問題をデバッグできるようになります。
Google Play でアプリを公開するときには、各バージョンの APK の mapping.txt ファイルをアップロードすることができます。Google Play によって、ユーザーが報告した問題のスタックトレースが難読化解除されるため、Google Play Developer Console で問題を調べることができます。詳細については、クラッシュのスタックトレースを解読するに関するヘルプセンターの記事を参照してください。
難読化されたスタックトレースを読み取り可能なスタックトレースに自身で変換するには、retrace スクリプト(Windows の場合は retrace.bat、Mac/Linux の場合は retrace.sh)を使用します。このスクリプトは <sdk-root>/tools/proguard/ ディレクトリにあります。このスクリプトは mapping.txt ファイルとスタックトレースを取得し、新しい読み取り可能なスタックトレースを生成します。retrace ツールを使用するための構文は次のとおりです。
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
次に例を示します。
retrace.bat -verbose mapping.txt obfuscated_trace.txt
スタックトレース ファイルを指定していない場合、retrace ツールは標準の入力からスタックトレースを読み取ります。
コード圧縮の有効化と Instant Run
アプリの増分ビルドを行う上でコードの圧縮が重要となった場合は、Android plugin for Gradle に組み込まれている試験運用版のコード圧縮ツールを試してください。この圧縮ツールは、ProGuard とは異なり Instant Run に対応しています。
Android プラグインの圧縮ツールを設定する際は、ProGuard と同じ設定ファイルを使用できます。ただし、Android プラグインの圧縮ツールでは、コードの難読化や最適化を行わず、未使用のコードを削除するだけです。そのため、デバックビルドのみに使用してください。また、リリースビルドでは ProGuard を有効にして、リリース APK のコードが難読化され、最適化されるようにしてください。
Android プラグイン圧縮ツールを有効にするには、「debug」ビルドタイプで useProguard を false にするだけです(また、minifyEnabled は true に設定されたままにします)。
android {
buildTypes {
debug {
minifyEnabled true
useProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
注: Android プラグイン圧縮ツールが最初にメソッドを削除した後に、コードに変更を加えてそのメソッドに到達できるようにした場合、Instant Run はこの変更を構造的なコード変更として処理し、コールド スワップを実行します。
リソースの圧縮
リソースの圧縮は、必ずコードの圧縮と連動して機能します。コード圧縮ツールにより未使用のコードが削除された後、リソース圧縮ツールで、アプリがまだ使用しているリソースを特定できます。これは、リソースが含まれるコード ライブラリを追加している場合に特に当てはまります。未使用のライブラリ コードを削除すると、ライブラリ リソースが参照されなくなります。したがって、リソース圧縮ツールによってライブラリ リソースを削除できます。
リソースの圧縮を有効にするには、build.gradle ファイルで shrinkResources プロパティを true に設定します(コード圧縮の minifyEnabled も同時に設定)。次に例を示します。
android {
...
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
}
コード圧縮用の minifyEnabled を使ってアプリをまだビルドしていない場合は、shrinkResources を有効にする前に、コードを圧縮したアプリを作成してください。これは、リソースの削除を始める前に、動的に作成または呼び出されるクラスまたはメソッドを保持するために、proguard-rules.pro ファイルの編集が必要な場合があるためです。
注: 現在、リソース圧縮ツールによって、values/ フォルダ内で定義されているリソース(文字列、寸法、スタイル、色など)は削除されません。これは、Android Asset Packaging Tool(AAPT)では、Gradle プラグインにより定義済みバージョンのリソースを指定することが許可されないためです。詳細については、問題 70869 を参照してください。
保持するリソースのカスタマイズ
特定のリソースを保持または破棄したい場合は、<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_ 接頭辞が付いたすべてのリソースを使用されるリソースとしてマークします。
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 サービスなど)を使用している場合、アプリの残りの部分が同じ言語に翻訳されるかどうかに関係なく、APK には、これらのライブラリにあるメッセージのすべての翻訳言語の文字列が含まれます。アプリで公式にサポートされる言語のみを保持する場合は、resConfig プロパティを使用して、それらの言語を指定することができます。指定されていない言語のリソースは削除されます。
次のスニペットは、言語リソースを英語とフランス語のみに制限する方法を示しています。
android {
defaultConfig {
...
resConfigs "en", "fr"
}
}
同様に、APK の分割を使用して、どの画面密度や ABI リソースを APK に含めるかをカスタマイズして、さまざまな端末用にそれぞれ異なる APK をビルドすることができます。
重複リソースの結合
Gradle はデフォルトで、異なるリソース フォルダに存在する同じ名前のドローアブルなど、同一の名前を持つリソースを結合します。この動作は、shrinkResources プロパティによって制御することも、無効にすることもできません。これは、コードで検索している名前に一致するリソースが複数あったときのエラーを回避するために必要な動作です。
リソースの結合は、2 つ以上のファイルが同一のリソース名、タイプ、修飾子を共有しているときにのみ発生します。Gradle は、重複ファイルのうち最適であると判断したファイルを選択し(以下に説明する優先順位に基づいて)、APK ファイルでの配布用にその 1 つのリソースのみを AAPT に渡します。
Gradle は次の場所で重複リソースを検索します。
- メインリソース。メイン ソースセットと関連付けられており、通常は
src/main/res/にあります。 - ビルドタイプとビルド フレーバーから成るバリアント オーバーレイ。
- ライブラリ プロジェクトの依存関係。
Gradle は、次の優先順位に従って重複リソースを結合します。
依存関係 → メイン → ビルド フレーバー → ビルドタイプ
たとえば、メイン リソースとビルド フレーバーの両方に重複リソースがある場合、Gradle はビルド フレーバー内のリソースを選択します。
同じソースセットに同一のリソースがある場合、Gradle はこれらのリソースを結合できないため、リソース結合エラーが出力されます。このエラーは、build.gradle ファイルの sourceSet プロパティに複数のソースセットを定義している場合に発生します。たとえば、src/main/res/ と src/main/res2/ の両方に同じリソースが含まれている場合です。
リソース圧縮のトラブルシューティング
リソースを圧縮すると、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] @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 属性を使用して、そのリソースを削除するようにビルドシステムに指定することができます。