核心要点
JNI 环境下,进行多线程编程,有以下两点是需明确的:
- JNIEnv 是一个线程作用域的变量,不能跨线程传递,每个线程都有自己的 JNIEnv 且彼此独立
- 局部引用不能在本地函数中跨函数使用,不能跨线前使用,当然也不能直接缓存起来使用
示例程序
示例程序主要演示:
- 如何在子线程获取到属于子线程自己的 JNIEnv
- 上面说了局部引用不能再线程之间直接传递,所以我们只有另觅他法。
Java 层:
public void javaCallback(int count) {
Log.e(TAG, "onNativeCallBack : " + count);
}
public native void threadTest();
Native 层:
static int count = 0;
JavaVM *gJavaVM = NULL;//全局 JavaVM 变量
jobject gJavaObj = NULL;//全局 Jobject 变量
jmethodID nativeCallback = NULL;//全局的方法ID
//这里通过标志位来确定 两个线程的工作都完成了再执行 DeleteGlobalRef
//当然也可以通过加锁实现
bool main_finished = false;
bool background_finished = false;
static void *native_thread_exec(void *arg) {
LOGE(TAG, "nativeThreadExec");
LOGE(TAG, "The pthread id : %d\n", pthread_self());
JNIEnv *env;
//从全局的JavaVM中获取到环境变量
gJavaVM->AttachCurrentThread(&env, NULL);
//线程循环
for (int i = 0; i < 5; i++) {
usleep(2);
//跨线程回调Java层函数
env->CallVoidMethod(gJavaObj, nativeCallback, count++);
}
gJavaVM->DetachCurrentThread();
background_finished = true;
if (main_finished && background_finished) {
env->DeleteGlobalRef(gJavaObj);
LOGE(TAG, "全局引用在子线程销毁");
}
return ((void *) 0);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_yuandaima_myjnidemo_MainActivity_threadTest(JNIEnv *env, jobject thiz) {
//创建全局引用,方便其他函数或线程使用
gJavaObj = env->NewGlobalRef(thiz);
jclass clazz = env->GetObjectClass(thiz);
nativeCallback = env->GetMethodID(clazz, "javaCallback", "(I)V");
//保存全局 JavaVM,注意 JavaVM 不是 JNI 引用类型
env->GetJavaVM(&gJavaVM);
pthread_t id;
if (pthread_create(&id, NULL, native_thread_exec, NULL) != 0) {
return;
}
for (int i = 0; i < 5; i++) {
usleep(20);
//跨线程回调Java层函数
env->CallVoidMethod(gJavaObj, nativeCallback, count++);
}
main_finished = true;
if (main_finished && background_finished && !env->IsSameObject(gJavaObj, NULL)) {
env->DeleteGlobalRef(gJavaObj);
LOGE(TAG, "全局引用在主线程销毁");
}
}
示例代码中,我们的子线程需要使用主线程中的 jobject thiz
,该变量是一个局部引用,不能赋值给一个全局变量然后跨线程跨函数使用,我们通过 NewGlobalRef
将局部引用装换为全局引用并保存在全局变量 jobject gJavaObj
中,在使用完成后我们需要使用 DeleteGlobalRef 来释放全局引用,因为多个线程执行顺序的不确定性,我们使用了标志位来确保两个线程所有的工作完成后再执行释放操作。
JNIEnv 是一个线程作用域的变量,不能跨线程传递,每个线程都有自己的 JNIEnv 且彼此独立,实际开发中,我们通过以下代码:
JavaVM *gJavaVM = NULL;
//主线程获取到 JavaVM
env->GetJavaVM(&gJavaVM);
//子线程通过 JavaVM 获取到自己的 JNIEnv
JNIEnv *env;
gJavaVM->AttachCurrentThread(&env, NULL);
在子线程中获取到 JNIEnv。JavaVM 是一个普通指针,由 JVM 来管理其内存的分配与回收,不是 JNI 引用类型,所以
我们可以把它赋值给一个全局变量,直接用,也不用考虑他的内存分配与后手问题。
关于
我叫阿豪,2015 年本科毕业于国防科技大学指挥自动化专业,毕业后,从事信息化装备的研发工作。主要研究方向为 Android Framework 与 Linux Kernel,2023年春节后开始做 Android Framework 相关的技术分享。
如果你对 Framework 感兴趣或者正在学习 Framework,可以参考我总结的Android Framework 学习路线指南,也可关注我的微信公众号,我会在公众号上持续分享我的经验,帮助正在学习的你少走一些弯路。学习过程中如果你有疑问或者你的经验想要分享给大家可以添加我的微信,我拉你进技术交流群。
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END