DEX(Dalvik Executable)は、JavaおよびKotlinで記述されたAndroidアプリケーションのソースコードがコンパイルされるバイトコード形式です。DEXファイルは、Dalvik仮想マシン(Android 4.4まで)またはAndroid Runtime(ART、Android 5.0以降)によって実行されます。Android Open Source Project、2026によると、DEX形式は標準のJVM Javaバイトコードと比較して、平均30%よりコンパクトなコード表現を提供します。
重要なポイント
DEX(Dalvik Executable)は、Androidモバイルデバイス向けに特別に設計されたバイトコード形式です。標準的なJavaバイトコード(.classファイル)とは異なり、DEXは限られたリソース向けに最適化されています:メモリ消費が少なく、サイズが小さく、クラスロードが高速です。
JavaまたはKotlinのソースコードは、javac/kotlincによって標準の.classファイル(Javaバイトコード)にコンパイルされます。次に、d8ツール(または以前のdx)が.classを1つ以上のDEXファイルに変換します。この変換は単なる再パッケージ化ではなく、d8は最適化を実行します:定数プールの統合、レジスタアーキテクチャへの命令の書き換え、重複データの削除です。
DEXはレジスタベースのアーキテクチャを使用します(スタックベースのJVMとは異なります)。各メソッドは固定数のレジスタ(最大65536)を持ちます。DEX命令はより短く、平均2バイト(JVMでは1〜4バイト)です。これにより、よりコンパクトなコードが生成されます:典型的なアプリケーションは10〜15MBの.classから4〜6MBの.dexに縮小します。
DEXファイルは厳密に定義されたバイナリ構造を持ちます。各ファイルはヘッダーで始まり、互いにオフセットを介して参照し合う複数のセクションを含みます。
| セクション | 目的 |
|---|---|
| header | ヘッダー:マジック、チェックサム、署名、セクションサイズとオフセット |
| string_ids | 文字列テーブル:クラス、メソッド、フィールド名 |
| type_ids | 型:型文字列識別子への参照 |
| proto_ids | メソッドプロトタイプ:戻り値の型とパラメータ |
| field_ids | クラスフィールド:クラス、型、名前 |
| method_ids | メソッド:クラス、プロトタイプ、名前 |
| class_defs | クラス定義:フラグ、スーパークラス、インターフェース、データオフセット |
| data | 実際のデータ:メソッドコード、アノテーション、デバッグ情報 |
DEXのマジックナンバーは`dex\n035\0`(バージョン035)です。その他のバージョン:036、037、038(Android 8.0+用)。ヘッダーサイズは0x70バイトで、SHA-1チェックサムと全セクションのオフセットを含みます。仮想マシンがDEXをロードする際の最初のステップはヘッダーの検証です。
string_ids、type_ids、proto_ids、field_ids、method_idsはインデックス付きテーブルです。メソッドコード内に完全な名前を保存する代わりに、4バイトのインデックスが使用されます。これが重要な最適化です:クラスが100回参照される場合、その名前はstring_idsに1回だけ保存されます。dex2oatはARTコンパイル時にこれらのテーブルをさらに最適化します。
ソースコードをDEXに変換するプロセスは複数の段階からなります。最新のツールチェーンはD8コンパイラを使用し、2018年にAndroid Gradle Plugin 3.2でDXを置き換えました。
javac(Java用)またはkotlinc(Kotlin用)がソースコードを.classファイルにコンパイルします。各クラスはJavaバイトコードの個別の.classファイルです。この段階では、型チェック、ブリッジメソッドの生成、定数のインライン化が実行されます。
D8はすべての.classファイルを受け取り、DEXバイトコードに変換します。D8はいくつかの最適化を実行します:未使用のメソッド引数の削除、異なる.classファイルからの定数プールを1つのグローバルDEXプールに統合、JVMスタック命令のDalvikレジスタ命令への変換です。
// Kotlinソースコード
data class User(
val name: String,
val email: String
)
fun greet(user: User): String {
return "Hello, ${user.name}!"
}
D8コンパイル後、このコードはコンパクトなDEX命令に変換されます:文字列ロード用のconst-string、オブジェクトフィールドアクセス用のiget-object、StringBuilder.append呼び出し用のinvoke-virtual。
D8はDXより2〜3倍高速で、よりコンパクトなDEX(5〜10%小さい)を生成し、Kotlin固有の構造(インライン関数、ラムダ)をより適切に最適化します。DXは2018年に非推奨が宣言され、Android Gradle Plugin 8.0で削除されました。
AndroidでのDEXコードの実行は、2つの段階を経てきました:オリジナルのDalvik仮想マシン(Android 2.2〜4.4)とAndroid Runtime ART(Android 5.0+)です。コンパイルアプローチの違いは根本的です。
DalvikはJust-In-Time(JIT)コンパイルを使用していました:DEXバイトコードは解釈され、頻繁に呼び出されるメソッドはその場でネイティブコードにコンパイルされていました。利点 — インストールが高速。欠点 — 起動が遅く、JITによるCPU消費が常に発生。
ART(Android Runtime)は、dex2oatを介してアプリケーションインストール時にDEXをネイティブコードにコンパイルします。これはAhead-Of-Time(AOT)アプローチです:インストールは遅くなりますが、起動が速く、消費電力が低くなります。Android 7.0以降、ARTはハイブリッドアプローチを使用しています — AOT + JIT + プロファイルガイド最適化。
dex2oatツールはアプリケーションのインストール時または更新時に実行されます。DEXをデバイスアーキテクチャ向けのネイティブコードを含むELFファイルにコンパイルします。結果 — /data/dalvik-cache/ディレクトリ内の.oatおよび.artファイル。Googleは継続的にdex2oatを改善しています:Android 14では折りたたみデバイス向けの最適化が追加されました。
1つのDEXファイルあたり65536メソッドの制限は、Dalvikアーキテクチャの遺産です。DEXヘッダーのmethod_idsフィールドは4バイトを占め、最大2^16 = 65536の一意の参照を提供します。Google Play Services、Firebase、その他のSDKを使用する最新のアプリケーションは、簡単にこの制限を超えます。
Multidexは、コードを複数のDEXファイルに分割するメカニズムです。メインのclasses.dexにはエントリポイント(Applicationクラス、メインActivity)が含まれ、残りはclasses2.dex、classes3.dexなどとなります。起動時に、追加のDEXからのクラスはDexClassLoaderを介してロードされます。
// build.gradle.kts — multidexの有効化
android {
defaultConfig {
multiDexEnabled = true
}
}
// MultidexをサポートするApplicationクラス
class MyApp : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
}
アプリケーション起動時の追加DEXのロードは、Android 5.0未満のデバイスでANR(Application Not Responding)を引き起こす可能性があります。推奨 — 必要な場合にのみmultidexを使用し、制限を超えないように依存関係を最小限に抑えます。
DEXの最適化は、リリース版Androidアプリケーションをビルドする際の標準的なステップです。R8およびProGuardツールはDEXサイズを削減し、コードを難読化し、未使用のクラスを削除します。
R8はProGuardの後継であり、2019年からAndroid Gradle Pluginに組み込まれています。R8は単一パスで縮小化、難読化、最適化を実行しますが、ProGuardは2つの段階が必要でした:ProGuard → D8。ProGuardはまだサポートされていますが、Googleは新しいプロジェクトにはR8を推奨しています。
R8は未使用のクラス、メソッド、フィールドを削除し、それらを短い名前(a、b、c)に変更し、インライン関数を組み込み、デッドコードを除去します。結果 — 機能を損なうことなく、DEXが20〜40%削減されます。
R8の設定はproguard-rules.proファイルで指定されます。開発者は、リネームできないクラスを指定できます(例えば、リフレクションやGsonシリアライゼーション用)。Firebaseやその他のSDKは、依存関係に独自のルールを提供します。
DEXはJavaコードに逆コンパイルすることができます。これはAndroidアプリケーションの重要なセキュリティ問題です:難読化なしでは、コードはオリジナルに近いレベルに復元されます。
JADXは最も人気のあるDEXからJavaへの逆コンパイラです。クラス名、メソッド、フィールド、およびほとんどのロジックを復元します。apktoolはDEXをsmaliコード(Dalvikアセンブラ)に逆コンパイルします — 元の命令に近い低レベル表現です。Bytecode Viewerは複数の逆コンパイラを1つのインターフェースに統合します。
R8/ProGuardによる難読化は防御の第一線です:クラス名とメソッド名が読み取り不能になります。DexGuardは追加の方法を備えた商用ツールです:文字列暗号化、整合性チェック、改ざん防止。制御フロー難読化(O-LLVM)は、機能を維持しながらコード構造を変更し、解析をはるかに困難にします。
よくある質問
DEXはスタックベースのJVMではなくレジスタベースのアーキテクチャを使用し、よりコンパクトな形式(30%小さい)で、すべての.classファイルを1つの定数プールを持つ1つのファイルに統合し、8ビットではなく16ビットのインデックスを使用します。
SmaliはDEXバイトコードのアセンブラです。各DEX命令にはsmali形式のテキスト表現があります。baksmaliツールはDEXをsmaliに変換(逆アセンブル)し、smaliはsmaliをDEXにアセンブルします。
GradleタスクcountMethodsまたはdex-method-countsプラグインは、各DEXファイル内のメソッド数を表示します。adb shellコマンドとdumpsysは、インストール済みアプリケーションのロードされたDEXの統計情報も表示します。
はい、Android 8.0未満のデバイスでは、複数のDEXファイルはアプリケーションの起動を遅くします。各追加ファイルが個別にロードされるためです。Android 8.0+のARTでは、dex2oatによる単一の.oatファイルへのコンパイルのおかげで、その差は最小限です。
はい、dexplorerやAndroid互換JVM実装などのプロジェクトが存在し、Android外部でDEXバイトコードを実行できます。ただし、ほとんどのDEXファイルはAndroid APIを使用しているため、標準のJVMでの実行には適していません。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。