Demystifying the Android DEX File Format: A Deep Dive into Compilation and Class Loading 🚀
The Kotlin compiler compiles each class into a .class file containing bytecode. Subsequently, the D8 compiler merges and compresses all these .class files into a single .dex file (or multiple files if the method count exceeds the 65,536 limit).
During compilation, D8 places a Header at the beginning of the file, recording the magic number (ensuring this is a valid dex file), file size, and checksum.
Then, all strings are extracted and placed into a large String IDs array. Actually, the String IDsarray only stores offsets; the real string contents are stored in the Data section. This includes class names, type descriptors, method names, method signatures, variable names, string constants, annotation strings, and paths.
Next, all types are extracted into the Type IDs array, which essentially contains indices pointing to the corresponding String IDs.
Following this is the Proto IDs array, which records all possible combinations of method input parameters and return types. For example:
text
0A 00 00 00 // shorty_idx: 10 (points to StringIds[#10] "LII")
02 00 00 00 // return_type_idx: 2 (points to TypeIds[#2] String)
00 08 00 00 // parameters_off: 0x0800 (offset pointing to TypeList)The TypeList is actually stored in the Data section. Note: The Shorty is a simplified representation of input parameters and return types, where all object types are represented by L. Therefore, it can only be used for initial filtering, not exact matching. The TypeList contains the specific input parameter types, physically stored in the Data section, with only an offset stored in the Proto IDs.
Field IDs (Field Index Area): Records all member variables.
java
public class User {
public String name;
public int age;
}text
// 1. First field: name (8 bytes)
05 00 // class_idx (2 bytes): points to TypeIds[#5] (User class)
0A 00 // type_idx (2 bytes): points to TypeIds[#10] (String)
14 00 00 00 // name_idx (4 bytes): points to StringIds[#20] ("name")// 2. Second field: age (8 bytes)
05 00 // class_idx (2 bytes): points to TypeIds[#5] (User class)
02 00 // type_idx (2 bytes): points to TypeIds[#2] (int)
15 00 00 00 // name_idx (4 bytes): points to StringIds[#21] ("age")Method IDs (Method Index Area): Records all methods.
Get Bearaddictedgames’s stories in your inbox
Join Medium for free to get updates from this writer.
java
public class Calculator {
public int add(int a, int b) { return a + b; }
public void log(String msg) { ... }
}text
// 1. First method: add (8 bytes)
05 00 // class_idx (2 bytes): points to TypeIds[#5] (Calculator)
08 00 // proto_idx (2 bytes): points to ProtoIds[#8] (Int, Int) -> Int
14 00 00 00 // name_idx (4 bytes): points to StringIds[#20] ("add")// 2. Second method: log (8 bytes)
05 00 // class_idx (2 bytes): points to TypeIds[#5] (Calculator)
09 00 // proto_idx (2 bytes): points to ProtoIds[#9] (String) -> void
15 00 00 00 // name_idx (4 bytes): points to StringIds[#21] ("log")Class Defs (Class Definition Area): This is the core! It links all scattered IDs together, telling the system: "What is this class called, what does it inherit, what methods does it have, and where is the method code located."
java
public class User extends Object {
public int age;
public void sayHi() { ... }
}text
// 1. Basic Identity Info (16 bytes)
05 00 00 00 // class_idx (4 bytes): points to TypeIds[#5] -> "Lcom/example/User;"
01 00 00 00 // access_flags (4 bytes): 0x01 indicates public
02 00 00 00 // superclass_idx (4 bytes): points to TypeIds[#2] -> "Ljava/lang/Object;"
00 00 00 00 // interfaces_off (4 bytes): 0 (No interfaces)// 2. Associated Info (16 bytes)
0A 00 00 00 // source_file_idx (4 bytes): points to StringIds[#10] -> "User.java"
00 00 00 00 // annotations_off (4 bytes): 0 (No annotations)
00 08 00 00 // class_data_off (4 bytes): 0x0800 (🔥 Core: points to specific data in the Data section)
00 00 00 00 // static_values_off (4 bytes): 0 (No static initial values)Data Section: The place where actual code instructions (Dalvik bytecode) and constant values are truly stored.
When our code needs to load a class (prior to the introduction ofDexCache and hash table optimizations), the underlying mechanism relies on mmap (memory mapping + page faults) + binary search. Physically speaking, all String IDs are stored in sorted order in the DEX file, and the strings in the Data section are tightly packed in the same order. Thus, we can read the middle string first, compare for binary search until we find the target. During this process, multiple page faults will occur, loading the corresponding 4KB data pages on demand.
Once the string is found, we use the same logic to find the corresponding type in Type IDs, and then find the class definition in Class Defs. The class metadata is then loaded into the Method Area in memory. Only when a specific method is actually executed will the system jump to the Data section to load and execute the corresponding bytecode.




