DEX: cấu trúc và nguyên lý hoạt động của bytecode

Tác giả: IT Sectr Đã đăng: 2026-04-15 Thời gian đọc: 8 phút

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 là định dạng bytecode cho Android, được thực thi trên Dalvik hoặc ART.
  • Tính nhỏ gọn — DEX chiếm ít hơn 30% dung lượng so với bytecode Java tiêu chuẩn.
  • Multidex — cơ chế vượt qua giới hạn 65536 phương thức trong một tệp DEX.
  • ART — Android Runtime, thay thế Dalvik, biên dịch DEX thành mã gốc khi cài đặt.
  • D8 — trình biên dịch hiện đại từ Java/Kotlin sang DEX, thay thế DX từ năm 2018.

DEX là gì và tại sao cần nó

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.

Từ Java đến DEX

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.

Đặc điểm kiến trúc

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.

Cấu trúc tệp DEX: các phần và tiêu đề

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ầnMục đích
headerTiêu đề: magic, tổng kiểm tra, chữ ký, kích thước và offset của các phần
string_idsBảng chuỗi: tên lớp, phương thức, trường
type_idsKiểu: tham chiếu đến định danh chuỗi kiểu
proto_idsNguyên mẫu phương thức: kiểu trả về và tham số
field_idsTrường của lớp: lớp, kiểu, tên
method_idsPhươ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
dataDữ liệu thực tế: mã phương thức, chú thích, thông tin gỡ lỗi

Tiêu đề DEX

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.

Nhóm hằng số

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 biên dịch Java và Kotlin sang DEX

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.

Giai đoạn 1: Biên dịch thành .class

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.

Giai đoạn 2: Biên dịch D8

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.

kotlin
// 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 vs DX

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.

Dalvik vs ART: cách thực thi DEX đã thay đổi

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 VM: biên dịch JIT

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: biên dịch AOT

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ơ.

dex2oat: chuyển đổi khi cài đặt

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.

Multidex: vượt qua giới hạn 64K phương thức

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.

Cơ chế Multidex

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.

kotlin
// 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)
    }
}

Vấn đề của Multidex

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: ProGuard, R8 và làm rối mã

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 vs ProGuard

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.

Quy tắc R8

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.

Dịch ngược DEX: công cụ và bảo vệ

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.

Công cụ dịch ngượ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.

Phương pháp bảo vệ

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 khác bytecode Java như thế nào?

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à gì?

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.

Làm thế nào để kiểm tra số lượng phương thức trong 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.

Số lượng tệp DEX có ảnh hưởng đến hiệu suất không?

, 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ó thể chạy DEX mà không cần Android không?

, 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

  • DEX là định dạng bytecode Android với kiến trúc dựa trên thanh ghi và biểu diễn mã nhỏ gọn.
  • Cấu trúc bao gồm tiêu đề, bảng định danh và phần dữ liệu với các lệnh.
  • Biên dịch sang DEX được thực hiện qua D8: .class → DEX với tối ưu hóa và hợp nhất nhóm hằng số.
  • ART biên dịch DEX thành mã gốc khi cài đặt (AOT), tăng tốc khởi động ứng dụng.
  • Multidex giải quyết giới hạn 65536 phương thức bằng cách chia thành nhiều tệp DEX.
  • Tối ưu hóa — R8 giảm DEX 20–40%, làm rối tên và loại bỏ mã chết.
  • Bảo vệ — làm rối với R8/ProGuard, DexGuard và O-LLVM ngăn dịch ngược DEX.

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.

Thảo luận dự án

Đọc thêm