前言
前面我们都是通过编译器生成的 JNI 函数,属于静态注册,静态注册比动态注册简单,但是在 Android 系统源码中会发现大量使用动态注册,比如你打开 /frameworks/base/core/jni/android_os_Parcel.cpp 的源码,会发现里面都是使用的动态注册,动态注册不需要暴露包名,安全性更高。
原理
在使用 System.loadLibrary(String libname) 加载 so 库时,会调用 JNI_OnLoad(JavaVM* vm, void* reserved) 函数,我们可以通过重写该函数来实现动态注册。重写后该函数的左边会出现
标志,
点击该标志可以跳转到 jni.h 中对应的函数位置。
动态注册需要使用 JNIEnv* 中的 RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods) 函数,其中 JNIEnv* 可以通过 JavaVM* 获取到,参数 JNINativeMethod 是一个结构体,其代码如下:
typedef struct {
const char* name; // Java 函数名
const char* signature; // 签名
void* fnPtr; // 函数指针
} JNINativeMethod;
该结构体由 3 部分组成:Java 函数名、签名和函数指针,因此我们只需要拿到 JNIEnv*、 Java 函数名、签名和函数指针,并在 JNI_OnLoad() 函数中调用 RegisterNatives() 函数即可实现动态注册。
具体实现
下面我们用代码来实现动态注册,MainActivity 代码如下:
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("jnitest");
}
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
dynamicRegTest1();
dynamicRegTest2("Raymond");
}
private native void dynamicRegTest1();
private native int dynamicRegTest2(String name);
}
MainActivity 中有两个 natvie 函数,可以看到这里跟静态注册的方式没区别,C++ 的代码就不一样了,native-lib.cpp 中的代码如下:
#include <jni.h>
#include <string>
// 日志输出
#include <android/log.h>
#define TAG "jni"
// __VA_ARGS__ 代表可变参数
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
// 该函数的两个参数没有用到可以不写
void dynamicMethod1(JNIEnv* env, jobject thiz){
LOGD("Dynamic register method")
}
int dynamicMethod2(JNIEnv* env, jobject thiz, jstring str){
const char* msg = env->GetStringUTFChars(str, nullptr);
LOGD("His name is %s", msg)
env->ReleaseStringUTFChars(str, msg);
return 0;
}
JNINativeMethod jniNativeMethods[] = {
{"dynamicRegTest1", "()V", (void *)(dynamicMethod1)},
{"dynamicRegTest2", "(Ljava/lang/String;)I", (void *)(dynamicMethod2)}
};
jint JNI_OnLoad(JavaVM* vm, void* args){
JNIEnv* env = nullptr;
// 给 env 赋值
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
jclass jclazz = env->FindClass("com/example/jnitest/MainActivity");
if (jclazz == nullptr) {
LOGD("MainActivity class not found!");
return JNI_ERR;
}
// 注册 Native 函数
if (env->RegisterNatives(jclazz, jniNativeMethods, sizeof(jniNativeMethods)/sizeof(JNINativeMethod)) < 0) {
LOGD("Failed to register natives");
return JNI_ERR;
}
// 返回最新的 JNI 版本号
return JNI_VERSION_1_6;
}
这里首先要把 JNINativeMethod 准备好,然后在 JNI_OnLoad() 中是通过 vm->GetEnv() 给 env 赋值,通过 env 调用 RegisterNatives() 函数,该函数代码如下:
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
{ return functions->RegisterNatives(this, clazz, methods, nMethods); }
需要传入 3 个参数,依次是:Java 函数所属的类、JNINativeMethod 和 函数个数。
运行后打印如下:
Dynamic register method
His name is Raymond
可以看到成功地通过动态注册实现了 Java 函数对 Native 函数的调用。