サイトのトップへ戻る

Android Studio ドキュメント 日本語訳

サイト内検索

コードとリソースを削減する

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
APK内の全てのクラスファイルの内部構造について説明します。
mapping.txt
クラス、メソッド、フィールド名のオリジナルと難読化後の変換状況について記述されています。
seeds.txt
難読かされなかったクラスとメンバの一覧。
usage.txt
APKから削除されたコードの一覧。

これらのファイルは<module-name>/build/outputs/mapping/release/に保存されます。



どのコードを維持するか、カスタマイズする

状況によっては既定のProGuard 設定ファイル(proguard-android.txt) で十分で、 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を追加すると、クラス全体が保持されます。 メソッドやフィールドに@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" ビルドタイプ内で useProguardfalseを設定するだけです。 (そして minifyEnabledtrueのままにしておきます):

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 ファイル内の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 Servicesのような)を使用している場合、 あなたのアプリがその言語向けに翻訳されているどうかに関わらず、APKはこれらのライブラリ内で使用されているメッセージ用の翻訳言語を全てインクルードします。 あなたのアプリが正式にサポートしている言語のみを保持したい場合は、resConfigプロパティを使って保持する言語を指定できます。 指定されていない言語のリソースは削除されます。

以下のコードでは、使用する言語リソースを英語とフランス語だけに制限する方法を示しています。:

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

同様に、APKスプリットを使って画面密度とABI用のリソースをAPKに含めるようにカスタマイズして、異なる端末ごとに異なるAPKをビルドすることができます。



重複したリソースを結合する

既定では、別々のリソースフォルダにある同じ名前のdrawables といったような、同じ名前のリソースについてもGradle は結合を行います。 この動作は shrinkResourcesプロパティによって制御され、無効にはできません。 コードが名前を検索している時に複数のリソース名と合致してしまうとエラーが発生するので、それを避けるために必要だからです。

リソースの結合は、二つ以上のファイルが同じリソース名、タイプ、修飾子を共有している場合にのみ発生します。 Gradle は重複リソースの中から最良と思われるファイルを一つ選び(後述する優先順位に基づいて)、 APK ファイル内の配布用AAPT へそのリソースのみを渡します。

Gradle は以下の場所で重複リソースを探します:

  • メインのソースセットに関連したメインリソース。これは、一般的には src/main/res/にあります。
  • The variant overlays, from the build type and build flavors.
  • ライブラリプロジェクトの依存ファイル群。

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>




エンジェル戦記