1. NDK 开发环境搭建与首个 JNI 程序

NDK 开发环境搭建与首个 JNI 程序 学习卡片

环境准备与版本对齐

NDK 开发链路涉及四个必须版本兼容的组件:Android Studio、Android Gradle Plugin(AGP)、NDK、CMake。版本错配是首次搭建最常见的失败原因,因此先确认工具链版本,再创建工程。

  • Android Studio:建议使用当前稳定版(如 Ladybug 或更新),自带 SDK Manager 与 NDK 下载入口。
  • NDK:通过 SDK Manager 的 SDK Tools 页勾选 NDK (Side by side),安装后位于 $ANDROID_SDK/ndk/<版本号>。推荐使用 LTS 版本(如 27.x),避免使用已弃用的 ndk-bundle 路径。
  • CMake:同样在 SDK Tools 中勾选 CMake,Android Studio 会使用 SDK 内置的 CMake 而非系统 PATH 中的版本,避免宿主机 CMake 版本干扰。
  • LLDB:勾选 Android SDK Platform-Tools 与 LLDB,用于 native 断点调试。
在 local.properties 中显式声明 ndk.dir 已不推荐;现代 AGP 通过 android.ndkVersion 指定版本,由 Gradle 自动解析 SDK 内的 NDK 路径,可避免团队协作时的路径差异。

工程结构与构建链路

一个可运行的 JNI 工程由 Java 层、native 源码层和 CMake 构建脚本三部分组成。Java 层通过 System.loadLibrary 加载 .so,native 层用 extern "C" 导出符合 JNI 命名约定的符号,CMake 负责把 .cpp 编译成各 ABI 的共享库并打包进 APK。

JNI 首次运行链路:Java → CMake → .so → native 返回字符串 Java / Kotlin 层 MainActivity.kt object NativeBridge { external fun hello(): String } System.loadLibrary("native-lib") CMakeLists.txt add_library(native-lib SHARED native-lib.cpp) find_library(log-lib log) target_link_libraries( native-lib ${log-lib}) native-lib.cpp extern "C" JNIEXPORT jstring JNICALL Java_..._hello(JNIEnv* env, jobject) { return env->NewStringUTF(...); 构建 编译 APK 内 lib/ 目录 arm64-v8a/libnative-lib.so x86_64/libnative-lib.so 交叉编译产物 设备运行时 loadLibrary 解析 .so 按 ABI 选择对应库 加载 验证结果 TextView 显示:Hello from JNI Logcat 无 UnsatisfiedLinkError 调用返回 LLDB 调试 在 hello() 内下断点 查看 env / jstring 值

创建工程与关键配置

在 Android Studio 中选择 Native C++ 模板新建工程,模板会自动生成 cpp/native-lib.cpp、CMakeLists.txt 与 externalNativeBuild 配置。若从空工程手动添加,需在 app/build.gradle.kts 中补齐以下内容:

android {
    namespace = "com.example.ndkfirst"
    compileSdk = 35
    ndkVersion = "27.0.12077973"   // 与 SDK Manager 中已安装版本一致

    defaultConfig {
        applicationId = "com.example.ndkfirst"
        minSdk = 24
        targetSdk = 35
        versionCode = 1
        versionName = "1.0"

        externalNativeBuild {
            cmake {
                cppFlags += "-std=c++17"
                abiFilters += listOf("arm64-v8a", "x86_64")
            }
        }
    }

    externalNativeBuild {
        cmake {
            path = file("src/main/cpp/CMakeLists.txt")
            version = "3.22.1"     // 与 SDK 内置 CMake 版本一致
        }
    }
}
abiFilters 只保留 arm64-v8a 与 x86_64 可显著缩短首次构建时间;若真机为 32 位设备,需补上 armeabi-v7a,否则运行时会抛出 UnsatisfiedLinkError。

CMakeLists.txt 与 native 实现

cmake_minimum_required(VERSION 3.22.1)
project("native-lib")

add_library(native-lib SHARED native-lib.cpp)

find_library(log-lib log)
target_link_libraries(native-lib ${log-lib})
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndkfirst_NativeBridge_hello(JNIEnv *env, jobject /* this */) {
    std::string msg = "Hello from JNI";
    return env->NewStringUTF(msg.c_str());
}

Java/Kotlin 侧声明与调用:

object NativeBridge {
    init { System.loadLibrary("native-lib") }
    external fun hello(): String
}

// Activity 中
binding.sampleText.text = NativeBridge.hello()

构建、运行与验证

  1. 点击 Sync Now,确认 Gradle 未报 NDK not configured 或 CMake version not found。
  2. 执行 Build > Make Project,在 app/build/intermediates/cxx/ 下可看到各 ABI 的 .so 产物。
  3. 连接设备或启动模拟器,点击 Run。界面应显示 Hello from JNI。
  4. 若显示异常,用 adb logcat | grep -i "UnsatisfiedLink\|JNI" 定位是库未加载还是符号未找到。
验证要点:
① 符号名必须与包名、类名、方法名严格对应,com.example.ndkfirst.NativeBridge.hello 对应 Java_com_example_ndkfirst_NativeBridge_hello;
② System.loadLibrary("native-lib") 中的名称不含 lib 前缀与 .so 后缀;
③ 首次构建会下载 CMake 与 NDK 组件,需保持网络可用。

常见失败与排查

现象可能原因处理
UnsatisfiedLinkError: dlopen failedAPK 中缺少当前设备 ABI 的 .so在 abiFilters 中补充对应 ABI 并重新构建
UnsatisfiedLinkError: No implementation foundJNI 函数名拼写或包名不匹配核对 Java_ 后的完整路径,注意下划线转义
CMake Error: could not find CMakeSDK Tools 未安装 CMake 或版本号写错在 SDK Manager 安装对应版本,或修正 version 字段
NDK not configured未安装 NDK 或 ndkVersion 与已装版本不一致安装匹配版本,或删除 ndkVersion 让 AGP 自动选择