NDK学习之路-JNI篇

4 篇文章 0 订阅

目录

前言

一、JNI是什么?

 二、基本使用流程

1.在Java中创建native方法

2.C++创建对应的方法

3.常用api使用

 4.引用类型

5.动态注册

6.jni线程操作



前言

JNI是学习NDK的必经之路。

一、JNI是什么?

详情摘录于:JNI笔记 : 数据类型、JNI函数与签名_jni 类型_pecuyu的博客-CSDN博客

jni是Java native interface 的简称,是伴随着Java诞生而出现的,目的就是Java语言和C语言、C++沟通的桥梁,实现了能与底层的交互。

1.表1 基本类型对照表

Java类型JNI类型描述
booleanJboolean无符号8位
byteJbyte无符号8位
charJchar无符号16位
shortJshort有符号16位
intJint有符号32位
longJlong有符号64位
floatJfloat有符号32位
doubleJdouble有符号64位

在jin.h头文件中有如下定义: 

# include <inttypes.h>      /* C99 */
typedef uint8_t         jboolean;       /* unsigned 8 bits */
typedef int8_t          jbyte;          /* signed 8 bits */
typedef uint16_t        jchar;          /* unsigned 16 bits */
typedef int16_t         jshort;         /* signed 16 bits */
typedef int32_t         jint;           /* signed 32 bits */
typedef int64_t         jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */
#else
typedef unsigned char   jboolean;       /* unsigned 8 bits */
typedef signed char     jbyte;          /* signed 8 bits */
typedef unsigned short  jchar;          /* unsigned 16 bits */
typedef short           jshort;         /* signed 16 bits */
typedef int             jint;           /* signed 32 bits */
typedef long long       jlong;          /* signed 64 bits */
typedef float           jfloat;         /* 32-bit IEEE 754 */
typedef double          jdouble;        /* 64-bit IEEE 754 */
#endif

2.表2 引用类型对照表

Java引用类型JNI类型
boolean[]jbooleanArray
byte[]jbyteArray
char[]jcharArray
short[]jshortArray
int[]jintArray
long[]jlongArray
float[]jfloatArray
double[]jdoubleArray
All objectsjobject
java.lang.Classjclass
java.lang.Stringjstring
Object[]jobjectArray
java.lang.Throwablejthrowable

如下图展示了引用类型以及关系:

 二、基本使用流程

1.在Java中创建native方法

例如:


    public native void addTest01(int number, String text, int[] intArray, String[] array);
    public native void putStudent(Student student);

2.C++创建对应的方法

c++方法的解释

extern "C" // 支持C语言的代码
JNIEXPORT // Linux 和 Windows jni.h 内部定义全部都不一样,此宏代表我要暴露出去的标准形式定义
          // 例如:在Windows中,对外暴露的标准就规定了,所以函数必须是Windows系统规则定义的

void JNICALL // Linux 和 Windows jni.h 内部定义全部都不一样,此宏代表 当前函数 压栈规则(行参规则)
             // 例如:Windows中:代表函数压栈 从右 到 左边

C++创建对应的方法及基本数据类型api的调用。

extern "C" JNIEXPORT void JNICALL Java_com_kevin_ndk08_1as_1code_MainActivity_addTest01(
    JNIEnv *env,  // Java虚拟机 自动携带过来的,就是为了 让我们可以使用JNI的API
    jobject thiz, // java中的 MainActivity 这个实例
    jint number,
    jstring text,
    jintArray int_array,
    jobjectArray string_array) {
    // C领域中         JNI领域中          Java领域中
    // int              jint            int
    // const char *    jstring          String

    int my_number = number;
    LOGD("my_number: %d\n", my_number);

    // 参数二:第一重意思:是否在内部完成Copy操作,NULL==0 false,  
    //第二重意思:要给他一个值,让内部可以转起来,这个值,随意
    const char * my_text = env->GetStringUTFChars(text, NULL);
    LOGD("my_text: %s\n", my_text);
    // 回收 GetStringUTFChars
    env->ReleaseStringUTFChars(text, my_text);

    // 打印Int数组
    // jint* GetIntArrayElements(jintArray array, jboolean* isCopy)
    jint * my_int_array = env->GetIntArrayElements(int_array, NULL);
    // jsize GetArrayLength(jarray array)
    jsize  intsize = env->GetArrayLength(int_array);
    for (int i = 0; i < intsize; ++i) {
        int result = *(my_int_array+i);
        *(my_int_array+i)+=1000;
        LOGD("遍历IntArray里面的值:%d\n",result);
    }
    // 回收
    env->ReleaseIntArrayElements(int_array, my_int_array, 0); // 0代表要刷新

    // 打印String数组
    jsize  jsize1 = env->GetArrayLength(string_array);
    for (int i = 0; i < jsize1; i++) {
        jobject jobject1 = env->GetObjectArrayElement(string_array, i);
        jstring  jstring1 = static_cast<jstring>(jobject1);
        const char * itemStr = env->GetStringUTFChars(jstring1, NULL);
        LOGD("遍历String Array 里面的值:%s\n", itemStr);

        // 回收

        env->ReleaseStringUTFChars(jstring1, itemStr);
    }
}

对象的调用

extern "C"
JNIEXPORT void JNICALL
Java_com_kevin_ndk08_1as_1code_MainActivity_putStudent(JNIEnv *env,
                                                       jobject thiz,
                                                       jobject student) {

    // C领域中         JNI领域中          Java领域中
    //                jclass            class
    //                jmethodID         Method

    // 1.获取字节码
    const char * student_clss_str = "com/kevin/ndk08_as_code/Student";
    jclass student_class = env->FindClass(student_clss_str);

    // 2.拿到方法对象
    const char * sig = "(Ljava/lang/String;)V"; // 方法签名  javap -s 全类名 必须在.class下
    jmethodID  setName = env->GetMethodID(student_class, "setName", sig);

    sig = "(I)V";
    jmethodID setAge = env->GetMethodID(student_class, "setAge", sig);

    sig = "()V";
    jmethodID myStaticMethod = env->GetStaticMethodID(student_class, "myStaticMethod", sig);

    // 3.调用对象
    const char * str = "AAAAAAAA";
    jstring str2 = env->NewStringUTF(str);
    env->CallVoidMethod(student, setName, str2);
    env->CallVoidMethod(student, setAge, 888);
    env->CallStaticVoidMethod(student_class, myStaticMethod);

    env->DeleteLocalRef(student_class); // 回收
    env->DeleteLocalRef(student); // 回收
}

3.常用api使用

  1. 获取字节码的方法
    ①.通过全类名获取字节码
     
    //通过全类名获取字节码
    const char * dog_cls_str = "com/kevin/ndk09_code/Dog";
    jclass dogcls = env->FindClass(dog_cls_str);

    ②.通过对象来拿

        jclass dogcls = env->GetObjectClass(jobject);

  2. 获取对象
    ①通过AllocObject,不经过对象的构造方法
     jobject jdog = env->AllocObject(dogcls);

    ②通过NewObject,通过java对象中定义的构造方法来创建对象。需要指定构造方法的jmethodID。

     jobject NewObject(jclass clazz, jmethodID methodID, ...)

  3. 获取对象的jmethodID
    ①普通函数的获取
     
      sig = "(Ljava/lang/String;)V";
      jmethodID setName = env->GetMethodID(student_class, "setName", sig);
    ②static函数的获取
     sig = "()V";
     jmethodID myStaticMethod = env->GetStaticMethodID(student_class, "staticMethodName", sig);

    ③构造函数的获取,第二个参数固定写法 "<init>"

     // Java构造方法的实例化
        const char * sig = "()V";
        const char * method = "<init>"; // Java构造方法的标识
        jmethodID init = env->GetMethodID(dogClass, method, sig);

 4.引用类型

如何创建全局引用,static_cast<jclass>(env->NewGlobalRef(temp));

jclass dogClass;
extern "C"
JNIEXPORT void JNICALL
Java_com_kevin_ndk09_1code_MainActivity_testDog(JNIEnv *env, jobject thiz) {
    // 局部引用:如果在函数里面,是在栈区,不用回收,函数结束,会自动回收 ,为了专业性,最好要写回收

    if (dogClass == NULL) { // 第一次满足,  第二次不满足了
        // 局部引用的方式
        /*const char * dog_class_str = "com/kevin/ndk09_code/Dog";
        dogClass = env->FindClass(dog_class_str);*/

        // 解决各个局部引用带来的问题,全局引用(自己来提升)
        const char * dog_class_str = "com/kevin/ndk09_code/Dog";
        jclass temp = env->FindClass(dog_class_str);
        dogClass = static_cast<jclass>(env->NewGlobalRef(temp));

        // 手动释放全局引用之后,再次点击,没有进来
        __android_log_print(ANDROID_LOG_DEBUG, "Kevin", "dogClass == NULL");
    }

    // Java构造方法的实例化
    const char * sig = "()V";
    const char * method = "<init>"; // Java构造方法的标识
    jmethodID init = env->GetMethodID(dogClass, method, sig);
    env->NewObject(dogClass, init); // 由于dogClass 是悬空的,直接报错

    // 会隐士释放 dogClass  , dogClass不为NULL, 悬空   同时手动全局释放一致
}

 手动释放全局

// 此函数就是为了 手动释放全局
extern "C"
JNIEXPORT void JNICALL
Java_com_kevin_ndk09_1code_MainActivity_testUnDog(JNIEnv *env, jobject thiz) {
    // TODO: implement testUnDog()
    if(dogClass != nullptr) {
        __android_log_print(ANDROID_LOG_DEBUG, "Kevin", "全局应用被释放了,上面的按钮不能点击了,否则报错");
        env->DeleteGlobalRef(dogClass);
        dogClass = nullptr;
    }

    // Studnet * student = new Student; // 堆 必须释放
}

5.动态注册
 

动态注册流程

  • 编写Java端的相关native方法
  • 编写C/C++代码, 实现JNI_Onload()方法
  • 将Java 方法和 C/C++方法通过签名信息一一对应起来
  • 通过JavaVM获取JNIEnv, JNIEnv主要用于获取Java类和调用一些JNI提供的方法
  • 使用类名和对应起来的方法作为参数, 调用JNI提供的函数RegisterNatives()注册方法

重写函数JNI_OnLoad,此函数会在 System.loadLibrary("native-lib"); 时加载。

JNI_OnLoad即可获取javaVM虚拟机实例,jvm可以跨线程。

// 下面是动态注册
JavaVM * jvm;

/*需要动态注册的方法结构体
 * typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
    } JNINativeMethod;
 */
static const JNINativeMethod jniNativeMethod[] = {
        {"registerJava01", "(Ljava/lang/String;)V", (void *)(register01)},
        {"registerJava02", "(Ljava/lang/String;)V", (int *)(register02)}
};
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM * javaVm, void * pVoid){
    jvm = javaVm;

    // 通过虚拟机 创建全新的 evn
    JNIEnv * env = nullptr;
    jint result = javaVm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6); // 参数2:是JNI的版本 NDK 1.6   JavaJni 1.8
    if (result != JNI_OK) {
        return -1; // 主动报错
    }

    const char * mainActivityClassStr = "com/kevin/ndk09_code/MainActivity";
    jclass mainActivityClass = jniEnv->FindClass(mainActivityClassStr);

    jniEnv->RegisterNatives(mainActivityClass, jniNativeMethod, sizeof(jniNativeMethod) / sizeof(JNINativeMethod)); // 参数三:到底要动态注册几个

    return JNI_VERSION_1_6;
}

6.jni线程操作


jobject instance;//调用函数的实例,要提升为全局变量,并记得释放。

//线程的异步方法
void * customThread(void * pVoid) {
    // jobject instance = static_cast<jobject>(pVoid);
    // 如果instance不提升为全局,悬空 == instance
    // 调用的话,一定需要JNIEnv *env
    // JNIEnv *env 无法跨越线程,只有JavaVM才能跨越线程

    JNIEnv * env = nullptr; // 全新的env
    // 把native的线程,附加到JVM,并实例化env
    int result = jvm->AttachCurrentThread(&env, 0); 
    if (result != 0) {//实例化结果
        return 0;
    }
    // MainActivity main = AllocaObject(); // C++实例化MainActivity,无法拿到MainActivity上下文

    // 包名 + 类名 同样不能拿到上下文
    /*const char * mainActivityStr = "com/kevin/ndk09_code/MainActivity";
    jclass mainActivityClass = env->FindClass(mainActivityStr);*/

    //通过对象拿到字节码才可以
    jclass mainActivityClass = env->GetObjectClass(instance);

    // 拿到MainActivity的updateUI
    const char * sig = "()V";
    jmethodID updateUI = env->GetMethodID(mainActivityClass, "updateUI", sig);

    env->CallVoidMethod(instance, updateUI);

    // 解除 附加 到 JVM 的native线程,必须要
    jvm->DetachCurrentThread();

    return 0;
}

//native 方法实现
extern "C"
JNIEXPORT void JNICALL
Java_com_kevin_ndk09_1code_MainActivity_testThread(JNIEnv *env, jobject thiz) {
    instance = env->NewGlobalRef(thiz); // 全局的,就不会被释放,所以可以在线程里面用
    // 如果是非全局的,函数一结束,就被释放了
    pthread_t pthreadID;
    pthread_create(&pthreadID, 0, customThread, instance);
    pthread_join(pthreadID, 0);
}

 主动释放被提升为全局变量的 instance。可以在activity中onDestroy中调用。

// 释放全局引用
extern "C"
JNIEXPORT void JNICALL
Java_com_kevin_ndk09_1code_MainActivity_unThread(JNIEnv *env, jobject thiz) {
    if (NULL != instance) {
        env->DeleteGlobalRef(instance);
        instance = NULL;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值