前言
如题在跨平台开发的过程中,通常跨平层/C++会创建native线程,如果创建的native线程运行的代码有调用到Java层接口(跟java层有交互),即有调到了AttachCurrentThread,当native线程退出时需要调DetachCurrentThread接口跟JVM世界做分离。否侧会遇到JNI经典崩溃问题之—Native thread exited without calling DetachCurrentThread,如果Deatch的处理逻辑写的不好,可能还会继续遇到JNI经崩溃崩溃问题之—attempting to detach while still running code (这种情况就是java的线程被错误的Detach了!)
解决方案
1.每个线程缓存自己的Env
每个线程缓存自己的Env意味着线程第一个attach到JVM就进行就缓存env实例,待线程退出/销毁时进行deattach操作并置空env实例!实例这个功能需要依赖如下三个接口跟一个线程存储变量/TSD(pthread_key_t变量,变量声名是全局变量,但各个线程会有自己独立的存诸空间)
//创建一个key实例,并绑定key实例释放时的回调函数,即线程退出时会清理所有key,同时调用跟key实例绑定的回调函数!(特别注意这个回调函数,后面会使到它)
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
//保存key实例对应的值,同hashmap的用法
int pthread_setspecific(pthread_key_t key, const void *value);
//获取key对应的值
void *pthread_getspecific(pthread_key_t key);
1
2
3
4
5
6
我们可以参考或抄或拿来主义等等方式使用cocos2d的jni的功能类 cocos2d JinHelper 或 webtrc的 jvm.cc , JinHelper缓存env的关键实现是getEnv中的cacheEnv函数
JNIEnv* JniHelper::getEnv() {
//优先从线程存储的cache中拿
JNIEnv *_env = (JNIEnv *)pthread_getspecific(g_key);
if (_env == nullptr)
_env = JniHelper::cacheEnv(_psJavaVM);
return _env;
}
JNIEnv* JniHelper::cacheEnv(JavaVM* jvm) {
JNIEnv* _env = nullptr;
// get jni environment
jint ret = jvm->GetEnv((void**)&_env, JNI_VERSION_1_4);
switch (ret) {
case JNI_OK :
// Success! ,已经attch了,就存在线程存储中(这种情况是java调用进来又回调到java层的情况)
pthread_setspecific(g_key, _env);
return _env;
case JNI_EDETACHED :
// Thread not attached, detached的话,再attach一把
if (jvm->AttachCurrentThread(&_env, nullptr) < 0)
{
LOGE("Failed to get the environment using AttachCurrentThread()");
return nullptr;
} else {
// Success : Attached and obtained JNIEnv!
pthread_setspecific(g_key, _env);
return _env;
}
case JNI_EVERSION :
// Cannot recover from this error
LOGE("JNI interface version 1.4 not supported");
default :
LOGE("Failed to get the environment using GetEnv()");
return nullptr;
}
}
void JniHelper::setJavaVM(JavaVM *javaVM) {
pthread_t thisthread = pthread_self();
//LOGD("JniHelper::setJavaVM(%p), pthread_self() = %ld", javaVM, thisthread);
_psJavaVM = javaVM;
//绑定key被回收时的回调函数_detachCurrentThread (里边会DetachCurrentThread的方法),从而可以实现线程退出时detach JVM
pthread_key_create(&g_key, _detachCurrentThread);
}
void _detachCurrentThread(void* a) {
cocos2d::JniHelper::getJavaVM()->DetachCurrentThread();
}
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
2.一次性使用Env
一次性使用Env的意思是,每次attch到JVM获到env并使用env后就deattch JVM。保持attch与eattch的配对!这种方法有点简单但有点暴力!(可能会有性能问题)
示例代码如下
JNIEnv* env = nullptr;
int result = g_jvm->GetEnv((void**)&env, JNI_VERSION_1_6);
bool shouldDetach = false;
if (result == JNI_EDETACHED) {
jint attachResult = jvm->AttachCurrentThread(&jenv, NULL);
if (attachResult >= 0)
shouldDetach = true;
}
// env的使用,调java的接口接
....
if (shouldDetach) {
g_jvm->DetachCurrentThread();
}
env = nullptr;
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/SCHOLAR_II/article/details/119676875