NDK (Native Development Kit) là bộ công cụ để viết một phần ứng dụng bằng C và C++ cho Android. Không giống như Android SDK thông thường, NDK biên dịch mã thành các thư viện gốc (.so) hoạt động trực tiếp với bộ xử lý mà không có lớp máy ảo. Theo Google NDK Guides, 2026, NDK được sử dụng cho tính toán hiệu suất cao, trò chơi, xử lý âm thanh và tái sử dụng các dự án C/C++ hiện có. JNI (Java Native Interface) kết nối mã gốc với Kotlin và Java.
Những điểm chính
NDK (Native Development Kit) là một bộ công cụ để biên dịch chéo mã C và C++ thành các thư viện thực thi cho Android. NDK bao gồm trình biên dịch Clang, thư viện chuẩn libc++, các tệp tiêu đề API Android và tiện ích xây dựng. Nhà phát triển viết mã gốc bằng C hoặc C++, biên dịch nó thành các tệp .so (đối tượng chia sẻ) và kết nối với ứng dụng qua JNI.
NDK không thay thế Android SDK mà bổ sung cho nó. Toàn bộ giao diện người dùng, vòng đời và dịch vụ hệ thống vẫn nằm trong Kotlin hoặc Java. Mã gốc giải quyết các tác vụ cụ thể: tính toán toán học, mật mã, codec, vật lý trong trò chơi. Google khuyến nghị chỉ sử dụng NDK khi SDK không cung cấp hiệu suất hoặc quyền truy cập cần thiết vào khả năng phần cứng.
Lịch sử của NDK bắt đầu từ năm 2009 (NDK r1). Trong thời gian này, bộ công cụ đã phát triển từ một tập hợp kịch bản thử nghiệm thành một hệ thống trưởng thành với hỗ trợ CMake, gỡ lỗi LLDB và phân tích hiệu năng. Tính đến năm 2026, phiên bản hiện tại là NDK r27 với Clang 19, hỗ trợ đầy đủ C++20 và libc++ là thư viện chuẩn duy nhất.
NDK cung cấp cho nhà phát triển quyền truy cập vào các khả năng cấp thấp của Android thông qua các API gốc. Các trường hợp sử dụng chính bao gồm: làm việc với OpenGL ES và Vulkan cho đồ họa 3D, xử lý âm thanh qua Oboe, hoạt động mật mã qua BoringSSL và tối ưu hóa NEON cho ARM. Tất cả các API này đều có sẵn từ C/C++ và yêu cầu NDK để biên dịch.
Ngoài ra, NDK cho phép tái sử dụng các thư viện C/C++ hiện có mà không cần viết lại bằng Kotlin. Điều này đặc biệt phù hợp với các dự án có lịch sử lâu dài bằng C++ — công cụ trò chơi (Unity, Unreal Engine), thư viện thị giác máy tính (OpenCV) hoặc gói mật mã (OpenSSL). Trong những trường hợp này, NDK tiết kiệm nhiều năm phát triển.
| Thành phần | Mục đích |
|---|---|
| Clang | Trình biên dịch C/C++ hỗ trợ C++20 |
| libc++ | Thư viện chuẩn C++ (duy nhất trong NDK r27) |
| CMake | Hệ thống xây dựng thư viện gốc |
| LLDB | Trình gỡ lỗi mã gốc trong Android Studio |
| ndk-build | Hệ thống xây dựng cũ (đã thay thế bằng CMake) |
Việc lựa chọn giữa NDK và SDK thuần túy phụ thuộc vào tác vụ. Android SDK cung cấp các API Java/Kotlin sẵn sàng cho 90% kịch bản — mạng, tệp, thông báo, máy ảnh. NDK được sử dụng khi các API này không cung cấp hiệu suất hoặc chức năng cần thiết. Ví dụ, xử lý âm thanh thời gian thực qua SDK có thể nhưng với độ trễ 50–200 ms, trong khi thư viện gốc qua Oboe giảm độ trễ xuống 5–15 ms.
Kích thước APK là một yếu tố khác. Thêm NDK làm tăng kích thước ứng dụng vì mỗi kiến trúc ABI yêu cầu một thư viện .so riêng. Tuy nhiên, sử dụng Android App Bundle (AAB) giảm nhẹ vấn đề: Google Play chỉ gửi cho người dùng thư viện phù hợp với kiến trúc của họ. Một dự án gốc điển hình thêm 1–10 MB vào kích thước cài đặt trên mỗi thiết bị.
| Tiêu chí | SDK (Kotlin/Java) | NDK (C/C++) |
|---|---|---|
| Hiệu suất | Trung bình (biên dịch JIT/AOT) | Cao (mã máy) |
| Độ trễ âm thanh | 50–200 ms | 5–15 ms qua Oboe |
| Đồ họa 3D | Qua API Kotlin Canvas/OpenGL | Vulkan/OpenGL trực tiếp |
| Độ phức tạp phát triển | Thấp | Cao (quản lý bộ nhớ, JNI) |
| Tính di động mã | Chỉ Android | Linux, Windows, macOS, iOS |
| Kích thước APK | Tối thiểu | +1–10 MB mỗi thư viện |
NDK được cài đặt riêng biệt với Android SDK qua SDK Manager. Bên trong thư mục NDK có chuỗi công cụ (trình biên dịch), thư viện nền tảng, tệp tiêu đề và tiện ích. Chuỗi công cụ NDK bao gồm Clang để biên dịch chéo cho tất cả các ABI mục tiêu: arm64-v8a, armeabi-v7a, x86_64 và x86. Trình biên dịch tự động chọn kiến trúc cần thiết dựa trên cờ CMake hoặc ndk-build.
Các API Android cho mã gốc được cung cấp bởi các tệp tiêu đề trong thư mục sysroot/usr/include. Ở đó có các khai báo cho tất cả API có sẵn trong mã gốc: native_activity.h cho vòng đời Activity, input.h cho sự kiện đầu vào, sensor.h cho cảm biến. Bao gồm các tiêu đề này cho phép truy cập khả năng phần cứng mà không cần lớp JNI.
ndk/
toolchains/
llvm/prebuilt/windows-x86_64/
bin/ // Clang, ld, llvm-profdata
sysroot/ // Tệp tiêu đề API Android
platforms/
android-26/ // libc, libm, libdl cho API 26
android-34/
sources/
cxx-stl/ // Tiêu đề libc++
build/
cmake/ // Tệp chuỗi công cụ CMake
Thông qua NDK, các nhóm API gốc sau đây có sẵn: Native App Glue (quản lý vòng đời Activity từ C), OpenGL ES 3.2 và Vulkan 1.3 (đồ họa), Oboe (âm thanh độ trễ thấp), Neural Networks API (học máy trên thiết bị). Mỗi API có một tệp tiêu đề và thư viện tĩnh/động như một phần của NDK.
Làm việc với API gốc không yêu cầu JNI — các hàm được gọi trực tiếp từ mã C/C++. Tuy nhiên, tương tác với giao diện người dùng Kotlin vẫn yêu cầu JNI. Kiến trúc lai này phổ biến trong các công cụ trò chơi: đồ họa và vật lý bằng C++ (qua Vulkan), menu và giao diện người dùng bằng Kotlin (qua Jetpack Compose).
JNI (Java Native Interface) là cơ chế tiêu chuẩn để gọi mã gốc từ máy ảo Java/Kotlin. Nhà phát triển khai báo một hàm bên ngoài trong Kotlin với từ khóa external và tải thư viện .so qua System.loadLibrary. Về phía C++, hàm được khai báo bằng quy ước đặt tên JNI, mã hóa tên gói và lớp.
class NativeBridge {
companion object {
init {
System.loadLibrary("native-lib")
}
}
external fun stringFromJNI(): String
external fun fibonacci(n: Int): Long
external fun processBuffer(data: ByteArray): ByteArray
}
// Sử dụng trong mã
val bridge = NativeBridge()
println(bridge.stringFromJNI()) // "Hello from C++"
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_app_NativeBridge_stringFromJNI(
JNIEnv* env, jobject /* this */) {
std::string hello = "Hello from C++ NDK";
return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT jlong JNICALL
Java_com_example_app_NativeBridge_fibonacci(
JNIEnv* env, jobject /* this */, jint n) {
if (n <= 1) return n;
jlong a = 0, b = 1;
for (int i = 2; i <= n; i++) {
jlong temp = a + b;
a = b;
b = temp;
}
return b;
}
CMake là hệ thống xây dựng chính cho NDK, đã thay thế ndk-build cũ. Tệp CMakeLists.txt mô tả các tệp nguồn, thư viện và cờ biên dịch. Android Studio tự động gọi CMake khi xây dựng dự án nếu externalNativeBuild được cấu hình trong build.gradle. CMake tạo các tệp make cho mỗi ABI mục tiêu và biên dịch mã gốc song song.
cmake_minimum_required(VERSION 3.22.1)
project("nativelib")
# Bao gồm tệp tiêu đề
include_directories(src/main/cpp/include)
# Tạo thư viện gốc
add_library(
native-lib
SHARED
src/main/cpp/native-lib.cpp
src/main/cpp/math_utils.cpp
src/main/cpp/audio_processor.cpp
)
# Liên kết thư viện hệ thống Android
target_link_libraries(
native-lib
android
log
OpenSLES
# libc++ được liên kết tự động
)
# Cờ tối ưu hóa
target_compile_options(native-lib PRIVATE -O3 -Wall -Wextra)
android {
defaultConfig {
ndk {
// ABI mục tiêu cho xây dựng
abiFilters "arm64-v8a", "armeabi-v7a", "x86_64"
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
version "3.22.1"
}
}
buildTypes {
release {
externalNativeBuild {
cmake {
arguments "-DCMAKE_BUILD_TYPE=Release"
}
}
}
}
}
ABI (Application Binary Interface) là định dạng mã máy xác định khả năng tương thích với bộ xử lý của thiết bị. Mỗi kiến trúc ARM hoặc x86 có ABI riêng. NDK biên dịch mã gốc riêng biệt cho mỗi ABI được chỉ định. Các ABI phổ biến nhất vào năm 2026: arm64-v8a (99% thiết bị hiện đại), armeabi-v7a (thiết bị cũ 32-bit), x86_64 (trình giả lập và Chromebook).
Chỉ định abiFilters trong build.gradle giới hạn việc xây dựng chỉ cho các kiến trúc cần thiết, giảm thời gian biên dịch. Đối với Google Play, nên bao gồm tất cả ABI mà thư viện hỗ trợ — điều này đảm bảo nó hoạt động trên tất cả thiết bị. Google Play Console cho phép cấu hình phân phối APK cụ thể theo ABI qua App Bundle.
#include <jni.h>
#include <android/api-level.h>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_app_NativeBridge_getABIInfo(
JNIEnv* env, jobject /* this */) {
#if defined(__arm__)
#if defined(__ARM_ARCH_7A__)
return env->NewStringUTF("armeabi-v7a");
#endif
#elif defined(__aarch64__)
return env->NewStringUTF("arm64-v8a");
#elif defined(__x86_64__)
return env->NewStringUTF("x86_64");
#elif defined(__i386__)
return env->NewStringUTF("x86");
#endif
return env->NewStringUTF("unknown");
}
| ABI | Kiến trúc | Số bit | Thiết bị |
|---|---|---|---|
| arm64-v8a | ARMv8-A | 64-bit | Gần như tất cả điện thoại hiện đại |
| armeabi-v7a | ARMv7-A | 32-bit | Thiết bị cũ (trước 2020) |
| x86_64 | x86-64 | 64-bit | Trình giả lập, Chromebook |
| x86 | x86 IA-32 | 32-bit | Trình giả lập cũ |
Hãy xem xét một ví dụ thực tế: thư viện toán học làm việc với số dấu phẩy động. Mã gốc C++ thực hiện tính toán hiệu quả hơn Kotlin nhờ truy cập trực tiếp vào các lệnh NEON của ARM và không có kiểm tra ranh giới mảng trong thời gian chạy. Ví dụ này minh họa mẫu sử dụng NDK điển hình — chuyển các tính toán nặng sang lớp gốc.
#include <jni.h>
#include <cmath>
#include <vector>
extern "C" JNIEXPORT jfloatArray JNICALL
Java_com_example_app_NativeBridge_normalizeArray(
JNIEnv* env, jobject, jfloatArray input) {
jsize len = env->GetArrayLength(input);
jfloat* elements = env->GetFloatArrayElements(input, nullptr);
// Tính giá trị trung bình và độ lệch chuẩn
float sum = 0.0f, sumSq = 0.0f;
for (jsize i = 0; i < len; i++) {
sum += elements[i];
sumSq += elements[i] * elements[i];
}
float mean = sum / len;
float stddev = std::sqrt(sumSq / len - mean * mean);
// Chuẩn hóa: (x - mean) / stddev
jfloat* result = new jfloat[len];
for (jsize i = 0; i < len; i++) {
result[i] = (elements[i] - mean) / stddev;
}
env->ReleaseFloatArrayElements(input, elements, JNI_ABORT);
jfloatArray output = env->NewFloatArray(len);
env->SetFloatArrayRegion(output, 0, len, result);
delete[] result;
return output;
}
Để gỡ lỗi mã gốc, hãy sử dụng macro __android_log_print từ thư viện android/log.h. Các thông báo xuất hiện trong Logcat cùng với nhật ký Java/Kotlin. Mức ghi nhật ký (ANDROID_LOG_DEBUG, ANDROID_LOG_ERROR) giúp lọc thông báo.
#include <android/log.h>
#define LOG_TAG "NativeLib"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
extern "C" JNIEXPORT void JNICALL
Java_com_example_app_NativeBridge_processData(
JNIEnv* env, jobject, jint count) {
LOGD("Processing %d items", count);
for (int i = 0; i < count; i++) {
// Xử lý nặng
LOGD("Item %d processed", i);
}
LOGD("Processing complete");
}
Câu hỏi thường gặp
Không. Đối với hầu hết ứng dụng, SDK Kotlin là đủ. NDK cần thiết cho các tác vụ hiệu suất cao: xử lý âm thanh/video thời gian thực, đồ họa 3D, mật mã hoặc tái sử dụng dự án C/C++ hiện có.
NDK sử dụng Clang từ chuỗi công cụ LLVM. Từ NDK r23, GCC đã bị loại bỏ hoàn toàn. Clang biên dịch chéo cho tất cả ABI Android: arm64-v8a, armeabi-v7a, x86_64, x86.
ABI (Application Binary Interface) là định dạng mã máy xác định khả năng tương thích với bộ xử lý. NDK xây dựng thư viện .so riêng cho mỗi ABI. arm64-v8a là ABI chính cho thiết bị Android hiện đại.
Có. Android Studio hỗ trợ LLDB — trình gỡ lỗi mã gốc. Bạn có thể đặt điểm dừng trong tệp C++, xem biến và ngăn xếp cuộc gọi. Yêu cầu NDK và plugin LLDB.
Mỗi thư viện gốc thêm 100 KB đến vài MB vào APK. Mỗi ABI yêu cầu một thư viện .so riêng. Android App Bundle chỉ gửi đến người dùng kiến trúc phù hợp, giảm kích thước cài đặt.
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