NDK之JNI多线程

前面讲到记录到ffmpeg音视频解码的时候,采用的是在主线程中进行操作,这样是不行的,在学习了POSIX多线程操作以后,就可以实现其在子线程中解码了,也可以实现音视频同步了

简单示例

在native实现中,直接调用pthread的多线程方法,这样就可以在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
25
26
27
28
29
30
31
32
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <android/log.h>

#include "com_cj5785_posixtest_PosixThread.h"

#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO, "cj5785",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR, "cj5785",FORMAT,##__VA_ARGS__);

void* thr_func(void *arg)
{
char *buf = (char *)arg;
int i = 0;
for (i = 0; i < 10; ++i) {
LOGI("thread %s --- %d", buf, i);
if(i == 8)
{
pthread_exit((void *)-1);
}
sleep(1);
}
return (void *)0;
}

JNIEXPORT void JNICALL Java_com_cj5785_posixtest_PosixThread_pthread
(JNIEnv *env, jobject jobj)
{
//创建多线程
pthread_t tid;
pthread_create(&tid, NULL, thr_func, (void *)"thread-0");
}

运行apk发现,主线程并未被阻塞,多线程开启成功

关于JNIEnv与JavaVM

  • 每个线程都会有自己独立的JNIEnv,因此不建议将主线程的JNIEnv传入到子线程中
    而是应该在子线程中获取到自己的JNIEnv

  • 在Android中,只有一个JavaVM对象

  • 那么就可以通过JavaVM获取到每一个线程关联的JNIEnv
    在JNI中,有两种方式可以得到JavaVM

    • JNI_OnLoad函数中获取,JNI_OnLoad在动态库加载时候就会执行,可以在JNI中实现JNI_OnLoad方法,可以得到验证

      1
      2
      3
      4
      JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
      LOGI("%s","JNI_OnLoad run");
      return JNI_VERSION_1_4;
      }
    • (*env)->GetJavaVM(env,&javaVM);,这种方法在创建子线程中调用即可

  • 在得到javaVM以后,就可以将javaVM关联当前线程,就可以获取到当前线程的JNIEnv

    1
    (*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
  • 在使用完毕以后必须解除关联

    1
    (*javaVM)->DetachCurrentThread(javaVM);

注意:这里有一个大坑,JNIEnv不可作为全局变量保存,而应该使用NewGlobalRef()来新建一个全局引用
Native层本地多线程回调Java函数时env->findClass()失败,这里是由于在子线程中不可以找不到类,这个动作需要在主线程中完成

NDK调试

  • 首先拿到出错地址,在:项目/obj/local/armeadi目录下
    adb logcat | ndk-stack -sym 链接库地址本地位置
    这时候定位到第一个地址,这个地址就是出错地址
  • 使用ndk下的编译链工具查找出错的行数
    adb logcat | arm-linux-androideabi-addr2line -e 链接库地址本地位置+so

完成以上两步就能够定位出错的位置了

通过JNI拿到Java生成的UUID示例

在Java中生成UUID

1
2
3
4
5
6
import java.util.UUID;
public class UUIDUtils {
public static String getUUID() {
return UUID.randomUUID().toString();
}
}

native方法类中的方法

1
2
3
public native void init();
public native void pthread();
public native void destory();

在native实现中得到UUID值

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
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <android/log.h>

#include "com_cj5785_posixtest_PosixThread.h"

#define LOGI(FORMAT,...) __android_log_print(4, "cj5785",FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(6, "cj5785",FORMAT,##__VA_ARGS__);

JavaVM *javaVM = NULL;
jclass uuidutils_cls_global = NULL;
jmethodID uuidutils_mid = NULL;

//动态库加载时会执行
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
LOGI("%s","JNI_OnLoad");
//javaVM = vm;
return JNI_VERSION_1_4;
}

void* thr_func(void *arg)
{
JNIEnv *env = NULL;
//关联
(*javaVM)->AttachCurrentThread(javaVM, &env, NULL);
char *buf = (char *)arg;
int i = 0;
for (i = 0; i < 10; ++i) {
LOGI("%s --- %d", buf, i);
jobject uuid = (*env)->CallStaticObjectMethod(env, uuidutils_cls_global, uuidutils_mid);
const char *uuid_cstr = (*env)->GetStringUTFChars(env, uuid, NULL);
LOGI("%s", uuid_cstr);
(*env)->ReleaseStringUTFChars(env, uuid, uuid_cstr);
}
//解除关联
(*javaVM)->DetachCurrentThread(javaVM);
pthread_exit((void *)0);
}

//初始化
JNIEXPORT void JNICALL Java_com_cj5785_posixtest_PosixThread_init
(JNIEnv *env, jobject jobj)
{
//在主线程中获取class以及methodID
jclass uuidutils_jcls = (*env)->FindClass(env, "com/cj5785/posixtest/UUIDUtils");
uuidutils_cls_global = (*env)->NewGlobalRef(env, uuidutils_jcls);
uuidutils_mid = (*env)->GetStaticMethodID(env, uuidutils_jcls, "getUUID", "()Ljava/lang/String;");
}

JNIEXPORT void JNICALL Java_com_cj5785_posixtest_PosixThread_pthread
(JNIEnv *env, jobject jobj)
{
(*env)->GetJavaVM(env, &javaVM);
//创建多线程
pthread_t tid;
pthread_create(&tid, NULL, thr_func, (void *)"thread-0");
}

//销毁
JNIEXPORT void JNICALL Java_com_cj5785_posixtest_PosixThread_destory
(JNIEnv *env, jobject jobj)
{
(*env)->DeleteGlobalRef(env, uuidutils_cls_global);
}

Donate comment here