一、概述
我们知道,JNI是Java层与Native层的桥梁,可以通过 JNI层实现Java层与Native层的互调。那么JNI是如何将 Java方法与 Native方法关联起来的呢?
JVM查找Native方法有两种方式:
- 静态注册:按照JNI规范的命名规则进行查找。
- 动态注册:调用JNIEnv提供的RegisterNatives函数,将本地函数注册到JVM中。
关联文章:
二、静态注册
2.1 静态注册的原理
所谓静态注册就是按照JNI规范书写函数名。
Java_类路径_方法名
:包名中的点使用下划线代替,如果路径中存在下划线"_“,则使用”_1"来代替。
我们以 NDK(一):NDK 的集成 工程实践部分的代码为例:
package com.elson.jnitestdemo;
public class MainActivity extends AppCompatActivity {
static {
// 1.加载动态链接库
System.loadLibrary("jnitestdemo");
}
// 2.定义一个Native函数。
public native String stringFromJNI();
}
对应的 JNI 层的函数名为:Java_com_elson_jnitestdemo_MainActivity_stringFromJNI
对应的JNI层代码如下所示:
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_elson_jnitestdemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
小结:
静态注册的方式是系统的默认方式,使用简单,但是灵活性比较差,如果修改了Java中的Native函数所在类的包名或类名,则需要同时修改C/C++函数名称 (头文件、源文件等)。
2.2 静态注册在Android中的应用
下面我们以 Framework 层的 MessageQueue.java 为例来说明:
Java层代码:MessageQueue.class
// MessageQueue.class
public final class MessageQueue {
private native static long nativeInit();
private native static void nativeWake(long ptr);
}
JNI 层代码:/frameworks/base/core/jni/android_os_MessageQueue.cpp
// frameworks/base/core/jni/android_os_MessageQueue.cpp
static const JNINativeMethod gMessageQueueMethods[] = {
// 第1个参数,对应java层的方法名。
// 第2个参数,对应java层的方法签名(参数和返回值类型)。
// 第3个参数,指向native层对应方法的指针。
{ "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },
{ "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
// 省略部分注册代码。
};
int register_android_os_MessageQueue(JNIEnv* env) {
int res = RegisterMethodsOrDie(env, "android/os/MessageQueue",
gMessageQueueMethods, NELEM(gMessageQueueMethods));
jclass clazz = FindClassOrDie(env, "android/os/MessageQueue");
gMessageQueueClassInfo.mPtr = GetFieldIDOrDie(env, clazz, "mPtr", "J");
gMessageQueueClassInfo.dispatchEvents = GetMethodIDOrDie(env, clazz,
"dispatchEvents", "(II)I");
return res;
}
// 对应java层的MessageQueue.nativeInit()方法。因为nativeInit是静态方法,所以native方法对于的第2个参数是jclass类型。
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);
}
// 对应java层的MessageQueue.nativeWake()方法。
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}
上面已经设置好了Java层与JNI层方法的映射关系,下面我们来看下它注册的地方。
通过源码查看网站的全局搜索功能直接搜,可以定位到 register_android_os_MessageQueue()
方法在 AndroidRuntime.cpp
中被调用。
frameworks/base/core/jni/AndroidRuntime.cpp
// frameworks/base/core/jni/AndroidRuntime.cpp
static const RegJNIRec gRegJNI[] = {
REG_JNI(register_com_android_internal_os_RuntimeInit),
REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),
// 省略
// 此处就是注册我们MessageQueue映射关系的地方。
REG_JNI(register_android_os_MessageQueue),
// 省略
REG_JNI(register_android_app_Activity),
REG_JNI(register_android_app_ActivityThread),
}
// frameworks/base/core/jni/AndroidRuntime.cpp
// Register android native functions with the VM.
int AndroidRuntime::startReg(JNIEnv* env)
{
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
env->PushLocalFrame(200);
// 实际使用gRegJNI数组的地方
if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {
env->PopLocalFrame(NULL);
return -1;
}
env->PopLocalFrame(NULL);
return 0;
}
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{
static const String8 startSystemServer("start-system-server");
/* start the virtual machine */
JniInvocation jni_invocation;
jni_invocation.Init(NULL);
JNIEnv* env;
// 虚拟机的启动
if (startVm(&mJavaVM, &env, zygote) != 0) {
return;
}
// 创建虚拟机
onVmCreated(env);
// 这里执行JNI的注册
if (startReg(env) < 0) {
return;
}
// 通过字符串拼接启动指定文件内的main方法。
// 略
}
小结:
- 在 AndroidRuntime 启动的时候,就把全局的 JNI 映射关系表注册到进程中,这里属于静态注册。
三、动态注册
3.1 动态注册原理
在库加载时会自动调用 JNI_OnLoad()
函数,开发者需要再 JNI_OnLoad()
函数中做一些初始化操作,动态注册就是在这里进行的。我们可以在 JNI_OnLoad()
方法中调用 JNIEnv->RegisterNatives(clazz, gMethods, numMethods)
进行动态注册。
JNINativeMethod 结构体:
关于结构体的说明,请参考文章: NDK(二):JNI 的数据结构
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
JNIEnv->RegisterNatives(clazz, gMethods, numMethods) 方法参数说明:
- 第一个参数是Java对应的类
- 第二个参数是JNINativeMethod数组
- 第三个参数是JNINativeMethod数组的长度,也就是需要注册的方法的个数。
- 其中 JNINativeMethod 表示的是native方法与Java方法的映射关系,它包括Java中的方法名,对应的方法签名和Native映射的函数方法三个信息。
// 前两个参数还是固定的
jstring stringFromJNI(JNIEnv *jniEnv,jobject jobj){
return jniEnv->NewStringUTF("hello from C++ string");
}
static const JNINativeMethod nativeMethods[] = {
{"stringFromJNI", "()Ljava/lang/String;", (void *) (stringFromJNI)},
};
// 类库加载时自动调用
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reversed)
{
JNIEnv *env = NULL;
// 初始化JNIEnv
if(vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK){
return JNI_FALSE;
}
// 找到需要动态动态注册的Jni类
jclass jniClass = env->FindClass("com/elson/jnitestdemo/MainActivity");
if(nullptr == jniClass){
return JNI_FALSE;
}
// 动态注册
env->RegisterNatives(jniClass, nativeMethods, sizeof(nativeMethods)/sizeof(JNINativeMethod));
// 返回JNI使用的版本
return JNI_VERSION_1_6;
}
小结:
相比静态注册,动态注册的灵活性更高。如果修改了Java 中 native 函数所在类的包名或类名,仅调整Java 中 native函数的签名信息即可。
3.2 动态注册在Android中的应用
System.loadLibrary("libName")
方法内部的JNI层调用使用的是动态注册。
Java层: java.lang.Runtime
// Runtime.class
private static native String nativeLoad(String filename, ClassLoader loader, Class<?> caller);
JNI层:/libcore/ojluni/src/main/native/Runtime.c
//jniRegisterNativeMethods是从jni_util.h文件头引入的。
#include "jni_util.h"
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
jobject javaLoader)
{
return JVM_NativeLoad(env, javaFilename, javaLoader);
}
static JNINativeMethod gMethods[] = {
// ...略...
NATIVE_METHOD(Runtime, nativeExit, "(I)V"),
// Runtime.nativeLoad()方法
NATIVE_METHOD(Runtime, nativeLoad,
"(Ljava/lang/String;Ljava/lang/ClassLoader;)"
"Ljava/lang/String;"),
};
void register_java_lang_Runtime(JNIEnv* env) {
// jniRegisterNativeMethods是从jni_util.h文件头引入的。
jniRegisterNativeMethods(env, "java/lang/Runtime", gMethods, NELEM(gMethods));
}
register_java_lang_Runtime()
函数在Register.JNI_OnLoad()
函数中调用。NATIVE_METHOD
对象在 JniConstants.h 中定义。jniRegisterNativeMethods()
是从jni_util.h
文件头引入的。
/libcore/ojluni/src/main/native/Register.cpp
jint JNI_OnLoad(JavaVM* vm, void*) { JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
ALOGE("JavaVM::GetEnv() failed");
abort();
}
// ...略
register_java_lang_ProcessEnvironment(env);
// 在JNI_OnLoad中注册Runtime。
register_java_lang_Runtime(env);
}
/libnativehelper/include/nativehelper/JniConstants.h
#define NATIVE_METHOD(className, functionName, signature) \
{ #functionName, signature, reinterpret_cast<void*>(className ## _ ## functionName) }
#endif // JNI_CONSTANTS_H_included
JniConstants.h 的 NATIVE_METHOD 内部将 className 与 functionName 使用下划线进行拼接,即将 Runtime与nativeLoad 拼接成 Runtime_nativeLoad 方法名。
/external/conscrypt/common/src/jni/main/cpp/conscrypt/jniutil.cc
void jniRegisterNativeMethods(JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods) {
ScopedLocalRef<jclass> c(env, env->FindClass(className));
if (c.get() == nullptr) {
char* msg;
(void)asprintf(&msg, "Native registration unable to find class '%s'; aborting...", className);
env->FatalError(msg);
}
// 这里调用的 JNIEnv.RegisterNatives()方法,传入gMethods参数。
if (env->RegisterNatives(c.get(), gMethods, numMethods) < 0) {
char* msg;
(void)asprintf(&msg, "RegisterNatives failed for '%s'; aborting...", className);
env->FatalError(msg);
}
}
在 jniRegisterNativeMethods()
函数中调用 JNIEnv.RegisterNatives()
函数,传入gMethods参数。