Andoird JNI动态注册与 JNI 线程

392 阅读4分钟

Android JNI 动态注册

动态注册与静态注册

  • 静态注册:平时默认的就是静态注册

例如:
在这里插入图片描述

静态注册:

  • 优点:使用简单,方便在程序运行时才会初始化调用
  • 缺点:类名长,与类名捆绑,一旦更改包名会比较麻烦

动态注册

  • 优点:最开始运行的时候就会初始化,代码简洁
  • 缺点:使用比较麻烦.

静态注册就不说了,本篇主要介绍动态注册

动态注册

动态注册像是调用类的构造器一样,每当最开始运行的时候就会初始化所有方法

在这里插入图片描述

在这个方法中,完成 Activity 中的所有初始化!

jint JNI_OnLoad(JavaVM *javaVm, void *) {
 	//返回 JNI 版本号
	return JNI_VERSION_1_6;
}

JNI_OnLoad源码:

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);

第一步:获取 JVM 中的 Env,如果返回结果!=0 说明获取Env失败
注:JVM 是唯一的

//获取 jVM 中的 Env
    int result = javaVm->GetEnv(reinterpret_cast<void **>(&jniEnv), JNI_VERSION_1_6);

    //result != 0 则失败
    if (result != JNI_OK) {
        //失败
        return -1;
    }

这段代码比较简单就不说了!

第二步:通过 Env 动态注册需要初始化的方法

//获取 jclass
    jclass thread_class = jniEnv->FindClass(path);
    /**
     * 源码:
     * jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
     * 参数一:class
     * 参数二:结构体数组
     * 参数三:结构体大小
     */
    jniEnv->RegisterNatives(thread_class, jniNativeMethod,
                            sizeof(jniNativeMethod) / sizeof(JNINativeMethod));
  • 参数一:jclass
  • 参数二:JNINativeMethod的结构体
typedef struct {
    const char* name;		//方法名
    const char* signature;	//签名
    void*       fnPtr;		//回调函数
} JNINativeMethod;
  • 参数三:JNINativeMethod数组长度

参数二对应代码:


void javaDynamicRegist(JNIEnv *jniEnv, jobject jobj) {
    LOGE("javaDynamicRegist")
}

int javaDynamicRegist2(JNIEnv *jniEnv, jobject jobj, jstring name) {
    const char *name2 = jniEnv->GetStringUTFChars(name, nullptr);
    LOGE("javaDynamicRegist2%s\n", name2);
    return 200;
}

/**
     typedef struct {
        const char* name;   //调用的名字
        const char* signature;  //签名
        void*       fnPtr;  //具体实现
    } JNINativeMethod;
 */
static const JNINativeMethod jniNativeMethod[] = {
        {"nativeDynamicRegist",  "()V",                   (void *) javaDynamicRegist},
        {"nativeDynamicRegist2", "(Ljava/lang/String;)I", (void *) javaDynamicRegist2},
};

辅助图:
在这里插入图片描述
使用:
在这里插入图片描述
这里 native 方法报错不用管,这是正常状态!

运行结果为:

2021-04-30 16:57:03.098 330-330/com.example.jni E/native 层:: javaDynamicRegist
2021-04-30 16:57:03.098 330-330/com.example.jni E/native 层:: javaDynamicRegist2李元霸

JNI线程

在 C/C++中,线程使用 pthread

pthread函数说明
pthread_create()创建线程开始运行相关线程函数,运行结束则线程退出
pthread_eixt()因为exit()是用来结束进程的,所以则需要使用特定结束线程的函数
pthread_join()挂起当前线程,用于阻塞式地等待线程结束,如果线程已结束则立即返回,0=成功
pthread_cancel()发送终止信号给thread线程,成功返回0,但是成功并不意味着thread会终止

参考文档

辅助代码图:
在这里插入图片描述
记得导包哦:

#include <pthread.h>

pthread_create()解释:

  • 参数一:线程 ID
  • 参数二:线程属性(一般都是 0 或者 nullptr)
  • 参数三:函数回调
  • 参数四:传递的值

为什么要把jobject设置为全局变量?
答:jobject不能跨越线程,不能跨越函数 [解决思路:吧 jobject 提升为全局引用]

函数回调:

void *my_thread_action(void *pVoid) {
    MyThread *thread = static_cast<MyThread *>(pVoid);
    /**
     * JVM 只有一个 jNIEnv
     * jNIEnv解决方式:
     */
    JNIEnv *newJniEnv = nullptr;
    /**
     *  jint AttachCurrentThread(JNIEnv** p_env, void* thr_args)
     *   得到全新的 JNIEnv
     */
    jint result = ::jvm->AttachCurrentThread(&newJniEnv, nullptr);

    //结果 == 0 表示成功
    if (result != JNI_OK) {
        return 0; // 附加失败,返回了
    }

    jclass j_c = newJniEnv->GetObjectClass(thread->jobj);
    jmethodID j_id = newJniEnv->GetMethodID(j_c, "isThread", "()V");
    //调用 java 层的 isThread 方法
    newJniEnv->CallVoidMethod(thread->jobj, j_id);

    //解除附加
    ::jvm->DetachCurrentThread();
    //不能直接释放引用
    return nullptr;
}

jvm是在JNI_OnLoad初始化的时候获取的

jint JNI_OnLoad(JavaVM *javaVm, void *) {
	 ::jvm = javaVm;
 }

::jvm 相当于 java 中的 this.jvm

最终调用java 层的方法:
在这里插入图片描述
效果图:

注意:

如果 so 库报错,把其他两个注释掉

同理,如果你想看 JNI 基本使用(native-simple-lib.cpp)的代码,那么吧 JNI 进阶和 QQ语音实战的代码注释掉!

现在只能有一个 cpp 文件存在
在这里插入图片描述

完整代码

其他 JNI 文章:

第一篇:Android JNI 入门(含完整Demo)

第二篇:Android JNI 进阶(含完整 Demo)

第三篇:Android JNI QQ 搞怪语音实战 (含完整 Demo)

第五篇:JNI 异常捕获与处理

原创不易,您的点赞就是对我最大的支持~