Android プラットフォームは成長を続けているので、アプリのサイズも大きくなっています。 あなたのアプリとそれが参照するライブラリが特定のサイズまで達した時、 アプリがAndroid アプリのビルドアーキテクチャの限界に達したことを示すビルドエラーが発生します。 以前のバージョンのビルドシステムでは、このエラーは以下のように報告されます:
Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536
より新しいバージョンのビルドシステムでは異なるエラーが表示されます。それが示している問題の内容は同じです。:
trouble writing output: Too many field references: 131000; max is 65536. You may try using --multi-dex option.
これらのエラー状況には、両方とも共通する数字: 65,536が表示されています。 この数字は、一つの Dalvik Executable (DEX)バイトコードファイル内でコードによって呼び出すことができる参照の総数を表すので、重要なものです。 このページでは、multidexとして知られるアプリ設定を有効にすることでこの制限を回避する方法について説明します。 multidexを使うことで、アプリは複数のDEXファイルのビルドと読み込みができるようになります。
Android app (APK) ファイルには、 Dalvik Executable (DEX) 形式の実行可能なバイトコードファイルが含まれています。 このファイルには、アプリの実行に使用されるコンパイル済みコードが含まれています。 Dalvik Executable の仕様では、一つのDEX ファイル内で参照できるメソッドの総数は65,536に制限されています。—これには Androidフレームワークメソッド、ライブラリメソッド、 自作コード内のメソッドが含まれます。 コンピューターサイエンスの文脈において、 Kilo, Kという用語は 1024 (もしくは 2^10)を表します。 65,536 は to 64 X 1024 の値と等しいので、この制限は '64K 参照制限'と呼ばれます。
Android 5.0 (API level 21)以前のバージョンのプラットフォームでは、アプリコードの実行に Dalvikランタイムを使用します。
既定では、Dalvik はアプリの classes.dex
バイトコードファイルをAPKごとに一つと制限しています。
この制限を回避するため、multidex サポートライブラリを使います。
このライブラリはあなたのアプリのプライマリDEX ファイルの一部となり、追加DEXファイルとそれらが保持しているコードへのアクセスを管理します。
メモ: プロジェクトがminSdkVersion 20
以下でmultidex の設定をしており、 Android 4.4 (API level 20)以下が動作している対象端末にデプロイする場合、
Android Studio は インスタントランを無効にします。
Android 5.0 (API level 21) 以上では、ART と呼ばれるランタイムを使用します。これは、APKファイルからの複数のDEXファイルの読み込みをネイティブでサポートしています。
ARTはアプリインストール時に事前コンパイルを実行します。この時classesN.dex
ファイルをスキャンし、それらを一つの .oat
ファイルにコンパイルしてAndroid 端末で実行できるようにします。
従って、 minSdkVersion
が 21 以上の場合はmultidex サポートライブラリは必要ありません。
Android 5.0ランタイムの詳細については ARTとDalvikを参照してください。
メモ:インスタントランを使用している間、
アプリのminSdkVersion
が21以上の時にはAndroid Studio が自動的にアプリをmultidex 用に設定します。
Instant Runはアプリのデバッグバージョンでのみ動作するため、リリースビルドでは64K 制限を避けるためにmultidex 用の設定をする必要があります。
アプリで64K以上のメソッド参照を使用できるようにする前に、 アプリコードから呼び出される参照の総数を減らすようにしてください(アプリコードやインクルードしたライブラリで定義されているメソッドを含む)。 以下の方法論は、DEXの参照制限に遭遇するのを回避するのに役立ちます。:
これらの技術を使うことで、multidexを有効にする必要性を回避し、APKの全体的なサイズを減らすこともできます。
アプリプロジェクトで multidex を使用できるよう設定するには、アプリプロジェクトで後述する変更を行う必要があります。 変更内容は、アプリがサポートする最小Androidバージョンによって異なります。
minSdkVersion
が 21 以上の場合、必要なのは以下で示すように、モジュールレベルbuild.gradle
ファイルでmultiDexEnabled
の設定値をtrue
にすることだけです。:
android { defaultConfig { ... minSdkVersion 21 targetSdkVersion 25 multiDexEnabled true } ... }
しかし、 minSdkVersion
が 20 以下の場合は、以下のようにして multidex サポートライブラリ を使用する必要があります:
以下で示すように、モジュールレベル build.gradle
ファイルを編集して multidex を有効にし、依存ファイルとして multidex ライブラリを追加します:
android { defaultConfig { ... minSdkVersion 15 targetSdkVersion 25 multiDexEnabled true } ... } dependencies { compile 'com.android.support:multidex:1.0.1' }
Application
クラスを上書きしているかどうかに応じて、以下のうちどれか一つを実行します。:
Application
クラスを上書きしていない場合、マニフェストファイルを編集して以下のように<application>
タグ内に android:name
を設定します。
:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp"> <application android:name="android.support.multidex.MultiDexApplication" > ... </application> </manifest>
Application
クラスを上書きしている場合、以下のようにして MultiDexApplication
を継承するように (可能であれば) 変更してください:
public class MyApplication extends MultiDexApplication { ... }
もしくは、Application
クラスを上書きしているが基底クラスの変更が不可能な場合は、代わりに attachBaseContext()
メソッドを上書きして MultiDex.install(this)
を呼び出し、 multidexを有効にすることができます:
public class MyApplication extends SomeOtherApplication { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(context); Multidex.install(this); } }
これで、アプリをビルドした時にAndroidビルドツールはプライマリDEXファイル(classes.dex
)とサポートDEX ファイル
(classes2.dex
, classes3.dex
, など) を必要に応じて作成します。
そしてビルドシステムは全ての DEX ファイルを APKにパッケージします。
実行時、multidex APIは特別なクラスローダーを使って使用可能なDEXを全て検索してメソッドを探します(メインclasses.dex
ファイル内のみを検索するのではなく)。
multidexサポートライブラリには既知の制限事項がいくつかあります。 このライブラリをビルド構成に組み込み際にはこの制限事項に注意してテストを行う必要があります。:
multidex アプリ用の各DEX ファイルをビルドする際、
アプリが正常に起動できるようするため、ビルドツールはプライマリDEXファイルに必要なクラスはどれなのかを複雑な意思決定により決定します。
起動中に必要なクラスがプライマリDEX ファイルにない場合、アプリはjava.lang.NoClassDefFoundError
エラーでクラッシュします。
この事象は、アプリのコード上から直接アクセスできるコードの場合は発生しません。 ビルドツールがこれらのコードパスを認識してくれるからです。 しかし、使用してるライブラリが複雑な依存関係を持っているといったようにコードパスが見えにくい場合は、この事象が発生する可能性があります。 例えば、ネイティブコード上のコードでJava メソッドの イントロスペクションや インボケーションを使用する場合、それらのクラスはプライマリDEX ファイルで必要なものと認識されないことがあります。
そのため java.lang.NoClassDefFoundError
が発生した場合は、
ビルドタイプのmultiDexKeepFile
プロパティに宣言を行うことで、それらの追加クラスをプライマリDEXファイルに必要なものとして手動で設定する必要があります。
ここで指定するファイルは、com/example/MyClass.class
の形式にして、1行につき1クラスにする必要があります。
例えば、以下のような中身のdex.keep
という名前のファイルを作成します:
com/example/MyClass.class com/example/MyOtherClass.class
それから以下のようにして、ビルドタイプでこのファイルを宣言します:
android { buildTypes { release { multiDexKeepFile file('dex.keep') ... } } }
Gradle はbuild.gradle
を基準とした相対パスを読み込むので、上記の例は dex.keep
がbuild.gradle
と同じディレクトリにある場合に動作するということを覚えておいてください。
どのクラスをプライマリDEX ファイルにインクルードしてどのクラスをセカンダリDEX ファイルにインクルードするか、 ビルドシステムは複雑な判断を行う必要があるため、 multidex を使用するとビルド処理時間は大幅に増加します。 つまり、multidex を使ったインクリメンタルビルドは通常より長く時間がかかり、開発プロセスが遅延する可能性があるのです。
multidex 出力のビルド時間を短縮するには、
productFlavors
を使って二つのビルドバリアント(異なるminSdkVersion
値を持った開発用フレーバーとリリース用フレーバー)を作成します。
開発用フレーバーには、 minSdkVersion
に 21を設定します。
この設定で、pre-dexingと呼ばれるビルド機能が有効になります。
この機能では、 Android 5.0(API level 21) 以上でのみ使用できるART 形式を使ってより高速でmultidex 出力を生成します。
リリース用フレーバーでは、実際にサポートする最小レベルに合わせてminSdkVersion
を設定します。
この設定ではより多くの端末と互換性のあるmultidex APKが生成されますが、ビルド時間は長くなります。
以下のビルド設定例では、Gradle ビルドファイルでこれらのフレーバーを設定する方法について例示しています。:
android { defaultConfig { ... multiDexEnabled true } productFlavors { dev { // Enable pre-dexing to produce an APK that can be tested on // Android 5.0+ without the time-consuming DEX build processes. minSdkVersion 21 } prod { // The actual minSdkVersion for the production version. minSdkVersion 14 } } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile 'com.android.support:multidex:1.0.1' }
この設定の変更が完了したら、インクリメンタルビルドでアプリのdevDebug
バリアントを使用することができます。
devDebug
とは、プロダクトフレーバーdev
とビルドタイプdebug
の各属性を組み合わせたものです。
これで、multidex が有効となってproguardが無効となっているデバッグ可能なアプリが作成されます (既定では minifyEnabled
の設定値は false
のため)。
これらの設定によって、Gradle 用Android プラグインは以下のように動作します:
これらの設定の結果ビルドが高速化します。増分ビルドにより、ビルドの際には変更のあったモジュールのDEXファイルのみが再計算と再パッケージされるためです。 ですが、このビルドで作成されたAPKのテストで使うことができるのは Android 5.0の端末のみです。 しかし設定をフレーバーとして実装することで、リリースに適した最小APIレベルとProGuard コード削減処理を伴う通常のビルドを実行する機能を維持することができます。
また、これ以外のバリアント(prodDebug
バリアントを含む)をビルドすることもできます。
prodDebug
バリアントとは、開発環境外でもテストに使用することができるバリアントのことです。
示された設定の中では、prodRelease
バリアントが最終的なテストを行うリリース版のバリアントになります。
ビルドバリアントの使用に関する詳細についてはビルドバリアントを設定するを参照してください。
ヒント:
これで、異なるmultidex ニーズごとの異なるビルドバリアントが準備できました。
さらに、各バリアントごとに異なるマニフェストファイルを用意したり(API level 20以下のバリアントでのみ <application>
タグ名を変更するなど)、
各バリアントごとに異なる Application
サブクラスを作成したりできます
(API level 20 以下のバリアントでのみMultiDexApplication
クラスを継承したり、 MultiDex.install(this)
を呼び出したりするなど)。
multidex アプリの計測テストを記述する際、追加の設定は必要ありません。
あなたが MultiDexApplication
を使用しているか、
もしくはカスタムApplication
オブジェクトのattachBaseContext()
メソッドを上書きしてその中でMultiDex.install(this)
を呼び出して multidexを有効にしているのであれば、
AndroidJUnitRunner
はすぐに multidex をサポートします。
それ以外の方法として、AndroidJUnitRunner内の onCreate()
メソッドを上書きすることもできます:
public void onCreate(Bundle arguments) { MultiDex.install(getTargetContext()); super.onCreate(arguments); ... }
メモ:テストAPKの作成に multidex を使用することは、現時点ではサポートされていません。