Android底层开发之动态注册和附加线程

学习Android NDK开发,了解JNI层的反射之后,就要开始逼格更高的知识学习了。今天我们讲两个东西,方法的动态注册和附加Native线程到JVM。

方法的静态注册与动态注册

静态注册

什么是方法的静态注册?即我们硬编码,按照一定的规则,映射Java本地方法和Native函数,使其自动关联绑定的过程。静态注册是不需要我们写额外的代码的,它的映射规则很简单,即Java_包名(.换_,_换_1),然后参数和返回值跟JNI类型对应即可,注意第一个函数参数一定是JNIEnv *env,第二个函数参数一定是jobject thiz,如果是静态方法,则可以替换为jclass clazz,jobject是兼容jclass的。

动态注册

说起动态注册,咱们不得不聊聊JNI_OnLoad函数。

cpp 复制代码
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    return JNI_VERSION_1_6;
}

每一个动态库so文件中只能定义一个名为JNI_OnLoad的函数,就跟定义main函数一样。在Java代码中调用System.loadLibary("库名称");的时候,就会调用JNI_OnLoad函数。在这里我们可以做一些库的初始化操作。通常我们在这里就可以拿到JNIEnv的指针,而无需通过硬编码native函数传入。

cpp 复制代码
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    // 获取JNIEnv
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    // 在此做你的事情
    return JNI_VERSION_1_6;
}

在这里我们就可以去反射Java代码了。那么重点来了,如何进行方法的动态注册?

cpp 复制代码
JNIEXPORT jstring JNICALL
yourMethod(JNIEnv *env, jobject thiz, jstring jstr) {
    return (jstr == nullptr) ? nullptr : (jstring) env->NewLocalRef(jstr);
}

// 构建 JNINativeMethod 的数组
static JNINativeMethod methods[] = {
        {"yourMethod", "(Ljava/lang/String;)Ljava/lang/String;", (void *) yourMethod},
};

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = NULL;
    // 获取JNIEnv
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    // 获取需要注册的方法所在的Java类
    jclass clazz = env->FindClass("com/example/MyClass");
    if (clazz == NULL) {
        return -1;
    }
    // 进行方法的动态注册
    if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
        // 注册失败
        return -1;
    }
    return JNI_VERSION_1_6;
}

我们总结下使用动态注册的好处和什么情况下需要使用方法的动态注册。

  • 需要提高安全性,防止被 Java 反射直接调用

  • Java 类名可能会变,不希望 C/C++ 代码跟着改

  • 需要在不同环境下运行时修改或替换 JNI 方法

附加线程与分离线程

我们可以通过调用javaVM->AttachCurrentThread(&jniEnv, nullptr);将当前线程附加到JVM。由于JNIEnv指针不能跨线程使用,需要先附加线程到JVM,才能调用JNI方法。例如:

cpp 复制代码
JavaVM *g_JavaVM;

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    g_JavaVM = vm;  // 保存 JavaVM 实例
    return JNI_VERSION_1_6;
}

void *thread_function(void *arg) {
    JNIEnv *env;
    // 附加当前线程到 JVM
    if (g_JavaVM->AttachCurrentThread(&env, nullptr) != JNI_OK) {
        return nullptr;
    }
    // 现在可以安全调用 Java 方法
    jclass clazz = env->FindClass("com/example/MyClass");
    jmethodID method = env->GetStaticMethodID(clazz, "callback", "()V");
    env->CallStaticVoidMethod(clazz, method);
    // 线程执行完成后,分离当前线程
    g_JavaVM->DetachCurrentThread();
    return nullptr;
}

最后我们需要使用POSIX创建一个可移植操作系统的线程,使用的是pthread_t。

cpp 复制代码
pthread_t thread;
pthread_create(&thread, nullptr, thread_function, nullptr);

总结

那么,本篇的内容就讲完了,主要讲解了JNI入口函数以及如何进行方法的动态注册,最后讲解了JNIEnv不能跨线程使用,所以需要附加线程到JVM。本篇是NDK底层开发的基础,非常重要,JYM务必重视起来,慢慢消化掌握。

相关推荐
海水冷却7 小时前
Android 群组聊天怎么实现:基于 ZIM 的完整落地指南(附可运行 Demo)
android
三少爷的鞋10 小时前
现代 Android 架构指南:不要再主动设计 Callback 接口
android
TimeFine15 小时前
AI 编码工作流的设计
android·ai编程
骑着蜗牛撵大象32716 小时前
C++20 协程 × 屏障:手写一个 co_await 版 Barrier,让等待不再钉死线程
android·c++20·协程·并发编程·co_await·屏障同步
山东孙燕姿18 小时前
设备台账重构:从一张表到三层模型
android·java·网络·低代码·重构·rxjava
蒸鱼Yuzheng19 小时前
移动端 CPU 调度分析:线程亲和性、大小核迁移与卡顿
android·perfetto·性能分析·线程优化·cpu调度
booboosui19 小时前
安卓唤醒词:12 维 MFCC 加 DTW,阈值 1.8
android
传奇开心果19 小时前
Compose Multiplatform 共享边界实战:架构契约、互操作同步与可回退设计
android·kotlin
事圆则缓19 小时前
从开机到首帧:Android 系统与应用启动流程源码分析
android
AI吃大瓜20 小时前
指针压力表读数4:Android实现指针压力表读数(可实时检测)
android·指针读数·指针压力表·压力表·压力表读数