什么是静态注册
通过固定的函数命名规则,让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();
}
});
}
}
- JavaVM全局,绑定当前进程, 只有一个地址
- JNIEnv线程绑定, 绑定主线程,绑定子线程
- jobject 谁调用JNI函数,谁的实例会给jobject
- JNIEnv *env 不能跨越线程,否则奔溃, 他可以跨越函数 【解决方式:使用全局的JavaVM附加当前异步线程 得到权限env操作】
- jobject thiz 不能跨越线程,否则奔溃,不能跨越函数,否则奔溃 【解决方式:默认是局部引用,提升全局引用,可解决此问题】
- JavaVM 能够跨越线程,能够跨越函数