Android native开发需要注意native线程的Detach

前言
如题在跨平台开发的过程中,通常跨平层/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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值