DEX (Dalvik Executable) là định dạng bytecode mà mã nguồn của các ứng dụng Android bằng Java và Kotlin được biên dịch sang. Các tệp DEX được thực thi bởi máy ảo Dalvik (cho đến Android 4.4) hoặc Android Runtime (ART, từ Android 5.0). Theo Android Open Source Project, 2026, định dạng DEX cung cấp trung bình khả năng biểu diễn mã nhỏ gọn hơn 30% so với bytecode Java JVM tiêu chuẩn.
Những điểm chính
DEX (Dalvik Executable) là định dạng bytecode được thiết kế đặc biệt cho thiết bị di động Android. Không giống như bytecode Java tiêu chuẩn (tệp .class), DEX được tối ưu hóa cho tài nguyên hạn chế: ít bộ nhớ hơn, kích thước nhỏ hơn và tải lớp nhanh hơn.
Mã nguồn bằng Java hoặc Kotlin được javac/kotlinc biên dịch thành các tệp .class tiêu chuẩn (bytecode Java). Sau đó, công cụ d8 (hoặc dx trước đây) chuyển đổi .class thành một hoặc nhiều tệp DEX. Việc chuyển đổi này không chỉ đơn thuần là đóng gói lại — d8 thực hiện các tối ưu hóa: hợp nhất các nhóm hằng số, viết lại lệnh thành kiến trúc thanh ghi và loại bỏ dữ liệu trùng lặp.
DEX sử dụng kiến trúc dựa trên thanh ghi (trái ngược với JVM dựa trên ngăn xếp). Mỗi phương thức có số lượng thanh ghi cố định (lên đến 65536). Các lệnh DEX ngắn hơn — trung bình 2 byte so với 1–4 byte trong JVM. Điều này tạo ra mã nhỏ gọn hơn: một ứng dụng điển hình giảm từ 10–15 MB .class xuống còn 4–6 MB .dex.
Tệp DEX có cấu trúc nhị phân được xác định chặt chẽ. Mỗi tệp bắt đầu bằng một tiêu đề và chứa nhiều phần tham chiếu lẫn nhau thông qua các offset.
| Phần | Mục đích |
|---|---|
| header | Tiêu đề: magic, tổng kiểm tra, chữ ký, kích thước và offset của các phần |
| string_ids | Bảng chuỗi: tên lớp, phương thức, trường |
| type_ids | Kiểu: tham chiếu đến định danh chuỗi kiểu |
| proto_ids | Nguyên mẫu phương thức: kiểu trả về và tham số |
| field_ids | Trường của lớp: lớp, kiểu, tên |
| method_ids | Phương thức: lớp, nguyên mẫu, tên |
| class_defs | Định nghĩa lớp: cờ, lớp cha, giao diện, offset dữ liệu |
| data | Dữ liệu thực tế: mã phương thức, chú thích, thông tin gỡ lỗi |
Số ma thuật của DEX là `dex\n035\0` (phiên bản 035). Các phiên bản khác: 036, 037, 038 (cho Android 8.0+). Tiêu đề có kích thước 0x70 byte và chứa tổng kiểm tra SHA-1 và offset của tất cả các phần. Xác thực tiêu đề là bước đầu tiên khi máy ảo tải DEX.
string_ids, type_ids, proto_ids, field_ids, method_ids — là các bảng được đánh chỉ mục. Thay vì lưu trữ tên đầy đủ trong mã phương thức, một chỉ mục 4 byte được sử dụng. Đây là tối ưu hóa quan trọng: nếu một lớp được tham chiếu 100 lần, tên của nó được lưu một lần trong string_ids. dex2oat tối ưu hóa thêm các bảng này trong quá trình biên dịch ART.
Quy trình chuyển đổi mã nguồn thành DEX bao gồm nhiều giai đoạn. Chuỗi công cụ hiện đại sử dụng trình biên dịch D8, thay thế DX vào năm 2018 với Android Gradle Plugin 3.2.
javac (cho Java) hoặc kotlinc (cho Kotlin) biên dịch mã nguồn thành các tệp .class. Mỗi lớp là một tệp .class riêng biệt trong bytecode Java. Ở giai đoạn này, kiểm tra kiểu, tạo phương thức bridge và nội tuyến hằng số được thực hiện.
D8 nhận tất cả các tệp .class và chuyển đổi chúng thành bytecode DEX. D8 thực hiện một số tối ưu hóa: loại bỏ đối số phương thức không sử dụng, hợp nhất các nhóm hằng số từ các tệp .class khác nhau thành một nhóm DEX toàn cục và chuyển đổi lệnh ngăn xếp JVM thành lệnh thanh ghi Dalvik.
// Mã nguồn Kotlin
data class User(
val name: String,
val email: String
)
fun greet(user: User): String {
return "Hello, ${user.name}!"
}
Sau khi biên dịch D8, mã này biến thành các lệnh DEX nhỏ gọn: const-string để tải chuỗi, iget-object để truy cập trường đối tượng, invoke-virtual để gọi StringBuilder.append.
D8 nhanh hơn DX 2–3 lần, tạo DEX nhỏ gọn hơn (nhỏ hơn 5–10%) và tối ưu hóa tốt hơn các cấu trúc đặc thù của Kotlin (hàm inline, lambda). DX đã bị tuyên bố không còn được dùng từ năm 2018 và bị xóa khỏi Android Gradle Plugin 8.0.
Thực thi mã DEX trong Android đã trải qua hai giai đoạn: máy ảo Dalvik gốc (Android 2.2–4.4) và Android Runtime ART (Android 5.0+). Sự khác biệt trong cách tiếp cận biên dịch là căn bản.
Dalvik sử dụng biên dịch Just-In-Time (JIT): bytecode DEX được thông dịch và các phương thức được gọi thường xuyên được biên dịch thành mã gốc ngay lập tức. Ưu điểm — cài đặt nhanh. Nhược điểm — khởi động chậm hơn và tiêu tốn CPU liên tục cho JIT.
ART (Android Runtime) biên dịch DEX thành mã gốc trong quá trình cài đặt ứng dụng thông qua dex2oat. Đây là cách tiếp cận Ahead-Of-Time (AOT): cài đặt lâu hơn, nhưng khởi động nhanh hơn và tiêu thụ điện năng thấp hơn. Từ Android 7.0, ART sử dụng cách tiếp cận kết hợp — AOT + JIT + Tối ưu hóa dựa trên hồ sơ.
Công cụ dex2oat chạy khi cài đặt hoặc cập nhật ứng dụng. Nó biên dịch DEX thành tệp ELF với mã gốc cho kiến trúc thiết bị. Kết quả — các tệp .oat và .art trong thư mục /data/dalvik-cache/. Google liên tục cải thiện dex2oat: trên Android 14, các tối ưu hóa cho thiết bị có thể gập đã được thêm vào.
Giới hạn 65536 phương thức trên mỗi tệp DEX là di sản của kiến trúc Dalvik. Trường method_ids trong tiêu đề DEX chiếm 4 byte, cho tối đa 2^16 = 65536 tham chiếu duy nhất. Các ứng dụng hiện đại với Google Play Services, Firebase và các SDK khác dễ dàng vượt quá giới hạn này.
Multidex là cơ chế chia mã thành nhiều tệp DEX. classes.dex chính chứa các điểm vào (lớp Application, Activity chính), phần còn lại là classes2.dex, classes3.dex, v.v. Khi khởi động, các lớp từ DEX bổ sung được tải qua DexClassLoader.
// build.gradle.kts — bật multidex
android {
defaultConfig {
multiDexEnabled = true
}
}
// Lớp Application hỗ trợ multidex
class MyApp : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
MultiDex.install(this)
}
}
Tải các tệp DEX bổ sung trong quá trình khởi động ứng dụng có thể gây ra ANR (Application Not Responding) trên các thiết bị chạy Android trước 5.0. Khuyến nghị — chỉ sử dụng multidex khi cần thiết và giảm thiểu phụ thuộc để không vượt quá giới hạn.
Tối ưu hóa DEX là bước tiêu chuẩn trong xây dựng ứng dụng Android bản phát hành. Các công cụ R8 và ProGuard giảm kích thước DEX, làm rối mã và loại bỏ các lớp không sử dụng.
R8 là sản phẩm kế thừa của ProGuard, được tích hợp vào Android Gradle Plugin từ năm 2019. R8 thực hiện thu nhỏ, làm rối và tối ưu hóa trong một lần, trong khi ProGuard yêu cầu hai giai đoạn: ProGuard → D8. ProGuard vẫn được hỗ trợ, nhưng Google khuyến nghị R8 cho các dự án mới.
R8 loại bỏ các lớp, phương thức và trường không sử dụng, đổi tên chúng thành tên ngắn (a, b, c), nhúng các hàm inline và loại bỏ mã chết. Kết quả — DEX giảm 20–40% mà không mất chức năng.
Cấu hình R8 được chỉ định trong tệp proguard-rules.pro. Nhà phát triển có thể chỉ định lớp nào không được đổi tên (ví dụ: cho phản chiếu hoặc tuần tự hóa Gson). Firebase và các SDK khác cung cấp quy tắc riêng trong các phụ thuộc của chúng.
DEX có thể được dịch ngược trở lại thành mã Java. Đây là vấn đề bảo mật quan trọng đối với ứng dụng Android: nếu không làm rối, mã được khôi phục ở mức gần với mã gốc.
JADX là trình dịch ngược DEX sang Java phổ biến nhất. Nó khôi phục tên lớp, phương thức, trường và hầu hết logic. apktool dịch ngược DEX thành mã smali (trình hợp dịch Dalvik) — biểu diễn cấp thấp gần với lệnh gốc. Bytecode Viewer kết hợp nhiều trình dịch ngược trong một giao diện.
Làm rối mã với R8/ProGuard là tuyến phòng thủ đầu tiên: tên lớp và phương thức trở nên khó đọc. DexGuard là công cụ thương mại với các phương pháp bổ sung: mã hóa chuỗi, kiểm tra tính toàn vẹn, chống giả mạo. Làm rối luồng điều khiển (O-LLVM) thay đổi cấu trúc mã trong khi vẫn giữ chức năng, làm cho việc phân tích trở nên khó khăn hơn nhiều.
Câu hỏi thường gặp
DEX sử dụng kiến trúc dựa trên thanh ghi thay vì JVM dựa trên ngăn xếp, có định dạng nhỏ gọn hơn (nhỏ hơn 30%), hợp nhất tất cả tệp .class thành một tệp với một nhóm hằng số duy nhất và sử dụng chỉ mục 16 bit thay vì 8 bit.
Smali là trình hợp dịch cho bytecode DEX. Mỗi lệnh DEX có biểu diễn văn bản ở định dạng smali. Công cụ baksmali chuyển đổi DEX thành smali (tháo hợp dịch) và smali hợp dịch smali trở lại thành DEX.
Gradle tác vụ countMethods hoặc plugin dex-method-counts hiển thị số lượng phương thức trong mỗi tệp DEX. Lệnh adb shell với dumpsys cũng hiển thị thống kê DEX đã tải cho các ứng dụng đã cài đặt.
Có, trên các thiết bị chạy Android trước 8.0, nhiều tệp DEX làm chậm khởi động ứng dụng vì mỗi tệp bổ sung được tải riêng biệt. Trên ART với Android 8.0+, sự khác biệt là tối thiểu nhờ biên dịch dex2oat thành một tệp .oat duy nhất.
Có, có các dự án như dexplorer và triển khai JVM tương thích Android có thể thực thi bytecode DEX bên ngoài Android. Tuy nhiên, hầu hết các tệp DEX sử dụng Android API, khiến chúng không phù hợp để chạy trên JVM tiêu chuẩn.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm