NDK(五):JNI静态注册与动态注册

一、概述

我们知道,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参数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值