一、JNI 是什么?为什么需要它?
JNI(Java Native Interface)是 Java 层与 Native 层(C/C++)通信的桥梁。想象一下:
- Java 层是 “高级指挥官”,擅长跨平台调度,但处理底层硬件或高性能任务时力不从心;
- Native 层是 “特种部队”,能直接操作硬件、处理高性能计算,但缺乏跨平台能力。
JNI 就是两者之间的 “翻译官”,让 Java 能调用 C/C++ 的能力,同时保持跨平台特性。
二、JNI 方法如何查找?从 Java 到 Native 的路径
当我们在 Java 代码中看到native void method();时,如何找到对应的 C++ 实现?
以 Android 消息机制中的MessageQueue.nativePollOnce()为例:
-
命名规则:
Java 方法android.os.MessageQueue.nativePollOnce
→ 对应 Native 方法名android_os_MessageQueue_nativePollOnce(点号变下划线)。 -
文件定位:
- 系统注册的 JNI 方法:在
frameworks/base/core/jni/AndroidRuntime.cpp的gRegJNI数组中,按register_包名_类名规则注册。
例如:MessageQueue对应android_os_MessageQueue.cpp。 - 动态加载的 JNI 方法:通过
System.loadLibrary("libname")加载,如MediaPlayer通过libmedia_jni.so注册,对应android_media_MediaPlayer.cpp。
- 系统注册的 JNI 方法:在
-
例外情况:
少数情况命名不规范,如Binder.java对应android_util_Binder.cpp,可通过全局搜索方法名定位。
三、JNI 注册流程:从加载到映射的全链路
以MediaPlayer的动态注册为例,看 JNI 如何建立 Java 与 Native 的映射:
-
加载动态库:
Java 调用System.loadLibrary("media_jni"),底层通过dlopen打开libmedia_jni.so,并调用其中的JNI_OnLoad函数。 -
JNI_OnLoad 初始化:
c++
jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; if (register_android_media_MediaPlayer(env) < 0) { // 注册JNI方法 goto bail; } ... } -
方法映射表:
通过JNINativeMethod结构体定义映射关系:c++
static JNINativeMethod gMethods[] = { {"prepare", "()V", (void *)android_media_MediaPlayer_prepare}, {"native_init", "()V", (void *)android_media_MediaPlayer_native_init}, ... };name:Java 层方法名;signature:方法签名(如()V表示无参数无返回值);fnPtr:Native 层函数指针。
-
注册到虚拟机:
通过JNIEnv::RegisterNatives将映射表注册到 Java 虚拟机,完成方法绑定。
四、数据类型与签名:跨语言通信的 “翻译规则”
JNI 通信时,Java 与 Native 的数据类型需按规则转换,签名相当于 “翻译词典”:
-
基本类型签名:
Java 类型 签名 Native 类型 int I jint long J jlong boolean Z jboolean -
数组与对象签名:
- 数组:前缀加
[,如int[]→[I,String[]→[Ljava/lang/String; - 对象:用
L类名;,如String→Ljava/lang/String;
- 数组:前缀加
-
方法签名示例:
Java 方法String foo(int[] arr, String str)→ 签名([ILjava/lang/String;)Ljava/lang/String;- 括号内是参数签名,括号外是返回值签名。
五、JNI 的两种注册方式:静态 VS 动态
-
静态注册(系统启动时注册) :
- 时机:Zygote 进程启动时,通过
AndroidRuntime::startReg注册gRegJNI数组中的方法(如MessageQueue)。 - 特点:提前注册,性能高,但灵活性差。
- 时机:Zygote 进程启动时,通过
-
动态注册(运行时注册) :
- 时机:通过
System.loadLibrary加载 so 时,调用JNI_OnLoad注册(如MediaPlayer)。 - 特点:按需加载,灵活,但首次调用有性能开销。
- 时机:通过
六、JNI 开发的 “坑” 与注意事项
-
内存管理:
- Java 层自动 GC,但 Native 层需手动释放
Global Reference,否则内存泄漏。 - 例:用
env->DeleteGlobalRef(obj)释放全局引用。
- Java 层自动 GC,但 Native 层需手动释放
-
异常处理:
- Native 层异常不会立即抛出,需手动检查
env->ExceptionOccurred(),否则返回 Java 层时崩溃。
- Native 层异常不会立即抛出,需手动检查
-
ART 与 Dalvik 差异:
- ART 虚拟机更严格,JNI 函数出错时倾向于抛异常而非返回 NULL,需注意兼容性。
七、JNI 在 Android 中的典型应用场景
-
性能优化:
- 图像处理(如相机滤镜)、音视频编解码(如 MediaPlayer)等高频操作,用 C++ 实现提升性能。
-
硬件交互:
- 访问传感器、蓝牙芯片等底层硬件,Java 无法直接操作,需通过 JNI 调用 Native 驱动接口。
-
安全需求:
- 敏感数据加密(如密码处理)放在 Native 层,增加逆向难度。
八、总结:JNI 的核心价值与学习路径
JNI 是 Android 系统的 “基础设施”,其核心价值在于:
-
跨语言协作:让 Java 的跨平台性与 C++ 的高性能互补;
-
系统能力扩展:允许应用调用 Android 框架的 Native 服务(如 Binder、SurfaceFlinger)。
学习 JNI 时,建议从以下方向入手:
-
掌握基本类型与签名规则,能看懂
JNINativeMethod映射表; -
理解注册流程,能通过命名规则定位 Native 方法;
-
实践动态注册案例(如 NDK 开发),积累异常处理和内存管理经验。
通过 JNI,开发者能深入理解 Android 系统的底层运行机制,为性能优化和系统级开发打下基础