C++ trong phát triển di động: Android NDK, Unity và Unreal Engine

Tác giả: IT Sectr Đã đăng: 2026-02-10 Thời gian đọc: 11 phút

C++ là ngôn ngữ lập trình đa mô hình với quản lý bộ nhớ thủ công, kết hợp lập trình thủ tục, hướng đối tượng và tổng quát. Trong phát triển di động, C++ được sử dụng qua Android NDK để chuyển thư viện và qua các game engine Unity và Unreal Engine để tạo game đa nền tảng. Theo TIOBE (2026), C++ nằm trong top 5 ngôn ngữ phổ biến nhất trong giới lập trình viên chuyên nghiệp.

Những điểm chính

  • C++ là ngôn ngữ lập trình hệ thống được sử dụng trong Android NDK, Unity và Unreal Engine
  • Android NDK cho phép biên dịch mã C++ thành thư viện .so gốc cho bộ xử lý ARM
  • Unreal Engine hoàn toàn được xây dựng trên C++, trong khi Unity sử dụng C++ cho plugin cấp thấp
  • JNI (Java Native Interface) là cầu nối giữa Java/Kotlin và C++ trên Android
  • CMake và NDK Build là hệ thống xây dựng cho các dự án C++ trên nền tảng di động

C++ là gì?

C++ là ngôn ngữ lập trình được tạo ra bởi Bjarne Stroustrup vào năm 1985 như một phần mở rộng của ngôn ngữ C với hỗ trợ lớp. C++ cung cấp truy cập bộ nhớ trực tiếp qua con trỏ, lập trình khuôn mẫu và đa kế thừa. Các chuẩn C++17 và C++20 đã thêm coroutine, concept và range.

Không giống Java và Kotlin, C++ không có bộ thu gom rác — lập trình viên tự quản lý việc cấp phát và giải phóng bộ nhớ qua các toán tử new và delete. Điều này cho phép kiểm soát hoàn toàn hiệu năng nhưng đòi hỏi kỷ luật. Theo Khảo sát nhà phát triển JetBrains (2025), 42% lập trình viên C++ sử dụng ngôn ngữ này trong ngành công nghiệp game.

Các chuẩn C++ phát triển ba năm một lần: C++11 (lambda, auto, unique_ptr), C++14 (lambda tổng quát), C++17 (if constexpr, filesystem), C++20 (module, coroutine, concept). Trình biên dịch Clang và GCC hỗ trợ tất cả các chuẩn hiện tại và Android NDK sử dụng Clang từ chuỗi công cụ LLVM.

Sự khác biệt giữa C++ và Java/Kotlin

Sự khác biệt chính giữa C++ và các ngôn ngữ JVM là quản lý bộ nhớ thủ công và biên dịch trực tiếp thành mã máy. Java và Kotlin chạy trên máy ảo với bộ thu gom rác tự động. Mã C++ được biên dịch thành tệp nhị phân gốc cho một kiến trúc cụ thể — ARM, x86 hoặc RISC-V.

Tiêu chíC++Java / Kotlin
Quản lý bộ nhớThủ công (new/delete, RAII)Tự động (Bộ thu gom rác)
Biên dịchMã máy gốcBytecode JVM / ART
Hiệu năngTối đa (trừu tượng zero-cost)Cao với tạm dừng GC
Sử dụng trong phát triển di độngNDK, game, thư việnỨng dụng UI, logic kinh doanh
Xây dựngCMake, NDK Build, Gradle + NDKGradle, Maven
Đa nền tảngBiên dịch cho từng nền tảngJVM / ART ở mọi nơi

C++ trong phát triển di động: Android NDK

Android NDK (Native Development Kit) là bộ công cụ của Google để viết các phần của ứng dụng Android bằng C và C++. NDK bao gồm chuỗi công cụ dựa trên Clang, tệp tiêu đề hệ thống Android và thư viện gỡ lỗi và phân tích hiệu năng. Phiên bản r27 (2025) hỗ trợ C++20 và sử dụng LLVM 18.

NDK biên dịch mã C++ thành thư viện động .so cho các kiến trúc ARM64, ARM32, x86 và x86_64. Ứng dụng Java hoặc Kotlin tải các thư viện này qua System.loadLibrary() và gọi hàm gốc qua JNI. Theo Google, NDK đã được tải xuống hơn 5 triệu lần.

Cấu trúc dự án với NDK

Một dự án Android NDK điển hình chứa thư mục app/src/main/cpp/ với tệp nguồn C++, CMakeLists.txt để xây dựng và một lớp Java khai báo các phương thức gốc. Gradkết nối NDK qua khối externalNativeBuild trong build.gradle.

cpp
// native-lib.cpp — phương thức JNI đơn giản cho Android
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_app_MainActivity_stringFromJNI(
    JNIEnv *env,
    jobject /*thiz*/) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

Mã khai báo một hàm JNI với tên đầy đủ Java_com_example_app_MainActivity_stringFromJNI — đây là quy ước đặt tên JNI. Hàm nhận một con trỏ tới JNIEnv (môi trường thực thi) và jobject (thể hiện của lớp gọi). env->NewStringUTF tạo một chuỗi Java từ chuỗi C++.

ABI và kiến trúc mục tiêu

Android hỗ trợ nhiều ABI (Giao diện nhị phân ứng dụng): arm64-v8a (thiết bị ARM 64-bit, 99% điện thoại thông minh hiện đại), armeabi-v7a (ARM 32-bit, thiết bị cũ), x86_64 (trình giả lập). NDK cho phép xây dựng .so cho mỗi kiến trúc qua bộ lọc ABI trong build.gradle.

gradle
android {
    defaultConfig {
        ndk {
            abiFilters "arm64-v8a", "armeabi-v7a"
        }
    }
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.22.1"
        }
    }
}

C++ và game engine: Unity và Unreal Engine

C++ là ngôn ngữ chính của các game engine. Unreal Engine sử dụng C++ cho toàn bộ mã game, trong khi Unity sử dụng C++ trong lõi engine để kết xuất và vật lý. Ngay cả khi lập trình viên viết bằng C# trong Unity, bên dưới C++ quản lý đồ họa, âm thanh và tính toán vật lý.

Unreal Engine: C++ là ngôn ngữ chính

Unreal Engine được xây dựng trên C++ từ đầu. Mã logic game được viết bằng C++ sử dụng macro UHT (Unreal Header Tool) để tích hợp với Blueprints — hệ thống kịch bản trực quan. Các lớp kế thừa từ các kiểu cơ sở AActor, UObject, UGameInstance với các chữ cái tiền tố U, A, F.

cpp
// Lớp nhân vật trong Unreal Engine sử dụng C++
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"

UCLASS()
class AMyCharacter : public ACharacter
{
    GENERATED_BODY()

public:
    AMyCharacter();

    virtual void BeginPlay() override;
    virtual void Tick(float DeltaTime) override;

    UPROPERTY(EditAnywhere, Category = "Movement")
    float MoveSpeed = 600.0f;

protected:
    void SetupPlayerInputComponent(UInputComponent *PlayerInputComponent) override;
};

Macro UCLASS() và UPROPERTY() là một phần của hệ thống phản chiếu Unreal Engine. UHT (Unreal Header Tool) tạo mã C++ từ tệp tiêu đề để tuần tự hóa, blueprint và sao chép mạng. MoveSpeed được hiển thị trong trình chỉnh sửa qua UPROPERTY(EditAnywhere).

Unity: C++ trong plugin và SDK gốc

Unity sử dụng C# cho logic game, nhưng C++ được sử dụng trong Plugin gốc — thư viện .so cho Android và .bundle cho iOS. Qua Android NDK, có thể tích hợp SDK C++ cho mạng quảng cáo, phân tích hoặc chuyển thư viện C++ từ game desktop.

csharp
using System.Runtime.InteropServices;
using UnityEngine;

public class NativeBridge : MonoBehaviour
{
    const string LIB_NAME = "native_plugin";

    [DllImport(LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
    private static extern int ProcessFrame(
        int width, int height,
        IntPtr pixelData
    );

    void Update()
    {
        int result = ProcessFrame(Screen.width, Screen.height, IntPtr.Zero);
        Debug.Log("Frame processed: " + result);
    }
}

Trong ví dụ này, C# marshals một lời gọi tới hàm C++ ProcessFrame qua P/Invoke. Thuộc tính DllImport chỉ định tên thư viện .so. Hàm C++ xử lý khung hình theo thời gian thực và trả về kết quả. Cách tiếp cận này được sử dụng cho hậu kỳ và thị giác máy tính.

JNI: tương tác giữa Java và C++

JNI (Java Native Interface) là cơ chế tiêu chuẩn để gọi mã C/C++ từ Java hoặc Kotlin. Trong Android, JNI là cách duy nhất để truyền dữ liệu giữa máy ảo ART và thư viện gốc. Mỗi lời gọi JNI có chi phí, do đó dữ liệu nên được truyền theo lô thay vì từng trường một.

JNIEnv là con trỏ tới bảng hàm JNI. env->FindClass tải một lớp Java, env->GetMethodID lấy định danh phương thức, env->CallVoidMethod gọi nó. Đối với chuỗi, sử dụng GetStringUTFChars và ReleaseStringUTFChars. JNI cũng hỗ trợ tham chiếu toàn cục và yếu tới đối tượng Java.

cpp
// Truyền mảng byte từ C++ sang Java qua JNI
extern "C" JNIEXPORT void JNICALL
Java_com_example_app_ImageProcessor_processArray(
    JNIEnv *env, jobject /*thiz*/,
    jbyteArray input, jint length) {

    jbyte *buffer = env->GetByteArrayElements(input, nullptr);
    jsize size = env->GetArrayLength(input);

    for (jsize i = 0; i < size; ++i) {
        buffer[i] = buffer[i] ^ 0xFF; // đảo ngược byte
    }

    env->ReleaseByteArrayElements(input, buffer, JNI_COMMIT);
}

GetByteArrayElements lấy con trỏ tới mảng byte Java. JNI_COMMIT trong ReleaseByteArrayElements sao chép thay đổi trở lại mảng Java mà không giải phóng bộ nhớ. Xử lý vòng lặp for chạy trên CPU với hiệu năng C++ tối đa không có tạm dừng GC.

Quản lý bộ nhớ trong JNI

Nguy hiểm chính của JNI là rò rỉ tham chiếu toàn cục. Mỗi NewGlobalRef phải được giải phóng qua DeleteGlobalRef. Tham chiếu cục bộ (do FindClass, NewStringUTF trả về) tự động được giải phóng khi trở về từ hàm gốc. Đối với các lời gọi chuyên sâu, hãy sử dụng PushLocalFrame / PopLocalFrame.

CMake và hệ thống xây dựng cho C++ trên Android

CMake là công cụ xây dựng chính cho C++ trong Android NDK. CMakeLists.txt mô tả mục tiêu xây dựng, thư viện và phụ thuộc. Gradle gọi CMake qua externalNativeBuild, truyền đường dẫn tới NDK và chuỗi công cụ. Một giải pháp thay thế là NDK Build dựa trên Android.mk và Application.mk.

CMake 3.22+ hỗ trợ cài đặt trước (CMakePresets.json) cho các cấu hình khác nhau: debug, release, profile với các cờ tối ưu hóa khác nhau. Đối với Android, chuỗi công cụ android.toolchain.cmake từ NDK được sử dụng, do Gradle tự động cung cấp trong quá trình xây dựng.

cmake
# CMakeLists.txt cho dự án Android NDK
cmake_minimum_required(VERSION 3.22)
project("native-lib")

add_library(
    native-lib
    SHARED
    src/main/cpp/native-lib.cpp
    src/main/cpp/image_utils.cpp
)

target_include_directories(
    native-lib PRIVATE
    src/main/cpp/include
)

target_link_libraries(
    native-lib
    android
    log
    ${CMAKE_SOURCE_DIR}/libs/libjpeg.a
)

set_target_properties(
    native-lib PROPERTIES
    CXX_STANDARD 20
    CXX_STANDARD_REQUIRED ON
)

add_library(SHARED) tạo thư viện .so. target_link_libraries liên kết thư viện hệ thống android (cho JNI) và log (cho __android_log_print). Thư viện tĩnh libjpeg.a được liên kết để xử lý ảnh JPEG. CXX_STANDARD 20 kích hoạt hỗ trợ C++20.

vcpkg và quản lý phụ thuộc

vcpkg là trình quản lý gói C++ của Microsoft hỗ trợ Android NDK làm nền tảng mục tiêu. Qua vcpkg, có thể cài đặt Boost, OpenCV, SQLite, nlohmann-json và các thư viện khác được biên dịch cho Android ABI. Vcpkg tích hợp với CMake qua CMAKE_TOOLCHAIN_FILE.

Hiệu năng của C++ trên thiết bị di động

C++ cung cấp hiệu năng tối đa trên thiết bị di động nhờ biên dịch thành mã ARM gốc và không có bộ thu gom rác. Theo thử nghiệm của Google (Android Performance Patterns, 2024), mã JNI trong C++ chạy nhanh hơn 3–10 lần so với mã Java tương đương cho các tác vụ tính toán chuyên sâu.

Các yếu tố chính ảnh hưởng đến hiệu năng C++ trên nền tảng di động: địa phương bộ nhớ đệm (bố trí bộ nhớ liên tục), lệnh SIMD NEON cho ARM, giao tiếp JNI bất đồng bộ qua hàng đợi tin nhắn. NEON là phần mở rộng SIMD của ARM, xử lý 128 bit dữ liệu trong một lệnh đơn, không thể thiếu cho xử lý âm thanh, video và hình ảnh.

cpp
// Xử lý mảng SIMD qua NEON intrinsics
#include <arm_neon.h>

void process_audio_neon(float *data, size_t n) {
    for (size_t i = 0; i < n; i += 4) {
        float32x4_t vec = vld1q_f32(&data[i]);
        float32x4_t result = vmulq_f32(vec, vdupq_n_f32(0.5f));
        vst1q_f32(&data[i], result);
    }
}

vld1q_f32 tải 4 float từ bộ nhớ vào thanh ghi NEON 128-bit. vmulq_f32 nhân cả 4 phần tử với 0.5 trong một chu kỳ. vst1q_f32 lưu kết quả trở lại. Không có SIMD, cùng một vòng lặp sẽ thực hiện 4 phép nhân tuần tự — NEON cung cấp tốc độ tăng lên đến 4x cho các thao tác như vậy.

Thao tácC++ (ms)Java/Kotlin (ms)Tăng tốc
Xử lý ảnh 1920x108012453.75x
Biến đổi Fourier nhanh (FFT)8344.25x
Mã hóa AES-256 khối 1MB3186x
Nén dữ liệu ZLIB5224.4x

Lỗi thường gặp khi sử dụng C++ trong dự án di động

Lỗi đầu tiên và nguy hiểm nhất là rò rỉ bộ nhớ. Mỗi new phải có delete, mỗi new[] phải có delete[]. Trên thiết bị di động với RAM hạn chế, bất kỳ rò rỉ nào cũng dẫn đến OOM (hết bộ nhớ) và ứng dụng bị treo. Sử dụng RAII qua con trỏ thông minh: std::unique_ptr, std::shared_ptr, std::weak_ptr.

Lỗi thứ hai là bỏ qua tương thích ABI. Thư viện được biên dịch cho armeabi-v7a sẽ không tải được trên thiết bị ARM64. Luôn chỉ định abiFilters trong build.gradle và kiểm thử trên thiết bị thực, không chỉ trên trình giả lập x86_64. Sử dụng Android App Bundle để lọc ABI tự động.

Lỗi thứ ba thường gặp là lời gọi JNI thường xuyên. Mỗi lần chuyển đổi giữa Java và C++ tốn khoảng 50–100 ns. Nếu bạn xử lý một mảng 10000 phần tử từng cái một qua JNI, chi phí sẽ vượt quá công việc hữu ích. Truyền toàn bộ mảng một lần và xử lý ở phía C++.

Vấn đề đa luồng là một lỗi thường gặp khác. C++ có thể khởi tạo std::thread, nhưng chỉ có thể truy cập JNI từ luồng do Java tạo ra. Để gọi phương thức Java từ luồng C++, sử dụng AttachCurrentThread / DetachCurrentThread qua JNI.

cpp
// Gọi Java đúng cách từ luồng C++
JavaVM *g_vm; // con trỏ toàn cục lấy được tại JNI_OnLoad

void background_task() {
    JNIEnv *env;
    jint res = g_vm->AttachCurrentThread(&env, nullptr);
    if (res == JNI_OK) {
        jclass clazz = env->FindClass("com/example/App");
        jmethodID mid = env->GetStaticMethodID(clazz, "onProgress", "(I)V");
        env->CallStaticVoidMethod(clazz, mid, 100);
        g_vm->DetachCurrentThread();
    }
}

Câu hỏi thường gặp

Android NDK là gì và tại sao cần C++ trong đó?

Android NDK (Native Development Kit) là bộ công cụ để viết các phần của ứng dụng bằng C hoặc C++. Nó được sử dụng cho các tác vụ quan trọng về hiệu năng, chuyển thư viện C/C++ sang Android và triển khai mã đa nền tảng cho Unity và Unreal Engine.

Những game Unity nào được viết bằng C++?

Trong Unity, mã logic game được viết bằng C#, nhưng C++ được sử dụng cùng với Android NDK cho plugin, tăng tốc phần cứng, chuyển thư viện C++ tùy chỉnh và tích hợp SDK gốc cho quảng cáo và phân tích.

Làm thế nào để cấu hình CMakeLists.txt cho C++ trên Android?

CMakeLists.txt mô tả tệp nguồn, thư viện và cờ biên dịch. Cấu hình tối thiểu bao gồm add_library để xây dựng .so và target_link_libraries để liên kết log Android và các thư viện hệ thống khác.

JNI trong C++ có bắt buộc cho Android không?

JNI (Java Native Interface) là lớp giữa mã Java/Kotlin của ứng dụng Android và thư viện C++. Nếu C++ được gọi từ Java, JNI là bắt buộc. Bạn có thể viết mã C++ hoàn toàn qua NDK, nhưng điểm vào thường là một Activity Java gọi các hàm gốc.

Khi nào C++ được ưu tiên hơn Kotlin trong phát triển di động?

C++ phù hợp cho game, xử lý âm thanh/video thời gian thực, thị giác máy tính, mật mã, mô phỏng vật lý và chuyển thư viện từ các nền tảng khác. Đối với ứng dụng UI thông thường, Kotlin hiệu quả và đơn giản hơn.

Tổng kết

  • C++ là ngôn ngữ đa mô hình với quản lý bộ nhớ thủ công, được sử dụng trong Android NDK, Unity và Unreal Engine cho mã hiệu năng cao
  • Android NDK biên dịch C++ thành thư viện .so cho kiến trúc ARM với chuỗi công cụ Clang từ LLVM
  • Unreal Engine hoàn toàn được xây dựng trên C++ với hệ thống phản chiếu UHT, Unity sử dụng C++ trong lõi kết xuất và vật lý
  • JNI là cầu nối bắt buộc giữa Java/Kotlin và C++ trên Android với quy ước đặt tên Java___
  • CMake là hệ thống xây dựng chính cho C++ trong NDK, hỗ trợ C++20 và tích hợp Gradle
  • NEON SIMD cung cấp tốc độ tăng lên đến 4x cho xử lý dữ liệu trên bộ xử lý ARM qua lệnh vector 128-bit
  • Lỗi thường gặp: rò rỉ bộ nhớ, ABI sai trong abiFilters, lời gọi JNI thường xuyên và đa luồng không có AttachCurrentThread

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