Android Framework 源码阅读:JNI 调用链
本文以旧版 AOSP 的 JNI 实现为例,跟踪一次 Java
native方法如何进入 C/C++,以及结果如何返回 Java。类名和构建命令会随 Android、JDK、NDK 版本变化,文中的javah只代表历史流程。
摘要
JNI 解决的是语言边界,不是进程间通信。Java 代码声明 native 方法,运行时根据静态命名约定或 RegisterNatives() 找到 C/C++ 函数,再通过线程相关的 JNIEnv 完成参数转换、对象访问和异常传递。
1. 一次 JNI 调用的四个阶段
Java 方法声明 native
→ 虚拟机查找 native 符号
→ C/C++ 函数通过 JNIEnv 访问 Java 对象
→ 返回值转换为 Java 类型
JNI 函数运行在发起调用的 Java 线程上。JNIEnv* 是线程相关的接口指针,不能从一个线程直接拿到另一个线程使用;native 创建的线程需要先 AttachCurrentThread(),退出前再 detach。
2. 静态注册:按命名约定查找
Java 端只需要声明方法:
public final class NativeHelper {
static {
System.loadLibrary("native-lib");
}
public static native String getAppKey();
}
旧版流程常用 javah 生成头文件。头文件是构建辅助产物,不是 JNI 必需条件;现代 JDK 通常使用 javac -h 或由 Gradle/NDK 构建系统生成声明。
静态查找的核心是 JNI 命名约定:类名、方法名和签名会编码进 native 符号。运行时加载 so 后,根据这个规则解析函数地址。
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_NativeHelper_getAppKey(JNIEnv* env, jclass clazz) {
return env->NewStringUTF("demo-key");
}
3. 动态注册:JNI_OnLoad 与 RegisterNatives
动态注册把 Java 方法和 C/C++ 函数显式写入 JNINativeMethod[],在 JNI_OnLoad() 中调用 RegisterNatives():
static JNINativeMethod kMethods[] = {
{"getAppKey", "()Ljava/lang/String;", (void*)GetAppKey},
};
jint JNI_OnLoad(JavaVM* vm, void*) {
JNIEnv* env = nullptr;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
jclass clazz = env->FindClass("com/example/NativeHelper");
if (env->RegisterNatives(clazz, kMethods, 1) != 0) return JNI_ERR;
return JNI_VERSION_1_6;
}
动态注册不要求每个 Java 类生成一个 .h 文件,但每个目标类仍需要在运行时完成注册。多个类可以共用同一个 native 实现文件。
4. 类型转换与引用生命周期
常见转换包括:
jstring与 UTF-8/UTF-16 字符串之间的转换;jobjectArray、jbyteArray与 native 缓冲区之间的转换;jobject、jclass、jmethodID的查找和调用。
局部引用通常在 JNI 调用返回时自动释放;需要跨越当前调用保存的对象必须创建全局引用,并在不再使用时 DeleteGlobalRef()。调用 Java 方法后要检查 ExceptionCheck(),否则异常可能延迟到下一次 JNI 边界才暴露。
5. native 线程与 JNIEnv
void Worker(JavaVM* vm) {
JNIEnv* env = nullptr;
vm->AttachCurrentThread(&env, nullptr);
// 只在当前线程使用 env
vm->DetachCurrentThread();
}
JavaVM* 可以在进程内共享,JNIEnv* 不能跨线程共享。这是排查 native 崩溃和“偶现”引用错误时最重要的边界。
6. JNI 与 Binder 的关系
JNI 负责 Java/native 之间的调用和类型转换;Binder 负责进程之间的事务传输。Framework 服务中经常先经过 JNI 进入 native,再由 BpBinder 发起跨进程调用,但两者不是同一层:
Java API → JNI 边界 → native Binder → Binder 驱动 → 远端服务
如果调用没有跨进程,JNI 调用结束后就直接返回;只有拿到远端 Binder 代理并执行 transact() 时,才进入 IPC 链路。
7. 源码核对顺序
- 记录 AOSP 分支、JDK、NDK 版本;
- 从 Java
native声明或System.loadLibrary()进入; - 核对静态符号或
RegisterNatives()的映射; - 检查
JNIEnv、引用生命周期和异常处理; - 最后确认是否继续进入 Binder 等其他子系统。
本文结论:JNI 是一条受线程和引用规则约束的 Java/native 调用边界;静态注册依赖命名约定,动态注册依赖 JNI_OnLoad() 的显式映射。