JNI技术之动态注册与JNI线程实战

什么是静态注册

通过固定的函数命名规则,让JVM自动找到对应的native函数,无需手动注册。

命名规则:

Java_包名_类名_方法名

  • 包名中的 . 要替换成 _
  • 如果方法重载,还需加上参数签名

java代码

java 复制代码
package com.example.myapp;
public class MainActivity {
    public native void sayHello();
}

c++代码(native-lib.cpp)

cpp 复制代码
#include <jni.h>

// 静态注册:函数名必须严格匹配
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapp_MainActivity_sayHello(JNIEnv *env, jobject thiz) {
    // 实现
}

优点:

简单直观,新手友好

不需要写额外的注册代码

缺点:

函数名冗长且易错(包名/类名改了就要改函数名)

不支持混淆(ProGuard/R8 混淆后包名/类名变了,native 函数找不到)

启动时通过反射查找函数,性能略低(首次调用慢)

什么是动态注册

在 JNI_OnLoad 中手动调用 RegisterNatives,将 Java 方法与 C 函数指针绑定。

步骤:

定义普通的 C 函数(名字随意)

在 JNI_OnLoad 中调用 env->RegisterNatives(...)

动态注册实操

java代码

java 复制代码
public native void sayHello();

c++代码

cpp 复制代码
#include <jni.h>

// 1. 普通函数,名字自由
void mySayHello(JNIEnv *env, jobject thiz) {
    // 实现
}

// 2. 方法注册表
static JNINativeMethod methods[] = {
    {"sayHello", "()V", (void *)mySayHello},
    // {"methodName", "signature", functionPointer}
};

// 3. 在 JNI_OnLoad 中注册
extern "C"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = nullptr;
    if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }

    // 找到 Java 类
    jclass clazz = env->FindClass("com/example/myapp/MainActivity");
    if (!clazz) return JNI_ERR;

    // 注册 native 方法
    if (env->RegisterNatives(clazz, methods, sizeof(methods)/sizeof(methods[0])) < 0) {
        return JNI_ERR;
    }

    return JNI_VERSION_1_6;
}

优点:

函数名自由,不依赖包名/类名

支持代码混淆(只要 FindClass 的字符串也做映射或使用其他方式)

启动更快:避免运行时查找,直接建立映射表

可批量注册多个方法

缺点:

代码稍复杂

需要维护方法签名(如 "()V"

JNI线程实战

通过在MainActivity点击事件调用c++层native方法开启子线程,在子线程处理好后,调用Java层更新UI

cpp 复制代码
JavaVM *jvm = nullptr;

extern "C"
JNIEXPORT jint JNI_OnLoad(JavaVM *javaVm,void *){
::jvm = javaVm;
return JNI_VERSION_1_6;
};

class MyContext{
public:
    JNIEnv *jniEnv = nullptr;// 不能跨线程,会崩溃
    jobject instance = nullptr; //不能跨线程,会崩溃
};

//函数的返回类型是 void*,参数类型也是 void*
//void *能够接收传入 任何非函数指针的地址
void * myThreadTaskAction(void * pVoid){
    LOGE("myThreadTaskAction run");

    //需求:有这样的场景,例如:下载完成 ,下载失败,等等,必须告诉Activity UI端,所以需要在子线程调用UI端
    //需要
    //JNIEnv *env
    //jobject thiz
    //将线程传递的pVoid转回来MyContext
    MyContext * myContext = static_cast<MyContext *>(pVoid);
    //通过jvm附加到当前异步线程后,会得到一个全新的env
    /**
     * 将当前正在执行此函数的线程(current thread)附加(attach)到 Java 虚拟机。
       成功后,JVM 会为这个线程创建一个 JNIEnv 上下文,并通过 p_env 返回。
     */
     JNIEnv * jniEnv = nullptr;
     jint  attachResult = ::jvm->AttachCurrentThread(&jniEnv, nullptr);
     if(attachResult!=JNI_OK){
         return 0;//附加失败,返回
     }
     //1.拿到class
     jclass mainActivityClass = jniEnv->GetObjectClass(myContext->instance);
     //2.拿到方法
     jmethodID  updateActivityUI = jniEnv->GetMethodID(mainActivityClass,"updateActivityUI","()V");
     //3.调用
     jniEnv->CallVoidMethod(myContext->instance,updateActivityUI);

     ::jvm->DetachCurrentThread();//必须解除附加,否则报错
     LOGE("C++异步线程OK");

    return nullptr;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_as_1jni_1project_MainActivity_nativeThread(JNIEnv *env, jobject job) {

    pthread_t  pid;
    /**
     * (*__start_routine) 表示 "__start_routine 是一个指针"
整体 (*__start_routine)(void*) 表示 "这个指针指向一个接受 void* 的函数"
返回类型是 void*
     */


    MyContext * myContext = new MyContext;
//    myContext->jniEnv = env;
    myContext->instance = env->NewGlobalRef(job);//提升全局引用
    pthread_create(&pid, nullptr,myThreadTaskAction, myContext);
    pthread_join(pid, nullptr);
}
java 复制代码
    /**
     * TODO 下面是 被native代码调用的 Java方法
     * 第二部分 JNI线程
     */
    public void updateActivityUI() {
        if (Looper.getMainLooper() == Looper.myLooper()) { // TODO C++ 用主线程调用到此函数 ---->  主线程
            new AlertDialog.Builder(MainActivity.this)
                    .setTitle("UI")
                    .setMessage("updateActivityUI Activity UI ...")
                    .setPositiveButton("老夫知道了", null)
                    .show();
        } else {  // TODO  C++ 用异步线程调用到此函数 ---->  异步线程
            Log.d(TAG, "updateActivityUI 所属于子线程,只能打印日志了..");

            runOnUiThread(new Runnable() { // 哪怕是异步线程  UI操作 正常下去 runOnUiThread
                @Override
                public void run() {

                    // 可以在子线程里面 操作UI
                    new AlertDialog.Builder(MainActivity.this)
                            .setTitle("updateActivityUI")
                            .setMessage("所属于子线程,只能打印日志了..")
                            .setPositiveButton("老夫知道了", null)
                            .show();
                }
            });
        }
    }
  1. JavaVM全局,绑定当前进程, 只有一个地址
  2. JNIEnv线程绑定, 绑定主线程,绑定子线程
  3. jobject 谁调用JNI函数,谁的实例会给jobject
  • JNIEnv *env 不能跨越线程,否则奔溃, 他可以跨越函数 【解决方式:使用全局的JavaVM附加当前异步线程 得到权限env操作】
  • jobject thiz 不能跨越线程,否则奔溃,不能跨越函数,否则奔溃 【解决方式:默认是局部引用,提升全局引用,可解决此问题】
  • JavaVM 能够跨越线程,能够跨越函数
相关推荐
庄小法2 小时前
pytest
开发语言·python·pytest
sonnet-10292 小时前
堆排序算法
java·c语言·开发语言·数据结构·python·算法·排序算法
csdn_zhangchunfeng2 小时前
Qt之智能指针使用建议
开发语言·qt
2401_895521342 小时前
Golang 构建学习
开发语言·学习·golang
墨香幽梦客2 小时前
大数据环境下的BI架构:Hadoop与Spark的企业级应用整理
java·开发语言
2301_810160952 小时前
C++中的状态模式
开发语言·c++·算法
码路星河2 小时前
SpringBoot3实战:优雅实现Word文档动态生成与下载
开发语言·c#·word
AIminminHu2 小时前
OpenGL渲染与几何内核那点事-项目实践理论补充(一-1-(2):看似“老派”的 C++ 底层优化,恰恰是这些前沿领域最需要的基础设施)
开发语言·c++
Eiceblue2 小时前
通过 C# 读取 Word 表格数据:高效解析 + 导出为 CSV/TXT
开发语言·c#·word