Android JNI 开发完全指南:从入门到精通

avatar
莫雨IP属地:上海
02026-01-27:01:36:11字数 4615阅读 2

Android JNI 开发完全指南:从入门到精通

在 Android 开发中,有时我们不得不跳出 Java/Kotlin 的舒适区,深入到底层 C/C++ 世界。无论是为了极致性能、复用已有 C/C++ 库,还是调用系统级 API,JNI(Java Native Interface) 都是我们绕不开的桥梁。

本文将带你系统掌握 Android JNI 开发的核心知识,涵盖环境搭建、函数注册、数据类型转换、异常处理、线程安全等关键内容,助你从零构建稳定高效的 JNI 模块。


一、什么是 JNI?

JNI 是 Java 提供的一套标准接口,允许 Java 代码与本地(Native)语言(如 C/C++)进行交互。在 Android 中,它常用于:

  • 调用高性能计算逻辑(如图像处理、音视频编解码)
  • 复用已有的 C/C++ 库(如 OpenSSL、FFmpeg)
  • 访问操作系统底层功能
  • 实现反逆向保护(混淆关键逻辑)

⚠️ 注意:JNI 并非“银弹”。过度使用会增加复杂性、降低可维护性,并可能引入内存泄漏或崩溃风险。仅在必要时使用!


二、开发环境准备

1. 必备工具

  • Android Studio(建议最新稳定版)
  • NDK(Native Development Kit):通过 SDK Manager 安装
  • CMake 或 ndk-build(推荐 CMake,官方主推)

2. 创建支持 JNI 的项目

build.gradle(Module 级别)中启用 NDK 支持:

android {
    defaultConfig {
        ndk {
            abiFilters 'arm64-v8a', 'armeabi-v7a' // 指定目标 ABI
        }
    }
    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.18.1'
        }
    }
}

src/main/cpp/ 目录下创建 native-lib.cppCMakeLists.txt


三、第一个 JNI 程序

1. Java 层声明 native 方法

public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib"); // 加载 so 库
    }

    public native String getStringFromJNI();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(getStringFromJNI());
    }
}

2. C++ 实现

#include <jni.h>
#include <string>

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

🔑 函数命名规则:Java_包名_类名_方法名
使用 extern "C" 防止 C++ 名称修饰(name mangling)

3. CMakeLists.txt 配置

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

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

find_library(log-lib log)
target_link_libraries(native-lib ${log-lib})

编译后,APK 将包含对应 ABI 的 .so 文件。


四、JNI 函数注册方式

方式一:静态注册(默认)

即上述的命名约定方式。优点是简单,缺点是函数名冗长,且每次 Java 方法变动需重写 C++ 函数名。

方式二:动态注册(推荐用于大型项目)

JNI_OnLoad 中手动注册:

static JNINativeMethod methods[] = {
    {"getStringFromJNI", "()Ljava/lang/String;", (void*)getStringFromJNI},
    // 更多方法...
};

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env;
    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }

    jclass clazz = env->FindClass("com/example/myapp/MainActivity");
    env->RegisterNatives(clazz, methods, sizeof(methods)/sizeof(methods[0]));

    return JNI_VERSION_1_6;
}

优势:

  • 函数名自由命名
  • 启动时一次性注册,性能略优
  • 更易管理大量 native 方法

五、数据类型映射与转换

Java 类型JNI 类型C/C++ 类型
booleanjbooleanunsigned char
bytejbytesigned char
charjcharunsigned short
shortjshortshort
intjintint
longjlonglong long
floatjfloatfloat
doublejdoubledouble
Objectjobjectvoid*

常见操作示例

字符串处理

// Java → C++
jstring jstr = ...;
const char* cstr = env->GetStringUTFChars(jstr, nullptr);
// 使用 cstr...
env->ReleaseStringUTFChars(jstr, cstr); // 必须释放!

// C++ → Java
const char* msg = "Hello";
jstring result = env->NewStringUTF(msg);

数组操作

jintArray arr = ...;
jsize len = env->GetArrayLength(arr);
jint* data = env->GetIntArrayElements(arr, nullptr);
// 修改 data...
env->ReleaseIntArrayElements(arr, data, 0); // 0 表示提交更改

🚨 警告:所有 GetXXX 操作必须配对 ReleaseXXX,否则会导致内存泄漏或 JVM 崩溃!


六、异常处理

JNI 中的异常不会自动抛给 Java 层,需手动检查:

jclass cls = env->FindClass("NonExistentClass");
if (cls == nullptr) {
    // 发生异常,清除并返回
    env->ExceptionClear();
    return -1;
}

常用方法:

  • ExceptionCheck():检查是否有 pending 异常
  • ExceptionOccurred():获取异常对象
  • ExceptionClear():清除异常(否则后续 JNI 调用会失败)

七、线程与 JNIEnv

  • JNIEnv 不是线程安全的! 每个线程有独立的 JNIEnv。
  • 在非 Java 创建的线程中调用 JNI,需先附加到 VM:
JavaVM* g_jvm; // 全局保存,在 JNI_OnLoad 中获取

// 在子线程中
JNIEnv* env;
g_jvm->AttachCurrentThread(&env, nullptr);
// 使用 env...
g_jvm->DetachCurrentThread();

八、最佳实践与避坑指南

  1. 避免频繁跨语言调用:JNI 调用有开销,尽量批量处理数据。
  2. 及时释放引用:局部引用在函数返回后自动释放,但循环中需手动 DeleteLocalRef
  3. 慎用全局引用:使用 NewGlobalRef 后务必 DeleteGlobalRef
  4. ABI 兼容性:明确支持的 CPU 架构,避免“so not found”错误。
  5. 调试技巧:使用 __android_log_print 打印日志(需链接 log 库)。

九、结语

JNI 是 Android 开发中的“双刃剑”——用得好,性能飞跃;用不好,bug 难测。掌握其核心机制、遵循规范、重视内存管理,才能安全高效地发挥其威力。

📚 推荐延伸阅读:

  • Android NDK 官方文档
  • 《Android 软件安全权威指南》—— JNI 安全章节
  • JNI Specification(Oracle 官方)

如果你正在开发音视频、游戏引擎、加密模块等高性能组件,JNI 将是你不可或缺的利器。现在,就去动手试试吧!


欢迎关注本公众号,回复“JNI 示例”获取完整 Demo 工程源码!
如有疑问,欢迎留言讨论 👇

总资产 0
暂无其他文章

热门文章

暂无热门文章