APK ファイルを可能な限り小さくするには、リリースビルドで使用されていないコードとリソースの削減機能を有効にする必要があります。 このページでは、削減がどのように行われるのか、ビルド時にどのコードとリソースを維持もしくは破棄するのか、について説明します。
コード削減はProGuardで使用可能です。ProGuardは、あなたのアプリ(インポートしたライブラリも含む)で使用されていないクラス、フィールド、メソッド、属性を検出して削除します(あの64k 参照制限に対処する貴重なツールです)。 また ProGuard はバイトコードの最適化、使用されていないコード命令の削除、残りのクラス・フィールド・メソッドを短い名前に難読化、といったことを行います。 コードを難読化することで、あなたのアプリをリバースエンジニアすることが難しくなります。 これはあなたのアプリでライセンス認証のようなセキュリティが重要な機能を使用している場合に特に役立ちます。
リソース削減はGradle用Android プラグインで使用可能です。 Gradle用Android プラグインでは、コードライブラリ内の未使用リソースも含む、パッケージされたアプリの未使用リソースを削除します。 未使用のコードが削除されると、そのコードで使用されていてもう参照されなくなってリソースも同様に削除できる、といったようにコード削減と連携して動作します。
このドキュメントで説明する機能は以下のバージョンで使用できます:
ProGuardでコード削減機能を有効にするには、build.gradle
ファイル内の適切なビルドタイプに minifyEnabled true
を追加します。
コード削減を使うとビルド時間が遅くなってしまうので、可能であればデバッグビルドでは使用を避けるよう注意してください。 しかし、テスト用に使用する最終APKでコード削減を有効にすることは重要です。どのコードを維持するかのカスタマイズが十分に行われていない場合、バグが発生する可能性があるからです。
例えば、build.gradle
ファイルの以下のコードでは、リリースビルド向けのコード削減機能を有効にしています。:
android { buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } ... }
メモ: インスタントランを使用している場合、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.gradle
ファイルの隣)にあります。
各ビルドバリアント専用のProGuardルールをさらに追加するには、対応するproductFlavor
ブロック内にもう一つ proguardFiles
プロパティを追加します。
例えば、以下のGradleファイルで flavor2
プロダクトフレーバーに flavor2-rules.pro
を追加しています。
release
ブロックのルールも適用されるので、これでflavor2
はProGuardルールを三つ全て使います。
android { ... buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } productFlavors { flavor1 { } flavor2 { proguardFile 'flavor2-rules.pro' } } }
各ビルドでは、 ProGuard は以下のファイルを出力します:
dump.txt
mapping.txt
seeds.txt
usage.txt
これらのファイルは<module-name>/build/outputs/mapping/release/
に保存されます。
状況によっては既定のProGuard 設定ファイル(proguard-android.txt
) で十分で、
ProGuard は未使用のコードを全て削除してくれます(コードを削除するだけ、とも言えますが)。
しかし多く状況では、ProGuard が正確な分析をすることが難しく、実際にはアプリで必要なコードを削除してしまう可能性があります。
誤ってコードを削除してしまう場合の例をいくつか挙げます:
AndroidManifest.xml
ファイルのみからクラスを参照していた場合
アプリのテストを行えば不適切に削除されたコードによりエラーが判明しますが、
<module-name>/build/outputs/mapping/release/
に保存された出力ファイルusage.txt
を見ることでどのファイルが削除されたのかを調べることもできます。
エラーを修正してProGuard に特定のコードを保持させるには、ProGuard の設定ファイルに -keep
行を追加します。例えば:
-keep public class MyClass
それ以外の方法として、保持したいコードに @Keep
アノテーションを追加することもできます。
クラスに @Keep
を追加すると、クラス全体が保持されます。
メソッドやフィールドに@Keep
を追加すると、メソッド/フィールド(およびその名前)だけでなくクラス名もそのままになります。
このアノテーションは、 アノテーションサポートライブラリを使用している場合のみ使用することができるので注意してください。
-keep
オプションを使用する際には多くの考慮すべき事項があります。; 設定ファイルのカスタマイズに関する詳細については、
ProGuard マニュアルを参照してください。
トラブルシューティング の項目では、コード削減を行った際に直面する可能性があるその他一般的な問題の概要を記載しています。
ProGuard がコードを削減した後だと、メソッド名が難読化されているためスタックとレースを読むのが難しくなります(不可能というわけではありませんが)。
幸運なことに、ProGuardは実行される度にmapping.txt
ファイルを作成します。これはクラス、メソッド、フィールドの元の名前と難読化後の名前の紐付け情報が記載されています。
ProGuard はこのファイルをアプリの<module-name>/build/outputs/mapping/release/
ディレクトリに保存します。
mapping.txt
ファイルはProGuardでリリースビルドを作成する度に毎回上書きされるため、新しいリリースを公開する際には毎回注意してコピーを保存する必要があります。
リリースビルドごとのmapping.txt
ファイルのコピーを保存しておくことで、ユーザーから古いバージョンのアプリのスタックとレースが送られてきた場合に問題点をデバッグすることができます。
Google Playでアプリを公開する際、APKの各バージョンのmapping.txt
ファイルをアップロードすることができます。
そうすることでGoogle Playがユーザーから届いたスタックトレースの難読化を解除するので、 Google Play デベロッパーコンソールからスタックトレースを閲覧することができます。
詳細については、ヘルプセンターの クラッシュ時のスタックトレースの難読化を解除する方法に関する記事を参照してください。
難読化されたスタックトレースを読めるように自分で変換するには、
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 ツールは標準出力から読み込みを行います。
アプリを段階的にビルドしていく際にコード削減機能が重要な場合、Android Plugin for Gradleに内蔵されている実験段階のコード削減ツールを試してみてください。 この削減ツールはProGuardと違ってインスタントランをサポートしています。
ProGuardと同じ設定ファイルを使って、Android プラグインの削減ツールを設定できます。 しかし、この 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' } } }
Note: 最初にAndroid プラグインの削除ツールがメソッドを削除するも、その後あなたがそのメソッドが参照されるようにコードを変更した場合、 インスタントランはそれを 構造的なコード変更として扱い、コールドスワップを実行します。
リソース削減機能は、コード削減機能との連動でのみ動作します。 コード削減機能が全ての未使用コードを削除した後、リソース削減機能はアプリがまだ使用しているリソースはどれなのかを識別します。 これは、リソースを含んだコードライブラリを追加している場合に特に当てはまります—使用されていないライブラリのコードが削除されるとそのライブラリのリソースはコード上から参照されなくなり、リソース削除機能で削除できるようになります。
リソース削減機能を有効にするには、 build.gradle
ファイルの shrinkResources
プロパティに true
を設定します(コード削減用のminifyEnabled
と合わせて)。例えば:
android { ... buildTypes { release { shrinkResources true minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }
コード圧縮にminifyEnabled
を使用してアプリをビルドしていない場合は、
shrinkResources
を有効にする前にminifyEnabled
を使用したビルドを行ってください。
リソースの削除を行う前に proguard-rules.pro
ファイルを編集して、動的に作成、呼び出しされるクラスやメソッドを保持する必要があるためです。
メモ:
現時点では、リソース削減機能ではvalues/
フォルダー内で定義されたリソース(string,dimension, style, color のような)の削除はしません。
これは、Gradle プラグインが事前定義されたバージョンのリソースを指定することを、Android Asset Packaging Tool (AAPT)が許可していないためです。
詳細についてはissue 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内にパッケージ化されません。
自分で削除できるのに、どのリソースを破棄するのかをわざわざ設定するなんて馬鹿げていると思うかもしれませんが、
これはビルドバリアントを使用する際に役立ちます。
For
example, you might put all your resources into the common project directory,
then create a different keep.xml
file for each build variant when
you know that a given resource appears to be used in code (and therefore not
removed by the shrinker) but you know it actually won't be used for the given
build variant.
通常、リソース削減機能はリソースが使用されているかどうかを正確に判断することができます。
しかし、コード上で Resources.getIdentifier()
を呼び出している場合 (もしくはライブラリが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 Servicesのような)を使用している場合、
あなたのアプリがその言語向けに翻訳されているどうかに関わらず、APKはこれらのライブラリ内で使用されているメッセージ用の翻訳言語を全てインクルードします。
あなたのアプリが正式にサポートしている言語のみを保持したい場合は、resConfig
プロパティを使って保持する言語を指定できます。
指定されていない言語のリソースは削除されます。
以下のコードでは、使用する言語リソースを英語とフランス語だけに制限する方法を示しています。:
android { defaultConfig { ... resConfigs "en", "fr" } }
同様に、APKスプリットを使って画面密度とABI用のリソースをAPKに含めるようにカスタマイズして、異なる端末ごとに異なるAPKをビルドすることができます。
既定では、別々のリソースフォルダにある同じ名前のdrawables といったような、同じ名前のリソースについてもGradle は結合を行います。
この動作は shrinkResources
プロパティによって制御され、無効にはできません。
コードが名前を検索している時に複数のリソース名と合致してしまうとエラーが発生するので、それを避けるために必要だからです。
リソースの結合は、二つ以上のファイルが同じリソース名、タイプ、修飾子を共有している場合にのみ発生します。 Gradle は重複リソースの中から最良と思われるファイルを一つ選び(後述する優先順位に基づいて)、 APK ファイル内の配布用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
という名前の診断ファイルも作成します。
このファイルには、どのリソースが他のリソースを参照しているか、どのリソースが使用もしくは削除されたか、といった詳細情報が記述されています。
例えば、なぜ @drawable/ic_plus_anim_016
がAPK内に残っているのかを調べるには、
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
属性を使ってビルドシステムにそれを削除するよう通知します。
/p>