NDK Android:什么是Native Development Kit及用于C++的JNI

作者: IT Sectr 发布日期: 2026-02-09 阅读时间: 10 分钟

NDK(Native Development Kit)— 是一个用C和C++编写Android应用程序部分的工具包。与普通的Android SDK不同,NDK将代码编译为本机库(.so),直接在处理器上运行,无需虚拟机层。根据Google NDK Guides, 2026,NDK用于高性能计算、游戏、音频处理和重用现有的C/C++项目。JNI(Java Native Interface)将本机代码与Kotlin和Java连接起来。

要点

  • NDK — 用于将C/C++代码编译为Android本机库的工具包。
  • JNI — Java/Kotlin虚拟机与本机代码之间的交互接口。
  • CMake — NDK项目的主要构建系统,通过CMakeLists.txt进行配置。
  • ABI — 目标处理器架构(arm64-v8a、armeabi-v7a、x86_64),库为目标架构构建。
  • NDK 对于大多数Android应用程序来说不是必需的,仅用于对性能要求较高的任务。

什么是NDK

NDK(Native Development Kit) — 是一个用于将C和C++代码交叉编译为Android可执行库的工具集。NDK包括Clang编译器、libc++标准库、Android API头文件和构建工具。开发人员用C或C++编写本机代码,将其编译为.so文件(共享对象),并通过JNI连接到应用程序。

NDK不会取代Android SDK,而是对其进行补充。整个UI、生命周期和系统服务仍使用Kotlin或Java。本机代码解决特定任务:数学计算、加密、编解码器、游戏中的物理效果。Google建议仅在SDK无法提供所需性能或硬件功能访问时才使用NDK。

NDK的历史始于2009年(NDK r1)。在此期间,这些工具从实验性脚本集发展成为支持CMake、LLDB调试和分析的成熟系统。截至2026年,当前版本为NDK r27,包含Clang 19、对C++20的完整支持以及libc++作为唯一的标准库。

NDK的关键功能

NDK通过本机API为开发人员提供对Android底层功能的访问。主要使用场景包括:使用OpenGL ES和Vulkan进行3D图形处理、通过Oboe进行音频处理、通过BoringSSL进行加密操作以及针对ARM的NEON优化。所有这些API都可以从C/C++访问,并且需要NDK进行编译。

此外,NDK允许重用现有的C/C++库,而无需重写为Kotlin。这对于具有多年C++历史的项目尤其重要——游戏引擎(Unity、Unreal Engine)、计算机视觉库(OpenCV)或加密包(OpenSSL)。在这种情况下,NDK可以节省多年的开发时间。

组件用途
Clang支持C++20的C/C++编译器
libc++标准C++库(NDK r27中唯一)
CMake本机库的构建系统
LLDBAndroid Studio中的本机代码调试器
ndk-build过时的构建系统(已被CMake替代)

NDK与SDK:何时需要本机代码

在NDK和纯SDK之间的选择取决于任务。Android SDK为90%的场景提供现成的Java/Kotlin API——网络、文件、通知、相机操作。当这些API无法提供所需的性能或功能时,就会连接NDK。例如,通过SDK进行实时音频处理是可能的,但延迟为50-200毫秒,而通过Oboe的本机库将延迟降低到5-15毫秒。

APK大小——另一个因素。添加NDK会增加应用程序的大小,因为每个ABI架构都需要单独的.so库。但是,使用Android App Bundle(AAB)可以缓解这个问题:Google Play只向用户提供其架构的库。一个典型的本机项目会在设备上增加1-10 MB的安装大小。

SDK和NDK按标准比较

标准SDK(Kotlin/Java)NDK(C/C++)
性能中等(JIT/AOT编译)高(机器码)
音频延迟50-200毫秒通过Oboe为5-15毫秒
3D图形通过Canvas/OpenGL Kotlin API直接Vulkan/OpenGL
开发复杂度高(内存管理,JNI)
代码可移植性仅限AndroidLinux、Windows、macOS、iOS
APK大小最小每个库+1-10 MB

NDK架构:工具和库

NDK通过SDK Manager与Android SDK分开安装。在NDK目录中包含工具链(编译器)、平台库、头文件和工具。NDK工具链包括用于针对所有目标ABI进行交叉编译的Clang:arm64-v8a、armeabi-v7a、x86_64和x86。编译器根据CMake或ndk-build标志自动选择正确的架构。

用于本机代码的Android API由sysroot/usr/include目录中的头文件表示。那里包含了本机代码中所有可用API的声明:用于Activity生命周期的native_activity.h、用于输入事件的input.h、用于传感器的sensor.h。连接这些头文件无需JNI层即可访问硬件功能。

NDK目录结构

text
ndk/
  toolchains/
    llvm/prebuilt/windows-x86_64/
      bin/        // Clang, ld, llvm-profdata
      sysroot/    // Android API头文件
  platforms/
    android-26/  // 适用于API 26的libc、libm、libdl
    android-34/
  sources/
    cxx-stl/     // libc++头文件
  build/
    cmake/       // CMake工具链文件

Android本机API

通过NDK,可以使用以下本机API组:Native App Glue(从C语言管理Activity生命周期)、OpenGL ES 3.2和Vulkan 1.3(图形)、Oboe(低延迟音频)、Neural Networks API(设备上的机器学习)。每个API在NDK组件中都有头文件和静态/动态库。

使用本机API不需要JNI——函数直接从C/C++代码调用。然而,与Kotlin中的UI交互仍然需要JNI。这种混合架构出现在游戏引擎中:图形和物理用C++(通过Vulkan),菜单和UI用Kotlin(通过Jetpack Compose)。

JNI:Kotlin如何调用C++

JNI(Java Native Interface) — 从Java/Kotlin虚拟机调用本机代码的标准机制。开发人员在Kotlin中使用external关键字声明外部函数,并通过System.loadLibrary连接.so库。在C++端,函数使用JNI命名约定声明,该约定对包和类名进行编码。

在Kotlin中声明本机函数

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

// 在代码中使用
val bridge = NativeBridge()
println(bridge.stringFromJNI())  // "Hello from C++"

在C++中实现JNI函数

cpp
#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;
}

为NDK配置CMakeLists.txt

CMake — NDK的主要构建系统,取代了过时的ndk-build。CMakeLists.txt文件描述源文件、库和编译标志。如果在build.gradle中配置了externalNativeBuild,Android Studio会在构建项目时自动调用CMake。CMake为每个目标ABI生成make文件,并并行编译本机代码。

本机库的CMakeLists.txt示例

cmake
cmake_minimum_required(VERSION 3.22.1)
project("nativelib")

# 连接头文件
include_directories(src/main/cpp/include)

# 创建本机库
add_library(
    native-lib
    SHARED
    src/main/cpp/native-lib.cpp
    src/main/cpp/math_utils.cpp
    src/main/cpp/audio_processor.cpp
)

# 连接Android系统库
target_link_libraries(
    native-lib
    android
    log
    OpenSLES
    # libc++自动连接
)

# 优化标志
target_compile_options(native-lib PRIVATE -O3 -Wall -Wextra)

为NDK配置build.gradle

groovy
android {
    defaultConfig {
        ndk {
            // 构建的目标ABI
            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和多平台构建

ABI(Application Binary Interface) — 机器码的格式,决定与设备处理器的兼容性。每个ARM或x86架构都有自己的ABI。NDK针对每个指定的ABI分别编译本机代码。2026年最常见的ABI:arm64-v8a(99%的现代设备)、armeabi-v7a(旧的32位设备)、x86_64(模拟器和Chromebook)。

在build.gradle中指定abiFilters将构建限制为仅必要的架构,从而缩短编译时间。对于Google Play,建议包含库兼容的所有ABI——这保证了在所有设备上的运行。Google Play Console允许通过App Bundle配置特定ABI的APK交付。

在本机代码中确定设备的ABI

cpp
#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架构位数设备
arm64-v8aARMv8-A64位几乎所有现代手机
armeabi-v7aARMv7-A32位旧设备(2020年之前)
x86_64x86-6464位模拟器、Chromebook
x86x86 IA-3232位过时的模拟器

C++本机代码示例

让我们看一个实际示例:用于处理浮点数的数学库。由于直接访问NEON ARM指令和运行时没有数组边界检查,C++中的本机代码比Kotlin更高效地执行计算。此示例演示了NDK使用的典型模式——将繁重计算转移到本机层。

本机代码中的数学运算

cpp
#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);

    // 计算平均值和标准差
    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);

    // 归一化:(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;
}

从本机代码记录日志

要调试本机代码,请使用来自android/log.h库的__android_log_print宏。消息会出现在Logcat中,与Java/Kotlin日志一起显示。日志级别(ANDROID_LOG_DEBUG、ANDROID_LOG_ERROR)有助于过滤消息。

cpp
#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++) {
        // 繁重处理
        LOGD("Item %d processed", i);
    }

    LOGD("Processing complete");
}

常见问题

Android开发是否必须使用NDK?

不。对于大多数应用程序,使用Kotlin的SDK就足够了。需要NDK用于高性能任务:实时音频/视频处理、3D图形、加密或重用现有的C/C++项目。

NDK使用什么编译器?

NDK使用来自LLVM工具链的Clang。从NDK r23开始,GCC已被完全移除。Clang为所有Android ABI交叉编译代码:arm64-v8a、armeabi-v7a、x86_64、x86。

在NDK的上下文中什么是ABI?

ABI(Application Binary Interface)——机器码的格式,决定与处理器的兼容性。NDK为每个ABI分别构建.so库。arm64-v8a是现代Android设备的主要ABI。

能否通过NDK调试C++代码?

可以。Android Studio支持LLDB——本机代码调试器。可以在C++文件中设置断点,查看变量和调用堆栈。需要NDK和LLDB插件才能工作。

NDK如何影响APK大小?

每个本机库会为APK增加100 KB到几MB的大小。每个ABI都需要单独的.so库。Android App Bundle只向用户提供适合的架构,从而减少安装大小。

总结

  • NDK — 通过Clang编译器将C/C++代码交叉编译为Android本机库的工具集。
  • JNI — 通过external关键字和函数命名约定将Kotlin/Java与本机代码连接的接口。
  • CMake — NDK的主要构建系统,通过CMakeLists.txt文件和Gradle中的externalNativeBuild进行配置。
  • ABI 确定处理器架构——arm64-v8a、armeabi-v7a、x86_64。每个ABI都需要单独的库构建。
  • 仅将NDK用于需要高性能的任务:游戏、音频、图形、加密、设备上的机器学习。
  • Google Play通过App Bundle支持ABI分离:用户只接收其设备架构的库。
  • 本机库显著增加APK大小,但为关键操作提供最大性能和低延迟。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读