1. 静态注册与动态注册函数

1.1 静态注册函数

在开发默认情况下,就是静态注册,静态注册是最简单的方式,NDK开发过程中,基本上使用静态注册,但是Android 系统的C++源码:基本上都是动态注册。

静态注册: 优点:开发简单
缺点
1.JNI函数名非常长
2.捆绑 上层 包名 + 类名
3.运行期 才会去 匹配JNI函数,性能上 低于 动态注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 日志输出
#include <android/log.h>

#define TAG "JNISTUDY"
// __VA_ARGS__ 代表 ...的可变参数
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__);

#include <jni.h>
#include <string>
#include <pthread.h> // 在AS上 pthread不需要额外配置,默认就有

extern "C" JNIEXPORT jstring JNICALL
Java_com_jack_as_1jni_1project_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "默认就是静态注册哦";
return env->NewStringUTF(hello.c_str());
}

extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity_staticRegister(JNIEnv *env, jobject thiz) {}
1.2 动态注册函数

JavaVM、JNIEnv、jobject 域的结论:

  1. JavaVM全局,绑定当前进程, 只有一个地

  2. JNIEnv线程绑定, 绑定主线程,绑定子线程

  3. jobject 谁调用JNI函数,谁的实例会给jobject

  4. JNIEnv *env 不能跨越线程,否则奔溃, 他可以跨越函数
    【解决方式:使用全局的JavaVM附加当前异步线程 得到权限env操作】

  5. jobject thiz 不能跨越线程,否则奔溃,不能跨越函数,否则奔溃
    【解决方式:默认是局部引用,提升全局引用,可解决此问题】

  6. JavaVM 能够跨越线程,能够跨越函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
JavaVM *jVm = nullptr; // 0x003545 系统乱值,C++11后,取代NULL,作用是可以初始化指针赋值
const char *mainActivityClassName = "com/jack/as_jni_project/MainActivity";

// native 真正的执行函数
// void dynamicMethod01(JNIEnv *env, jobject thiz) { // OK的
// 如果你用不到 JNIEnv jobject ,可以不用写
void dynamicMethod01() {
LOGD("我是动态注册的函数 dynamicMethod01...");
}

int dynamicMethod02(JNIEnv *env, jobject thiz, jstring valueStr) { // 也OK
const char *text = env->GetStringUTFChars(valueStr, nullptr);
LOGD("我是动态注册的函数 dynamicMethod02... %s", text);
env->ReleaseStringUTFChars(valueStr, text);
return 200;
}

/*
typedef struct {
const char* name; // 函数名
const char* signature; // 函数的签名
void* fnPtr; // 函数指针
} JNINativeMethod;
*/
static const JNINativeMethod jniNativeMethod[] = {
{"dynamicJavaMethod01", "()V", (void *) (dynamicMethod01)},
{"dynamicJavaMethod02", "(Ljava/lang/String;)I", (int *) (dynamicMethod02)},
};


// Java:像 Java的构造函数,如果你不写构造函数,默认就有构造函数,如果你写构造函数 覆写默认的构造函数
// JNI JNI_OnLoad函数,如果你不写JNI_OnLoad,默认就有JNI_OnLoad,如果你写JNI_OnLoad函数 覆写默认的JNI_OnLoad函数
extern "C"
JNIEXPORT jint JNI_OnLoad(JavaVM *javaVm, void *) {
// this.javaVm = javaVm;
// javaVm全局唯一
::jVm = javaVm;

// 做动态注册 全部做完
// JNIEnv线程唯一
JNIEnv *jniEnv = nullptr;
int result = javaVm->GetEnv(reinterpret_cast<void **>(&jniEnv), JNI_VERSION_1_6);

// result 等于0 就是成功
if (result != JNI_OK) {
return -1; // 会奔溃,故意奔溃
}

LOGE("System.loadLibrary ---》 JNI Load init");

jclass mainActivityClass = jniEnv->FindClass(mainActivityClassName);

// jint RegisterNatives(Class, 我们的数组==jniNativeMethod, 注册的数量 = 2)
jniEnv->RegisterNatives(mainActivityClass,
jniNativeMethod,
sizeof(jniNativeMethod) / sizeof(JNINativeMethod));

LOGE("动态 注册没有毛病");

return JNI_VERSION_1_6; // // AS的JDK在JNI默认最高1.6 存Java的JDKJNI 1.8
}

2. JNI子线程中,对JniEnv的理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
class MyContext {
public:
JNIEnv *jniEnv = nullptr; // 不能跨线程 ,会奔溃
jobject instance = nullptr; // 不能跨线程 ,会奔溃
};

// 在子线程中跨线程对JniEnv的使用
// 方法1,在子线程中使用AttachCurrentThread来获取jnienv
void *myThreadTaskAction(void *pVoid) { // 当前是异步线程
LOGE("myThreadTaskAction run");

// 需求:有这样的场景,
// 例如:下载完成 ,下载失败,等等,必须告诉Activity UI端,所以需要在子线程调用UI端

// 这两个是必须要的
// JNIEnv *env
// jobject thiz OK

MyContext * myContext = static_cast<MyContext *>(pVoid);

// jclass mainActivityClass = myContext->jniEnv->FindClass(mainActivityClassName); // 不能跨线程 ,会奔溃
// mainActivityClass = myContext->jniEnv->GetObjectClass(myContext->instance); // 不能跨线程 ,会奔溃

// TODO 解决方式 (安卓进程只有一个 JavaVM,是全局的,是可以跨越线程的)
JNIEnv * jniEnv = nullptr; // 全新的JNIEnv 异步线程里面操作
jint attachResult = ::jVm->AttachCurrentThread(&jniEnv, nullptr); // 附加当前异步线程后,会得到一个全新的 env,此env相当于是子线程专用env
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;
}

// 通过创建MyContext并且把
extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity_naitveThread(JNIEnv *env, jobject job) { // 当前是主线程
/*pthread_t pid;
pthread_create(&pid, nullptr, myThreadTaskAction, nullptr);
pthread_join(pid, nullptr);*/

MyContext * myContext = new MyContext;
myContext->jniEnv = env;
// myContext->instance = job; // 默认是局部引用,会奔溃
myContext->instance = env->NewGlobalRef(job); // 提升全局引用

pthread_t pid;
pthread_create(&pid, nullptr, myThreadTaskAction, myContext);
pthread_join(pid, nullptr);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity_closeThread(JNIEnv *env, jobject thiz) {
// 做释放工作
}


// 第三部分 纠结纠结细节 区域 ==================================================

extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity_nativeFun1(JNIEnv *env, jobject job) {
JavaVM * javaVm = nullptr;
env->GetJavaVM(&javaVm);

// 打印:当前函数env地址, 当前函数jvm地址, 当前函数job地址, JNI_OnLoad的jvm地址
LOGE("nativeFun1 当前函数env地址%p, 当前函数jvm地址:%p, 当前函数job地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, job, ::jVm);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity_nativeFun2(JNIEnv *env, jobject job) {
JavaVM * javaVm = nullptr;
env->GetJavaVM(&javaVm);

// 打印:当前函数env地址, 当前函数jvm地址, 当前函数job地址, JNI_OnLoad的jvm地址
LOGE("nativeFun2 当前函数env地址%p, 当前函数jvm地址:%p, 当前函数job地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, job, ::jVm);
}

void * run(void *) { // native的子线程 env地址 和 Java的子线程env地址,一样吗 不一样的
JNIEnv * newEnv = nullptr;
::jVm->AttachCurrentThread(&newEnv, nullptr);
// 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址, JNI_OnLoad的jvm地址

LOGE("run jvm地址:%p, 当前run函数的newEnv地址:%p \n", ::jVm, newEnv);

::jVm->DetachCurrentThread();
return nullptr;
}

extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity_staticFun3(JNIEnv *env, jclass clazz) {
JavaVM * javaVm = nullptr;
env->GetJavaVM(&javaVm);

// 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址, JNI_OnLoad的jvm地址
LOGE("nativeFun3 当前函数env地址%p, 当前函数jvm地址:%p, 当前函数clazz地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, clazz, ::jVm);

// 调用run
pthread_t pid;
pthread_create(&pid, nullptr, run, nullptr);
}

// Java子线程调用的
extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity_staticFun4(JNIEnv *env, jclass clazz) {
JavaVM * javaVm = nullptr;
env->GetJavaVM(&javaVm);

// 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址, JNI_OnLoad的jvm地址
LOGE("nativeFun4 当前函数env地址%p, 当前函数jvm地址:%p, 当前函数clazz地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, clazz, ::jVm);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_jack_as_1jni_1project_MainActivity2_nativeFun5(JNIEnv *env, jobject job) {
JavaVM * javaVm = nullptr;
env->GetJavaVM(&javaVm);

// 打印:当前函数env地址, 当前函数jvm地址, 当前函数clazz地址, JNI_OnLoad的jvm地址
LOGE("nativeFun5 当前函数env地址%p, 当前函数jvm地址:%p, 当前函数job地址:%p, JNI_OnLoad的jvm地址:%p\n", env, javaVm, job, ::jVm);
}