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 能够跨越线程,能够跨越函数
相关推荐
lbb 小魔仙38 分钟前
基于Python构建RAG(检索增强生成)系统:从原理到企业级实战
开发语言·python
代码的小搬运工1 小时前
UITableView
开发语言·ui·ios·objective-c
刚子编程1 小时前
C# Join 深度解析:参数顺序、多表关联与空值处理最佳实践
开发语言·c#·最佳实践·join·多表关联·空值处理
AbandonForce1 小时前
哈希表(HashTable,散列表)个人理解
开发语言·数据结构·c++·散列表
代码中介商1 小时前
栈结构完全指南:顺序栈实现精讲
c语言·开发语言·数据结构
平凡但不平庸的码农1 小时前
Go 错误处理详解
开发语言·后端·golang
z200509301 小时前
C++中位图和布隆过滤器的一些面试题
开发语言·c++
Bat U2 小时前
JavaEE|文件操作和IO
java·开发语言
脉动数据行情2 小时前
Python 实现融通金行情数据对接(实时推送 + K 线 + 产品列表)
开发语言·python
skywalk81632 小时前
Trae生成的中文编程语言关键字(如“定“、“函“、“印“等)需要和标识符之间用 空格 隔开,以确保正确识别
服务器·开发语言·编程