前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于JNI开发的一些建议

关于JNI开发的一些建议

作者头像
马上就说
发布2022-05-25 14:48:37
7250
发布2022-05-25 14:48:37
举报
文章被收录于专栏:码上就说码上就说

上一篇文章介绍了JNI开发的基础知识,但是大多数同学在JNI开发中还是会遇到一些问题,我们选择一些问题给大家分析一下,希望对大家有些帮助。

JNIEnv和线程相关,一个线程对应一个JNIEnv,如果想要切换到当前线程的JNIEnv,首先通过JavaVM获取JNIEnv,然后调用JavaVM的AttachCurrentThread来切换到对应线程的JNIEnv,使用完了之后还要DetachCurrentThread切走。JNI开发中经常需要切换线程,那我们是不是要频繁地AttachCurrentThread和DetachCurrentThread。通常有两种方式来处理这种问题。

每个线程缓存JNIEnv

每个线程都缓存自己的JNIEnv实例表明如果这个线程attach到JavaVM的时候就将获取的JNIEnv保存起来,等会销毁这个线程的时候才将JNIEnv实例DetachCurrentThread销毁。webrtc中就有一个很好的例子:https://webrtc.googlesource.com/src/+/refs/heads/main/sdk/android/src/jni/jvm.cc,此文件包含几个函数:

每个线程Attach的时候调用此函数,注意调用了pthread_setspecific会将JNIEnv实例和当前线程绑定,然后通过pthread_getspecific获取存储的JNIEnv实例。

代码语言:javascript
复制
// Return a |JNIEnv*| usable on this thread.  Attaches to `g_jvm` if necessary.
JNIEnv* AttachCurrentThreadIfNeeded() {
  JNIEnv* jni = GetEnv();
  if (jni)
    return jni;
  RTC_CHECK(!pthread_getspecific(g_jni_ptr))
      << "TLS has a JNIEnv* but not attached?";

  std::string name(GetThreadName() + " - " + GetThreadId());
  JavaVMAttachArgs args;
  args.version = JNI_VERSION_1_6;
  args.name = &name[0];
  args.group = nullptr;
// Deal with difference in signatures between Oracle's jni.h and Android's.
#ifdef _JAVASOFT_JNI_H_  // Oracle's jni.h violates the JNI spec!
  void* env = nullptr;
#else
  JNIEnv* env = nullptr;
#endif
  RTC_CHECK(!g_jvm->AttachCurrentThread(&env, &args))
      << "Failed to attach thread";
  RTC_CHECK(env) << "AttachCurrentThread handed back NULL!";
  jni = reinterpret_cast<JNIEnv*>(env);
  RTC_CHECK(!pthread_setspecific(g_jni_ptr, jni)) << "pthread_setspecific";
  return jni;
}

JNI_OnLoad初始化的时候会注册一个函数:如果发现当前线程退出的情况下,就是调用Detach和当前线程解绑。

代码语言:javascript
复制
static void ThreadDestructor(void* prev_jni_ptr) {
  // This function only runs on threads where `g_jni_ptr` is non-NULL, meaning
  // we were responsible for originally attaching the thread, so are responsible
  // for detaching it now.  However, because some JVM implementations (notably
  // Oracle's http://goo.gl/eHApYT) also use the pthread_key_create mechanism,
  // the JVMs accounting info for this thread may already be wiped out by the
  // time this is called. Thus it may appear we are already detached even though
  // it was our responsibility to detach!  Oh well.
  if (!GetEnv())
    return;

  RTC_CHECK(GetEnv() == prev_jni_ptr)
      << "Detaching from another thread: " << prev_jni_ptr << ":" << GetEnv();
  jint status = g_jvm->DetachCurrentThread();
  RTC_CHECK(status == JNI_OK) << "Failed to detach thread: " << status;
  RTC_CHECK(!GetEnv()) << "Detaching was a successful no-op???";
}

static void CreateJNIPtrKey() {
  RTC_CHECK(!pthread_key_create(&g_jni_ptr, &ThreadDestructor))
      << "pthread_key_create";
}

jint InitGlobalJniVariables(JavaVM* jvm) {
  RTC_CHECK(!g_jvm) << "InitGlobalJniVariables!";
  g_jvm = jvm;
  RTC_CHECK(g_jvm) << "InitGlobalJniVariables handed NULL?";

  RTC_CHECK(!pthread_once(&g_jni_ptr_once, &CreateJNIPtrKey)) << "pthread_once";

  JNIEnv* jni = nullptr;
  if (jvm->GetEnv(reinterpret_cast<void**>(&jni), JNI_VERSION_1_6) != JNI_OK)
    return -1;

  return JNI_VERSION_1_6;
}

这样做的好处比较明显,就是每个线程绑定唯一的JNIEnv实例,不用频繁地Attach和Detach。大型的项目可以这样采用,比较方便。

使用时获取JNIEnv

还有一种方式就是每个需要使用JNIEnv的实例的时候Attach,用完之后立即Detach。下面就是例子,每次调用jni_get_env获取当前线程的JNIEnv实例,使用完之后,调用jni_detach_thread_env解绑。

代码语言:javascript
复制
JavaVM *media_jni_get_java_vm() {
  void *vm;
  pthread_mutex_lock(&lock);
  vm = java_vm;
  pthread_mutex_unlock(&lock);
  return vm;
}

int jni_get_env(JNIEnv **env) {
  JavaVM *vm = media_jni_get_java_vm();
  int ret = (*vm)->GetEnv(vm, (void **) env, JNI_VERSION_1_6);
  if (ret == JNI_EDETACHED) {
    if ((*vm)->AttachCurrentThread(vm, env, NULL) != JNI_OK) {
      LOGE("%s Failed to attach the JNI environment to the current thread", __func__);
      *env = NULL;
      return -10;
    }
  }
  return ret;
}

void jni_detach_thread_env() {
  JavaVM *vm = media_jni_get_java_vm();
  (*vm)->DetachCurrentThread(vm);
}

这样的好处是比较容易,但是缺点是Attach和Detach过分频繁,可能会影响性能。选择第二种方式,有一个比较容易出错的点。因为JNIEnv不是缓存下来的,每次获取的JNIEnv实例都是不同的,如果需要调用Java层类中static方法,使用第二种方法调用会出现ClassNotFound的问题,为什么会出现这种现象了,因为JNIEnv变化了,对应的ClassLoader变化了,所以多次调用static方法,会出现前后的ClassLoader不一样,之前的类已经被前一个ClassLoader加载了,后面一个ClassLoader肯定就找不到了。

如何在JNI中访问Bitmap数据

如果想让Bitmap中的数据在native层也能访问到,怎么做呢?

通常也有两种方法,方法一当然是直接传输Bitmap对象到native层,JNI中会收到一个jobject对象。将Bitmap中的数据存储到uint8_t数组中,直接访问。

代码语言:javascript
复制
jobject j_bitmap = env->CallObjectMethod(j_album_info, env->GetMethodID(clazz, "getImage", "()Landroid/graphics/Bitmap;"));
uint8_t *image_data;
int64_t size = 0;
if (j_bitmap != nullptr) {
  AndroidBitmapInfo info;
  int result = AndroidBitmap_getInfo(env, j_bitmap, &info);
  if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
    LOGE("get bitmap info failed, result=%d", result);
  }
  result = AndroidBitmap_lockPixels(env, j_bitmap, reinterpret_cast<void **>(&image_data));
  if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
    LOGE("lock pixel failed, result=%d", result);
  }
  size = info.stride * info.height;
  result = AndroidBitmap_unlockPixels(env, j_bitmap);
  if (result != ANDROID_BITMAP_RESULT_SUCCESS) {
    LOGE("unlock pixel failed, result=%d", result);
  }
}

还有一种方法是在Java层将Bitmap中数据写入ByteBuffer,native层访问ByteBuffer。底层通过GetDirectBufferAddress来访问ByteBuffer地址上的数据。

代码语言:javascript
复制
Bitmap bitmap = BitmapFactory.decodeFile(path);
if (bitmap != null) {
    mWidth = bitmap.getWidth();
    mHeight = bitmap.getHeight();
    int byteCount = bitmap.getByteCount();
    ByteBuffer buffer = ByteBuffer.allocateDirect(byteCount);
    bitmap.copyPixelsToBuffer(buffer);
    buffer.flip();
    return buffer;
}

你还遇到哪些JNI的问题,可以在留言区交流一下。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-05-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 音视频平凡之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档