jni学习

接上一篇android中的jni

上一篇我们看android源码中的jni用法,现在继续。

1. 介绍

1.1 what

Java代码 里调用 C、C++等语言的代码 或 C、C++代码调用 Java 代码

  • JNI是 Java 调用 Native 语言的一种特性
  • JNI 是属于 Java 的,与 Android 无直接关系

1.2 why

  • 背景:实际使用中,Java 需要与 本地代码 进行交互
  • 问题:因为 Java 具备跨平台的特点,所以Java 与 本地代码交互的能力非常弱
  • 解决方案: 采用 JNI特性 增强 Java 与 本地代码交互的能力

1.3 how

  1. java中声明native方法
  2. 编译java源码文件得到.class文件
  3. javah命令导出jni头文件,.h文件
  4. 使用java需要交互的本地代码实现java中声明的Native方法
  5. 如有需要,c++中也可实现java的navive方法

1.4 ndk

Native Development Kit,是 Android的一个工具开发包,属于android,与java无关。

  • 作用:快速开发c c++动态库,自动将so打包成apk
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述

1.5 两种注册方法

  • 静态注册
public class JniDemo1{
       static {
             System.loadLibrary("samplelib_jni");
        }

        private native void nativeMethod();
}
//返回值 + Java前缀+全路径类名+方法名+参数1JNIEnv+参数2jobject+其他参数
  • 动态注册

    System.loadLibarary()方法加载so库的时候,Java虚拟机就会找到这个JNI_OnLoad函数兵调用该函数,这个函数的作用是告诉Dalvik虚拟机此C库使用的是哪一个JNI版本,VM会默认该库使用最老的JNI 1.1版本。最新版本的JNI做了很多扩充,也优化了一些内容,必须在JNI_OnLoad()函数声明JNI的版本。同时也可以在该函数中做一些初始化的动作。该函数前面也有三个关键字分别是JNIEXPORTJNICALLjint。其中JNIEXPORTJNICALL是两个宏定义,用于指定该函数时JNI函数。jint是JNI定义的数据类型,因为Java层和C/C++的数据类型或者对象不能直接相互的引用或者使用,JNI层定义了自己的数据类型,用于衔接Java层和JNI层。

public class JniDemo1{
       static {
             System.loadLibrary("samplelib_jni");
        }
}
#include <jni.h>
#include "Log4Android.h"
#include <stdio.h>
#include <stdlib.h>

using namespace std;

#ifdef __cplusplus
extern "C" {
#endif

static const char *className = "com/gebilaolitou/jnidemo/JNIDemo2";

static void sayHello(JNIEnv *env, jobject, jlong handle) {
    LOGI("JNI", "native: say hello ###");
}
//方法签名 格式  (参数1类型标示;参数2类型标示;参数3类型标示...)返回值类型标示
static JNINativeMethod gJni_Methods_table[] = {
    {"sayHello", "(J)V", (void*)sayHello},
};

static int jniRegisterNativeMethods(JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    LOGI("JNI","Registering %s natives\n", className);
    clazz = (env)->FindClass( className);
    if (clazz == NULL) {
        LOGE("JNI","Native registration unable to find class '%s'\n", className);
        return -1;
    }

    int result = 0;
    if ((env)->RegisterNatives(clazz, gJni_Methods_table, numMethods) < 0) {
        LOGE("JNI","RegisterNatives failed for '%s'\n", className);
        result = -1;
    }

    (env)->DeleteLocalRef(clazz);
    return result;
}

//这个函数里面动态的注册native方法
jint JNI_OnLoad(JavaVM* vm, void* reserved){
    LOGI("JNI", "enter jni_onload");

    JNIEnv* env = NULL;
    jint result = -1;
	//获取JNIEnv结构体指针
    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return result;
    }

    jniRegisterNativeMethods(env, className, gJni_Methods_table, sizeof(gJni_Methods_table) / sizeof(JNINativeMethod));

    return JNI_VERSION_1_4;
}

#ifdef __cplusplus
}
#endif

1.6 动态注册流程

//1)声明 Java 层 Native 方法 : 在 Java 类中声明 native 方法    
/**
     * 动态注册 JNI 方法 , Java 方法
     */
    public native void dynamicRegisterJavaMethod();
    public native int dynamicRegisterJavaMethod2(int i);
// 2)准备数据 JNINativeMethod methods[] 数组
/*
    该数组中 , 每个元素都是一个 JNI 的 Native 方法
    JNINativeMethod 是结构体
        typedef struct {
            const char* name;       //Java 中定义的 Native 方法名 , 注意这是一个 C 字符串
            const char* signature;  //函数签名 , 可以使用 javap 生成
            void*       fnPtr;      //C/C++ 中的 Native 函数签名
        } JNINativeMethod;
 */
static const JNINativeMethod methods[] = {
        {"dynamicRegisterJavaMethod", "()V", (void *)dynamicRegisterCMethod},
        {"dynamicRegisterJavaMethod2", "(I)I", (void *)dynamicRegisterCMethod2}
};
//3)编写JNI_Onload方法
int JNI_OnLoad(JavaVM *vm , void *r){
	return JNI_VERSION_1_6;
}
//4)获取JENEnv指针,调用JavaVM 结构体的 GetEnv 方法 , 获取 JNIEnv 指针
    //1 . 获取 JNIEnv JNI 环境 , 需要从 JavaVM 获取
    JNIEnv *env = nullptr;

    //2 . 调用 JavaVM / _JavaVM 结构体的 jint GetEnv(void** env, jint version) 方法
    //      返回值分析 : 动态注册会返回一个结果
    //          如果 registerResult < 0 , 则动态注册失败
    //          如果 registerResult == 0 , 则动态注册失败
    int registerResult = vm->GetEnv( (void **) &env, JNI_VERSION_1_6 );
//5) 获取java类,调用JNIEnv结构体的FindClass方法获取jclass对象
/*
    动态注册的 Java 类名称
        注意 : 包名类名之间使用 "/" 分割
 */
static const char* className = "kim/hsl/onload/MainActivity";

//获取要动态注册的 Java 类的 Class 对象
 jclass jclazz = env->FindClass(className);
//6)进行动态注册,调用JNIEnv的RegisterNatives方法,进行正式注册
    /*
      5 .正式注册

            注册方法解析 :

            jint RegisterNatives(
                jclass clazz,                   //要注册的 Java 类
                const JNINativeMethod* methods, //JNI注册方法数组
                jint nMethods                   //要注册的 JNI 方法个数
            )


            sizeof(methods) / sizeof(JNINativeMethod) : 计算 JNINativeMethod methods[] 数组大小

     */
env->RegisterNatives(jclazz, methods, sizeof(methods) / sizeof(JNINativeMethod));

2 案例

2.1基本数据类型传递

java 基本数据类型boolean byte char short int long float double

C/C++编写native代码时不能直接使用java,

jni提供了 jbooan jbyte jchar jshot jint jlong jfloat jdouble

2.2 引用数据类型

jni包含大量的引用类型 jObject jstring jclass jbooleanArray jbyteArray jcharArray jshortArray

2.2.1 数组

// native->java
extern "C"
JNIEXPORT jbooleanArray JNICALL
Java_com_ihubin_ndkjni_NativeUtil_getNativeArray(JNIEnv *env, jclass clazz) {
    jboolean* jb = new jboolean[5];
    jb[0] = JNI_TRUE;
    jb[1] = JNI_FALSE;
    jb[2] = JNI_TRUE;
    jb[3] = JNI_FALSE;
    jb[4] = JNI_TRUE;
    jbooleanArray jba = env->NewBooleanArray(5);
    env->SetBooleanArrayRegion(jba, 0, 5, jb);
    return jba;
}
//java->native
Java_com_ihubin_ndkjni_NativeUtil_formatArray(JNIEnv *env, jclass clazz, jintArray int_array) {
    jint array[5];
    env->GetIntArrayRegion(int_array, 0, 5, array);
    jsize size = env->GetArrayLength(int_array);
    char resutStr[100] = {0};
    char str[10] = {0};
    strcat(resutStr, "[");
    for(int i = 0; i < size; i++) {
        sprintf(str, "%d", array[i]);
        strcat(resutStr, str);
        if(i != size - 1) {
            strcat(resutStr, ", ");
        }
    }
    strcat(resutStr, "]");
    return env->NewStringUTF(resutStr);
}

2.2.2 对象

extern "C"
JNIEXPORT jobject JNICALL
Java_com_ihubin_ndkjni_NativeUtil_javaClassTest(JNIEnv *env, jclass clazz) {

    jclass userClass = env->FindClass("com/ihubin/ndkjni/User");
    //参数,返回值,jni签名为了支持函数重载 格式: (参数1类型标示;参数2类型标示;参数3类型标示...)返回值类型标示
    jfieldID normalField = env->GetFieldID(userClass, "normalField", "I");
    jfieldID staticField = env->GetStaticFieldID(userClass, "staticField", "I");
    jmethodID normalMethod = env->GetMethodID(userClass, "getNormalUserInfo", "()Ljava/lang/String;");
    jmethodID staticMethod = env->GetStaticMethodID(userClass, "getStaticUserInfo", "()Ljava/lang/String;");

    jmethodID voidInitMethod = env->GetMethodID(userClass, "<init>", "()V");
    jmethodID paramInitMethod = env->GetMethodID(userClass, "<init>", "(Ljava/lang/String;I)V");

    jmethodID getFormatInfoMethod = env->GetMethodID(userClass, "getFormatInfo", "()Ljava/lang/String;");

    jobject userOne = env->NewObject(userClass, voidInitMethod);

    jstring name = env->NewStringUTF("HUBIN");
    jint age = 8;
    jobject userTwo = env->NewObject(userClass, paramInitMethod, name, age);

    //通过一个类创建对象,默认构造函数
    jobject userThree = env->AllocObject(userClass);


    jint normalFieldValue = env->GetIntField(userOne, normalField);
    LOGD("normalField: %d", normalFieldValue);

    jint staticFieldValue = env->GetStaticIntField(userClass, staticField);
    LOGD("staticField: %d", staticFieldValue);
    //调用对象中的方法
    jobject normalMethodResultObj = env->CallObjectMethod(userOne, normalMethod);
    jstring normalMethodResult = static_cast<jstring>(normalMethodResultObj);
    const char *normalMethodResultNativeString = env->GetStringUTFChars(normalMethodResult, 0);
    LOGD("normalMethodResult: %s", normalMethodResultNativeString);

    jobject staticMethodResultObj = env->CallStaticObjectMethod(userClass, staticMethod);
    jstring staticMethodResult = static_cast<jstring>(staticMethodResultObj);
    const char *staticMethodResultNativeString = env->GetStringUTFChars(staticMethodResult, 0);
    LOGD("staticMethodResult: %s", staticMethodResultNativeString);

    jobject getFormatInfoMethodResultObj = env->CallObjectMethod(userTwo, getFormatInfoMethod);
    jstring getFormatInfoMethodResult = static_cast<jstring>(getFormatInfoMethodResultObj);
    const char *getFormatInfoMethodResultNativeString = env->GetStringUTFChars(getFormatInfoMethodResult, 0);
    LOGD("getFormatInfoMethodResult: %s", getFormatInfoMethodResultNativeString);
    jobject userThreeMethodResultObj = env->CallObjectMethod(userThree, normalMethod);
    jstring userThreeMethodResult = static_cast<jstring>(userThreeMethodResultObj);
    const char *userThreeMethodResultNativeString = env->GetStringUTFChars(userThreeMethodResult, 0);
    LOGD("userThreeMethodResult: %s", userThreeMethodResultNativeString);
    jstring ygName = env->NewStringUTF("老妖怪");
    jint ygAge = 999;
    jobject userYG = env->NewObject(userClass, paramInitMethod, ygName, ygAge);
    return userYG;
}

3 jni方法解析

## 3.1 JNIEXPORT JNICALL 宏定义

  • windows
#ifndef _JAVASOFT_JNI_MD_H_
#define _JAVASOFT_JNI_MD_H_
//windows如果需要生成动态库,并且需要将该动态库交给其他项目使用,需要再方法前加入特殊标识
//才能在外部程序代码中调用DLL动态库中定义的方法,返回值前加入__declspec(dllexport)
#define JNIEXPORT __declspec(dllexport)
#define JNIIMPORT __declspec(dllimport)
//__stdcall 是一种函数调用参数的约定,在windows中调用函数时,该函数参数是以栈的形式保存的
//栈元素是后进先出的,__stdcall表示参数从右到左保存的。
#define JNICALL __stdcall

typedef long jint;
typedef __int64 jlong;
typedef signed char jbyte;

#endif /* !_JAVASOFT_JNI_MD_H_ */
  • Linux
#define JNIIMPORT
//返回值前加 __attribute__ ((visibility ("default")))
//该声明的作用是保证在本动态库中声明的方法 , 能够在其他项目中可以被调用 ;
#define JNIEXPORT  __attribute__ ((visibility ("default")))
//没有进行定义 , 直接置空
#define JNICALL
extern "C"//表示 C 语言 和 C++ 的兼容 ;
JNIEXPORT jstring JNICALL
Java_kim_hsl_jni_MainActivity_stringFromJNI(
        JNIEnv *env,//JNIEnv *env 代表了 JNI 环境
        jobject /* this */) {

    // 创建 C++ 字符串
    std::string hello = "Hello from C++";
    // 返回 jstring 类型的字符串
    return env->NewStringUTF(hello.c_str());
}

3.2 JNI *env参数解析(JNIEnv *env 参数)

#if defined(__cplusplus)//__cplusplus 是 C++ 编译器中定义的宏
typedef _JNIEnv JNIEnv;//_JNIEnv 结构体类型声明为 JNIEnv 类型
typedef _JavaVM JavaVM;
#else
//JNINativeInterface 结构体指针 类型 声明为 JNIEnv 类型
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

3.2.1 c 语言环境中JNIEnv *env 参数解析

  1. typedef const struct JNINativeInterface* JNIEnv;
  2. JNINativeInterface结构体定义了229个函数
  3. 调用JNINativeInterface 结构体中的函数指针
    1. 类型转换 给定的参数是 JNIEnv *env 类型的 , 即 JNINativeInterface ** env 类型 , 是一个 JNINativeInterface 结构体边来那个的二级指针 ;
    2. 通过 JNINativeInterface 指针调用 ( 推荐) : 首先要 解引用 ( *env ) , 该变量就变成了 JNINativeInterface 结构体变量的 一级指针 , 使用 -> 即可调用指定方法 , ( *env ) -> NewStringUTF( “Hello World !” );
    3. 通过 JNINativeInterface 结构体调用 ( 不常用 ) : ( **env ).NewStringUTF( “Hello World !” ) , 相当于将上面的 -> 操作符变成 * . 操作符

3.2.3 c++ JNIEnv *env参数解析

  1. typedef const struct JNINativeInterface* JNIEnv;

  2. 通过 _JNIEnv 指针调用 ( 推荐) : 直接使用 -> 符号访问该方法即可 , env-> NewStringUTF( “Hello World !” );

  3. struct _JNIEnv {
        /* do not rename this; it does not seem to be entirely opaque */
        const struct JNINativeInterface* functions;
    #if defined(__cplusplus)
        jint GetVersion()
        { return functions->GetVersion(this); }
        
        jclass DefineClass(const char *name, jobject loader, const jbyte* buf,
            jsize bufLen)
        { return functions->DefineClass(this, name, loader, buf, bufLen); }
    ...
    }
    

3.3 基本数据类型,引用数据类型(数组)

3.3.1 jintArray类型定义

class _jobject {};						// 定义 _jobject 类 , 这是一个空类
class _jarray : public _jobject {};     // 定义 _jarray 类 继承 _jobject 类
class _jintArray : public _jarray {};	// 定义 _jintArray 类 , 继承 _jarray 类
typedef _jintArray*     jintArray;		// 定义 _jintArray* 别名 , jintArray

( jintArray -> jint * ) jintArray 是java环境中的int数组的内存地址,jint* GetIntArrayElements(jintArray array, jboolean* isCopy) 方法 , 可以实现上述转化 ;

3.3.2 jboolean

typedef unsigned char __uint8_t;	// 定义 char 类型别名 __uint8_t
typedef __uint8_t	uint8_t;		// 定义 __uint8_t 类型别名 uint8_t
typedef uint8_t  jboolean;			// 定义 uint8_t 类型别名 jboolean

3.3.3 GetIntArrayElements

  • 将java环境中的int数组类型变量(jintArray),转为c/c++环境中的jint数组指针,返回一个指针指向jint数组首元素地址

    jnit本质就是int类型 GetIntArrayElements函数作用就是将jntArray转为int* 指针

  • struct _JNIEnv {
        /* _JNIEnv  结构体中封装了 JNINativeInterface 结构体指针 */
        const struct JNINativeInterface* functions;
        ...
        jint* GetIntArrayElements(jintArray array, jboolean* isCopy)
        { 
        	// 调用 JNINativeInterface 结构体中封装的 GetIntArrayElements 方法
            //isCopy 指向JNI_TRUE 将int数组拷贝到一个新的内存空间中,并将该内存空间首地址返回
            //           JNI_FALSE 直接使用java中int数组地址,返回java中的int数组的首地址
            //NULL(推荐)不关心如何实现让系统自动选择指针生成方式。
        	return functions->GetIntArrayElements(this, array, isCopy); 
        }
        ...
    }
    

3.3.4 GetArrayLength

//获取 jarray 数组长度 , 该 jarray 类型可以是下面定义的类型 
typedef _jarray*        jarray;

//下面是 9 个是 Java 传入的数组类型别名
typedef _jobjectArray*  jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray*    jbyteArray;
typedef _jcharArray*    jcharArray;
typedef _jshortArray*   jshortArray;
typedef _jintArray*     jintArray;
typedef _jlongArray*    jlongArray;
typedef _jfloatArray*   jfloatArray;
typedef _jdoubleArray*  jdoubleArray;

//GetArrayLength 函数原型
struct _JNIEnv {
    /* _JNIEnv  结构体中封装了 JNINativeInterface 结构体指针 */
    const struct JNINativeInterface* functions;
    ...
    jsize GetArrayLength(jarray array)
    { 
    	 调用 JNINativeInterface 结构体中封装的 GetArrayLength 方法
    	return functions->GetArrayLength(this, array); 
    }
    ...
}

3.3.5 日志打印

  • 导入日志库 #include <android/log>

  • CMake设置日志库

    # 设置动态库名称
    add_library( 
            native-lib
            SHARED
            native-lib.cpp)
    # 查找日志库		
    find_library( 
            log-lib
            log)
    # 	连接日志库	
    target_link_libraries( 
            native-lib
            ${log-lib})
    
  • 日志打印函数原型

    int __android_log_print(int prio, const char* tag, const char* fmt, ...)
    //调用
    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
    //推荐
    #define LOG_TAG "C_TAG"
    #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
    #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO  , LOG_TAG, __VA_ARGS__)
    #define LOGW(...) __android_log_print(ANDROID_LOG_WARN  , LOG_TAG, __VA_ARGS__)
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR  , LOG_TAG, __VA_ARGS__)
    
    

    3.3.6 ReleaseIntArrayElements(释放c/c++ 中的int数组)

    struct _JNIEnv {
        /* _JNIEnv  结构体中封装了 JNINativeInterface 结构体指针 */
        const struct JNINativeInterface* functions;
        ...
        void ReleaseIntArrayElements(jintArray array, jint* elems,
            jint mode)
        //调用的是 JNINativeInterface 结构体中封装的 ReleaseIntArrayElements 方法
        { functions->ReleaseIntArrayElements(this, array, elems, mode); }
        ...
    }
    

    ① 模式 0 : 刷新 Java 数组 , 释放 C/C++ 数组
    ② 模式 1 ( JNI_COMMIT ) : 刷新 Java 数组 , 不释放 C/C ++ 数组
    ③ 模式 2 ( JNI_ABORT ) : 不刷新 Java 数组 , 释放 C/C++ 数组

    3.3.6 代码示例

    #include <jni.h>
    #include <string>
    
    //导入日志库
    #include <android/log.h>
    
    //定义日志宏 , 其中的 __VA_ARGS__ 表示可变参数
    #define  LOGE(...) __android_log_print(ANDROID_LOG_ERROR,"JNI",__VA_ARGS__);
    
    extern "C"
    JNIEXPORT jstring JNICALL
    Java_kim_hsl_jni_MainActivity_stringFromJNI(
            JNIEnv *env,
            jobject /* this */) {
    
        // 创建 C++ 字符串
        std::string hello = "Hello from C++";
    
        // 返回 jstring 类型的字符串
        //  将 C/C++ 的 char* 字符串转为 Java 中的 jstring 类型字符串
        return env->NewStringUTF(hello.c_str());
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_kim_hsl_jni_MainActivity_jniTest(JNIEnv *env, jobject instance, jint i, jstring s_) {
    
        // 将 jstring 类型数据转为 char 类型数据
        const char *s = env->GetStringUTFChars(s_, 0);
    
        // 释放
        env->ReleaseStringUTFChars(s_, s);
    }
    
    extern "C"
    JNIEXPORT void JNICALL
    Java_kim_hsl_jni_MainActivity_jniArrayTest(JNIEnv *env, jobject instance, jintArray intArray_,
                                               jobjectArray stringArray) {
    
        // I . 基本类型数组操作
    
    
        // 1 . jboolean 类型
    
        /*
            jboolean 类型的值可以设置成 true 或 false , 也可以不设置
            如果将值传递给 GetIntArrayElements 方法 , 需要将 isCopy 的地址放在第二个参数位置
            当做参数的格式 : env->GetIntArrayElements(intArray_, &isCopy);
            可取值 JNI_FALSE 0 和 JNI_TRUE 1 两个值
         */
        jboolean isCopy = JNI_TRUE;
    
        //2 . GetIntArrayElements 方法参数解析
        /*
            GetIntArrayElements 方法参数解析
                方法作用 : 将 Java 的 int 数组 , 转为 jint 数组 , 返回一个指针指向 jint 数组首元素地址
    
            函数原型 : jint* GetIntArrayElements(jintArray array, jboolean* isCopy)
    
            第一个参数 : jintArray array 是参数中的 jintArray 类型变量
    
                jintArray 类型说明 :
                    class _jobject {};                      C ++ 中定义了 _jobject 类
                    class _jarray : public _jobject {};     定义 _jarray 类 继承 _jobject 类
                                                            public 继承 : 父类成员在子类中访问级别不变
                    class _jintArray : public _jarray {};   定义 _jintArray 类 继承 _jarray 类
                    typedef _jintArray*     jintArray;      将 _jintArray* 类型 声明成 jintArray 类型
    
            第二个参数 : jboolean* isCopy
    
                该参数用于指定将 jintArray 类型的变量 , 转为 jint * 指针类型的变量 , 新的指针变量的生成方式
    
                将 该参数设置成指向 JNI_TRUE 的指针 : 将 int 数组数据拷贝到一个新的内存空间中 , 并将该内存空间首地址返回
                将 该参数设置成指向 JNI_FALSE 的指针 : 直接使用 java 中的 int 数组地址 , 返回 java 中的 int 数组的首地址
                将 该参数设置成 NULL ( 推荐 ) : 表示不关心如何实现 , 让系统自动选择指针生成方式 , 一般情况下都不关心该生成方式
    
    
            注意如果是 其它类型的数组
    
                如果是布尔类型的数组 , 使用 GetBooleanArrayElements 方法
                如果是浮点型的数组 , 使用 GetFloatArrayElements 方法
                如果是字符型的数组 , 使用 GetCharArrayElements 方法
                ...
    
         */
    
        jint *intArray = env->GetIntArrayElements(intArray_, NULL);
        //注意区别
        //jint array[5];
        //env->GetIntArrayRegion(int_array, 0, 5, array)
    
        //3 . 操作 jint * 指针变量 , 循环获取数组中每个元素的值
    
        /*
           获取数组长度
             函数原型 : jsize GetArrayLength(jarray array)
    
             返回值类型 jsize :
                jsize 类型 : 由下面可知 jsize 只是 int 类型的别名
                    typedef jint            jsize;
                    typedef int32_t         jint;
                    typedef __int32_t       int32_t;
                    typedef int             __int32_t;
    
         */
        jsize len = env->GetArrayLength(intArray_);
    
        //4 . 循环打印 int 数组中的元素
    
        /*
            使用指针进行访问
            intArray 是数组首元素地址
            intArray + 1 是第 1 个元素的首地址
            intArray + k 是第 k 个元素的首地址
    
            使用 *(intArray + k) 可以获取第 k 个元素的值
         */
        for(int i = 0; i < len; i ++){
    
            //获取第 i 个元素的首地址 , 使用 *num 可以获取第 i 个元素的值
            int *num = intArray + i;
    
            /*
                __android_log_print 打印 Android 日志函数
                    函数原型 : int __android_log_print(int prio, const char* tag, const char* fmt, ...)
    
                    int prio 参数 : 日志的等级 , 定义在 jni.h 的 android_LogPriority 枚举中
                                    ANDROID_LOG_VERBOSE
                                    ANDROID_LOG_DEBUG
                                    ANDROID_LOG_INFO
                                    ANDROID_LOG_WARN
                                    ANDROID_LOG_ERROR
    
                    const char* tag 参数 : 日志打印的 TAG 标签 , 这是一个 C/C++ char* 类型字符串
    
                    const char* fmt, ... 参数 : 可变参数
             */
            __android_log_print(ANDROID_LOG_INFO, "JNI_TAG" , "%d . %d" , i , *num);
    
            //修改数组中的值
            *num = 8888;
    
        }
    
        //5 . 释放 jint * 类型的指针变量
    
        /*
    
            函数原型 : void ReleaseIntArrayElements(jintArray array, jint* elems, jint mode)
    
            第一参数 jintArray array : 是 Java 层传入的 int 数组 参数 , 即 Native 层的调用函数的参数
            第二参数 jint* elems : 通过 GetIntArrayElements 方法将 jintArray 变量转成的 jint* 变量
    
            第三参数 jint mode : 设置处理模式 , 有三种处理模式
    
                模式 0 :                  刷新 Java 数组 , 释放 C/C++ 数组
                模式 1 ( JNI_COMMIT ) :   刷新 Java 数组 , 不释放 C/C ++ 数组
                模式 2 ( JNI_ABORT ) :   不刷新 Java 数组 , 释放 C/C++ 数组
    
            下面是 jni.h 中的定义的模式 :
            #define JNI_COMMIT      1            copy content, do not free buffer
            #define JNI_ABORT       2            free buffer w/o copying back
    
            如果设置 0 和 1 , 那么 如果修改了 int 数组的值 , 那么最终 Java 层的值会被修改
            如果设置 2 , 那么 如果修改了 int 数组的值 , 那么最终 Java 层的值不会被修改
    
         */
        env->ReleaseIntArrayElements(intArray_, intArray, 0);
    
    }
    

    GetIntArrayElements vs GetArrayRegion

    • GetIntArrayRegion 把java数组拷贝到c++新建数组内存区域中

      • c++ 不会改变java中的数值
      • 不用拷贝占用额外的内存
    • GetIntArrayElements把c++的一个数组指针指向java的数组,这样c++ 修改数组后java数组也会改变

      • 要想使GetIntArrayElements让java层收到,还需要释放掉c++指针,否则会造成内存泄漏

3.4 c/c++调用java方法

extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_jni_MainActivity_jniObjectTest(JNIEnv *env, jobject instance, jobject student) {

    /*
        参数解析 :
        JNIEnv *env : JNI 环境 , 结构体指针 , 结构体中封装了 209 个方法
        jobject instance : 是 MainActivity 对象
        jobject student : Java 层创建的 Student 对象 , 传入 Native 层
     */

    //在 C/C++ 中调用 Student 对象的 get 方法

    //1 . 获取 Java 对应的 Class 对象
    jclass student_class = env->GetObjectClass(student);

    //2 . 通过 Class 的反射获取要调用的方法

    /*
        函数原型 :
            jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
            { return functions->GetMethodID(this, clazz, name, sig); }

        参数说明 :
            jclass clazz : 使用 GetObjectClass 方法获取的返回值
            const char* name : 要调用的方法名称
            const char* sig : 函数签名 , 具体的签名规则查看签名表格

        public int getAge() 函数签名 : ()I
            左侧的 () : 左侧的括号是参数列表类型签名
            右侧的 I : 括号右的 I 是返回值类型 int

        public void setAge(int age) 函数签名 : (I)V
            (I) 表示左侧的参数列表
            右侧的 V 表示其返回值类型是 void 类型

        引用类型签名 : L + 全限定名 + ;


        javap 工具 :
            可以使用 javap 工具获取方法签名

     */

    //获取 Student 的 public int getAge() 方法
    jmethodID method_getAge = env->GetMethodID(student_class, "getAge" , "()I");

    //获取 Student 的 public void setAge(int age) 方法
    jmethodID method_setAge = env->GetMethodID(student_class, "setAge" , "(I)V");

    //获取 Student 的 public static void logInfo(String info) 方法
    //  注意这里要使用 GetStaticMethodID 方法反射该静态方法
    jmethodID method_logInfo = env->GetStaticMethodID(student_class, "logInfo" , "(Ljava/lang/String;)V");



    //3 . 调用 Java 对象的方法

    /*
        调用 Java 引用对象的方法 : 要根据 返回值类型不同 , 调用不同的方法

            如果返回值是 int 类型 , 那么就需要调用 CallIntMethod 方法
            如果返回值是 void 类型 , 那么就需要调用 CallVoidMethod 方法

            如果调用的是静态方法 , 那么需要调用

        ( 注意 : 调用方法时传入的参数都必须是 C/C++ 中的 Java 类型参数 , 如 jint , jstring 等 )

     */

    //调用 Student 对象的 public void setAge(int age) 方法
    env->CallVoidMethod(student, method_setAge, 18);

    //调用 Student 的 public int getAge() 方法
    jint age = env->CallIntMethod(student, method_getAge);


    /*
        调用静态方法 :
            1 . 创建 Java 字符串
            2 . 调用静态方法
            3 . 释放 Java 字符串
     */

    // 创建 Java 字符串
    jstring info = env->NewStringUTF("C/C++ 创建的 Java 字符串");

    // 调用静态方法 : 注意传入的参数
    env->CallStaticVoidMethod(student_class, method_logInfo, info);

    // jstring info 在方法中创建新的字符串 , 需要在方法结束之前释放该引用对象
    env->DeleteLocalRef(info);


    //4 . 设置 Student 对象属性

    /*
        反射获取属性

        函数原型 :
            jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
            { return functions->GetFieldID(this, clazz, name, sig); }

        参数说明 :
            jclass clazz : Java 类对象
            const char* name : 属性名称
            const char* sig : 属性类型签名

        设置反射属性值 :

        函数原型 :
            void SetIntField(jobject obj, jfieldID fieldID, jint value)
            { functions->SetIntField(this, obj, fieldID, value); }

        参数说明 :
            jobject obj : 设置对象
            jfieldID fieldID : 通过 GetFieldID 方法获取的属性 ID
            jint value : 要设置的值

        注意 : 设置不同类型的值 , 调用不同的设置方法

     */
    jfieldID age_field_id = env->GetFieldID(student_class, "age", "I");
    env->SetIntField(student, age_field_id, 90);

    // 验证是否设置成功
    age = env->CallIntMethod(student, method_getAge);


    //5 . 在 JNI 中创建 java 对象 , 并设置给另一个对象

    /*
        获取 Teacher 类 : 通过调用 FindClass 方法获取 Teacher 类
            目前已知两种获取 jclass 的方法


        获取 Teacher 类的构造方法 public Student(int age, String name)
            构造方法的方法名都是 "<init>"
            构造方法的函数签名为

        此处还要特别注意 : 传入到 Java 方法中的参数 , 必须都是 Java 参数类型
            如 jstring , jint , jintArray 等类型 , 不能将 C/C++ 类型传入参数
            尤其是 char* 字符串 , 需要使用 NewStringUTF 将 C/C++ 字符串转为 jstring 类型字符串

        创建 Teacher 对象

        将 Teacher 对象设置给 Student 对象

     */

    // 5.1 获取 Student 的 public void setTeacher(Teacher teacher) 方法
    //  注意这个 Teacher 的类型签名是 Lkim/hsl/jni/Teacher;
    jmethodID method_setTeacher = env->GetMethodID(student_class, "setTeacher" , "(Lkim/hsl/jni/Teacher;)V");

    LOGE("method_setTeacher");

    // 5.2 获取 Teacher 类 ( 该变量需要释放 )
    jclass class_teacher = env->FindClass("kim/hsl/jni/Teacher");

    // 5.3 查找构造方法
    jmethodID method_init = env->GetMethodID(class_teacher, "<init>", "(ILjava/lang/String;)V");


    // 5.4 准备 Java 类型参数 ( 该变量需要释放 )
    //     此处特别注意 : 传入到 Java 方法中的参数都必须是 Java 参数
    jint teacher_age = 88;
    jstring teacher_name = env->NewStringUTF("Tom Wang");

    // 5.5 创建 Teacher 对象 ( 该变量需要释放 )
    jobject teacher = env->NewObject(class_teacher, method_init, teacher_age, teacher_name);

    // 5.6 调用 Student 对象的 setTeacher 设置方法
    env->CallVoidMethod(student, method_setTeacher, teacher);

    // 5.7 释放上面通过 FindClass NewStringUTF NewObject 创建的引用变量 , 便于节省内存 , 也可以等到 作用域结束 自动释放
    //     使用完这三个引用之后 , 不再使用 ; 这里特别建议手动释放三个引用
    //     如果不手动释放 , 在 该引用 作用域 结束后 , 也会自动释放掉
    env->DeleteLocalRef(teacher_name);
    env->DeleteLocalRef(teacher);
    env->DeleteLocalRef(class_teacher);
}

3.5 jni引用(局部引用,全局引用)

  • 局部引用
extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_jni_MainActivity_jniLocalReferenceTest(JNIEnv *env, jobject instance) {


    /*
        局部引用
            局部引用只能在当前作用域有效
                超出作用域
                手动释放
            上面 两种情况 都会导致 该局部变量都会失效


        局部引用作用范围 :
                空间 : 不能 跨线程 , 跨方法调用 , 仅在本作用域有效
                时间 : 创建后可以使用 , 手动释放 或 作用域结束 引用被释放不可使用

        局部引用 创建 : 使用 NewXXX / FindXXX 等 大多数 JNI 方法 默认创建的都是局部引用
                释放 : 调用 DeleteLocalRef 方法 释放该局部引用


        关于上面的三个创建的 局部引用 有两种释放方式
            方式一 : 在方法作用域结束后 , VM 自动释放上述变量
            方式二 : 通过调用 DeleteLocalRef 方法手动释放

        建议使用方式二 :
            局部引用 释放尽量灵活 , 不要等待自动释放 , 在使用完毕后 建议就手动释放 , 今早回收内存
            如果该 引用 一直到最后都要使用 , 那么可以不进行手动释放 ;

            建议用法 : 局部引用建议都要手动释放 , 哪怕是在作用域最后 , 也要进行手动释放

        局部引用传递到 Java 层 , 该传递是拷贝传递 , JNI 中该释放还是释放 , 不影响 Java 层使用


        引用概念 :
            这里要将 引用 和 指针的概区分清楚 ;
            class_teacher 引用在 作用域结束时 会被释放 , 不能将其用于 JNI 反射 Java 类的方法和字段
                          其指针值不为空 , 仍然有值 , 其仍然指向一个地址 , 但是地址中的数据被释放了

     */


    // 1 . 获取 Teacher 类 ( 该变量需要释放 )
    jclass class_teacher = env->FindClass("kim/hsl/jni/Teacher");

    // 2 .  查找构造方法
    jmethodID method_init = env->GetMethodID(class_teacher, "<init>", "(ILjava/lang/String;)V");


    // 3 . 准备 Java 类型参数 ( 该变量需要释放 )
    //     此处特别注意 : 传入到 Java 方法中的参数都必须是 Java 参数
    jint teacher_age = 88;
    jstring teacher_name = env->NewStringUTF("Tom Wang");

    // 4 .  创建 Teacher 对象 ( 该变量需要释放 )
    jobject teacher = env->NewObject(class_teacher, method_init, teacher_age, teacher_name);

    // 5 .  释放上面通过 FindClass NewStringUTF NewObject 创建的引用变量 , 否则会造成内存泄漏
    //     使用完这三个引用之后 , 不再使用 ; 这里特别建议手动释放三个引用
    //     如果不手动释放 , 在 该引用 作用域 结束后 , 也会自动释放掉
    env->DeleteLocalRef(teacher_name);
    env->DeleteLocalRef(teacher);
    env->DeleteLocalRef(class_teacher);
}
  • 全局引用
// 全局引用
//  访问时如果局部变量也有同名变量 , 可以使用 域作用符 访问
//  ::class_teacher 表示访问全局的变量
jclass class_teacher_global;

extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_jni_MainActivity_jniGlobalReferenceTest(JNIEnv *env, jobject instance) {

    /*
        全局引用 作用域 :
                空间 : 可以 跨方法 , 跨线程使用
                时间 : 创建后可以使用 , 手动释放后全局引用失效

        全局引用创建 : NewGlobalRef

        全局引用释放 : DeleteGlobalRef

        全局引用会阻止 JVM 回收该引用


        这里注意域作用符的使用 , 本方法中没有 class_teacher_global 同名变量 , :: 可用 可 不用
     */

    // 1 . 获取 Teacher 类 ( 该变量需要释放 )
    if(::class_teacher_global == NULL) {

        //生成局部引用 , 该局部引用使用完毕后可释放
        jclass tmp_class = env->FindClass("kim/hsl/jni/Teacher");

        //将上述生成的局部引用变成 全局引用
        //      全局引用释放时 , env->DeleteGlobalRef(class_teacher_global) 即可释放下面转换的 全局引用
        ::class_teacher_global = static_cast<jclass>(env->NewGlobalRef(tmp_class));

        //将局部引用释放掉
        env->DeleteLocalRef(tmp_class);

    }
}


// 弱全局引用
//  访问时如果局部变量也有同名变量 , 可以使用 域作用符 访问
//  ::class_teacher_weak_global 表示访问全局的变量
jclass class_teacher_weak_global;

extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_jni_MainActivity_jniWeakGlobalReferenceTest(JNIEnv *env, jobject instance) {

    /*
        弱全局引用

        弱全局引用 与 Java 引用类似

        弱全局引用 作用域 :
            空间 : 都可以 跨方法 , 跨线程使用
            时间 : 创建弱全局引用后可以开始使用 , JVM 自动回收 或 手动释放 该弱全局引用不可用

        弱全局引用 与 全局引用 区别 :
            全局引用 : 不能被 回收 , 如果内存不足就 抛出异常
            弱全局引用 : 当内存不足时 , 会被系统自动回收

        弱全局引用 判定是否被回收 :
            使用 IsSameObject(弱引用变量 , NULL) 判断该对象是否被回收了 , 将其与 NULL 比较 , 即可判定
                返回 true ( 该对象与NULL相等 ) : 该若全局引用已经被释放
                返回 false ( 该对象与NULL不相等 ) : 还可以使用



     */

    // 1 . 获取 Teacher 类 ( 该变量需要释放 )

    //如果 class_teacher_weak_global 对象被回收 , 返回 true ; 没有被回收返回 false ;
    jboolean isClassReleased = env->IsSameObject(class_teacher_weak_global, NULL);

    if( class_teacher_weak_global == NULL || isClassReleased ) {

        //生成局部引用 , 该局部引用使用完毕后可释放
        jclass tmp_class = env->FindClass("kim/hsl/jni/Teacher");

        //将上述生成的局部引用变成弱全局引用
        //      弱全局引用释放时 , env->DeleteWeakGlobalRef(class_teacher_weak_global) 即可释放下面转换的弱全局引用
        class_teacher_weak_global = static_cast<jclass>(env->NewWeakGlobalRef(tmp_class));

        //将局部引用释放掉
        env->DeleteLocalRef(tmp_class);

    }

}

3.6 动态注册

package kim.hsl.onload;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        dynamicRegisterJavaMethod();
        dynamicRegisterJavaMethod2(250);

    }

    /**
     * 动态注册 JNI 方法 , Java 方法
     */
    public native void dynamicRegisterJavaMethod();
    public native int dynamicRegisterJavaMethod2(int i);
}
#include <jni.h>
#include <string>
#include <android/log.h>


/*

    I . JNI_Onload 方法


    JNI_Onload 方法在 Java 层执行 System.loadLibrary("native-lib") 代码时调用的方法
        主要是执行一些 JNI 初始化操作

    JNI_Onload 常见操作 :
        ① 保存 JavaVM 对象 , 使用全局变量记录该 Java 虚拟机对象
        ② 动态注册 : 动态注册是该方法中最常见的操作


    JNI_Onload 参数说明 :
        JavaVM *vm : 代表了 Java 虚拟机
        void *r : 一般是 NULL

    JNI_Onload 返回值说明 :
        int 类型返回值代表了当前 NDK 使用的 JNI 版本
        JNI 版本 中可选的有四个值 , 但是只能选择返回后三个 JNI_VERSION_1_2 , JNI_VERSION_1_4 , JNI_VERSION_1_6
            返回上述三个值任意一个没有区别
            返回 JNI_VERSION_1_1 会报错

            #define JNI_VERSION_1_1 0x00010001
            #define JNI_VERSION_1_2 0x00010002
            #define JNI_VERSION_1_4 0x00010004
            #define JNI_VERSION_1_6 0x00010006

            这四个值分别代表了 JDK 1.1 , 1.2 , 1.4 , 1.6 对应的 JNI 版本


    II . 动态注册

    动态注册 :
        动态注册与静态注册 :
            静态注册 : 使用 Java_包名_类名_方法名(JNIEnv* env, jobject obj, ...) 方式进行注册是静态注册
            动态注册 : 将 C/C++ 中的本地方法 与 Java 中的方法对应起来 , 就需要使用动态注册

        动态注册 与 静态注册 : 没有太大区别 , 都可以将 C/C++ 本地方法 与 Java 方法对应起来

    动态注册流程 :
        ① 声明 Java 层 Native 方法
        ② 准备数据 JNINativeMethod methods[] 数组
        ③ 编写 JNI_OnLoad 方法
        ④ 获取 JNIEnv 指针
        ⑤ 获取 Java 类
        ⑥ 进行动态注册


 */

//使用 全局变量 记录 Java 虚拟机对象
JavaVM *_vm;

/*

    动态注册对应的 C/C++ 本地方法

    如果动态注册的方法需要传递参数 , 需要加上 前面的 JNIEnv *env, jobject obj 两个参数

    如果不传递参数 , 就可以不添加任何参数

    不传递参数 , 参数可以空着

 */
void dynamicRegisterCMethod(){

    __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "dynamicRegisterCMethod");

}

/*

    动态注册对应的 C/C++ 本地方法

    如果动态注册的方法需要传递参数 , 需要加上 前面的 JNIEnv *env, jobject obj 两个参数

    如果不传递参数 , 就可以不添加任何参数

    传递参数 , 那么需要写上 JNI 调用的完整参数
 */
jint dynamicRegisterCMethod2(JNIEnv *env, jobject obj, jint i){

    __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "dynamicRegisterCMethod2 : %d", i);

    return i + 1;
}


/*
    该数组中 , 每个元素都是一个 JNI 的 Native 方法

    JNINativeMethod 是结构体

        typedef struct {
            const char* name;       //Java 中定义的 Native 方法名 , 注意这是一个 C 字符串
            const char* signature;  //函数签名 , 可以使用 javap 生成
            void*       fnPtr;      //C/C++ 中的 Native 函数指针
        } JNINativeMethod;

 */
static const JNINativeMethod methods[] = {
        {"dynamicRegisterJavaMethod", "()V", (void *)dynamicRegisterCMethod},
        {"dynamicRegisterJavaMethod2", "(I)I", (void *)dynamicRegisterCMethod2}
};

/*
    动态注册的 Java 类名称
        注意 : 包名类名之间使用 "/" 分割
 */
static const char* className = "kim/hsl/onload/MainActivity";


int JNI_OnLoad(JavaVM *vm , void* reserved){

    __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "JNI_Onload");

    //I . 存储 Java 虚拟机对象

    //将 Java 虚拟机对象记录到全局变量中
    _vm = vm;



    //II . 动态注册

    //1 . 获取 JNIEnv JNI 环境 , 需要从 JavaVM 获取
    JNIEnv *env = nullptr;

    //2 . 调用 JavaVM / _JavaVM 结构体的 jint GetEnv(void** env, jint version) 方法
    //      返回值分析 : 动态注册会返回一个结果
    //          如果 registerResult < 0 , 则动态注册失败
    //          如果 registerResult == 0 , 则动态注册失败
    int registerResult = vm->GetEnv( (void **) &env, JNI_VERSION_1_6 );

    //3 . 判断结果 : 如果动态注册 Native 方法失败 , 直接退出
    if(registerResult != JNI_OK){
        return -1;
    }

    //4 . 获取要动态注册的 Java 类的 Class 对象
    jclass jclazz = env->FindClass(className);

    /*
      5 .正式注册

            注册方法解析 :

            jint RegisterNatives(
                jclass clazz,                   //要注册的 Java 类
                const JNINativeMethod* methods, //JNI注册方法数组
                jint nMethods                   //要注册的 JNI 方法个数
            )


            sizeof(methods) / sizeof(JNINativeMethod) : 计算 JNINativeMethod methods[] 数组大小

     */
    env->RegisterNatives(jclazz, methods, sizeof(methods) / sizeof(JNINativeMethod));
    
    return JNI_VERSION_1_6;
}

3.7 jni 线程创建

package kim.hsl.thread;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        threadDemoJava();
    }

    /**
     * JNI 线程 Demo
     */
    public native void threadDemoJava();

    /**
     * 打印当前线程信息
     */
    public void logThread(){
        Log.i("JNI_TAG", Thread.currentThread().toString());
    }
}
#include <jni.h>
#include <string>
#include <android/log.h>

//导入线程头文件
#include <pthread.h>

//Java 虚拟机指针 , 在 JNI_OnLoad 方法中设置该值
JavaVM *_vm;

//JNI 方法参数中的第二个参数 , 需要先将局部变量转为全局变量 , 然后再其它方法中调用
jobject obj;

/*
    线程执行的方法

    如果在 Native 层执行耗时操作 , 如下载文件 , 需要在线程中处理

    JNI 方法参数中的 JNIEnv 指针是不能跨线程使用的 , 在 主线程中调用 JNI 方法 , 其 JNIEnv 指针不能在子线程中使用

    如果在子线程中使用 JNIEnv 指针 , 需要使用 JavaVM 获取 指定线程的 JNIEnv 指针
        调用 JavaVM 的 AttachCurrentThread 可以获取本线程的 JNIEnv 指针
        注意最后还要将线程从 Java 虚拟机中剥离


    关于参数传递 :
        传递 int 类型  和 int * 类型 , 传递指针可以在 方法中修改 int 变量值 ;
        传递 int * 类型 和 int ** 类型 , 传递二维指针 可以在方法中修改 int * 一维指针值
        因此有些参数需要在方法中修改, 并且需要保存该修改状态 , 就需要将该变量的地址当做参数传入
            原来的普通变量 变成 指针变量 , 一维指针 变 二维指针
*/
void* threadRun(void *args){

    __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "threadRun");

    //JNIEnv 不能跨线程使用 , 这里需要先获取本线程的 JNIEnv
    JNIEnv *env;

    //将线程附加到 Java 虚拟机中 ( 注意后面对应剥离线程操作 )
    //  如果成功返回 0 , 如果失败 , 直接退出
    int attachResult = _vm->AttachCurrentThread(&env, 0);

    //获取 MainActivity 对应的 jclass 对象
    jclass clazz = env->GetObjectClass( obj );

    //反射获取 logThread 方法
    jmethodID logThreadID = env->GetMethodID(clazz, "logThread", "()V");

    //调用 logThread 方法
    env->CallVoidMethod(obj, logThreadID);

    //释放相关的局部变量
    env->DeleteLocalRef(clazz);


    //将线程从 Java 虚拟机中剥离
    _vm->DetachCurrentThread();

    //注意这里一定要返回 0 , 否则执行到结尾会崩溃
    return 0;

}

void threadDemoC(JNIEnv *env, jobject instance){

    __android_log_print(ANDROID_LOG_INFO, "JNI_TAG", "threadDemoC");

    //保存全局变量 , 先将局部变量转为全局变量 , 然后再赋值给全局的 obj 变量
    //  使用域作用符访问全局的 ::obj 变量
    ::obj = env->NewGlobalRef(instance);

    //代表一个线程的句柄
    pthread_t pid;

    //创建线程并执行
    pthread_create( &pid , 0 , threadRun, 0 );
}


//下面的代码是动态注册内容

static const JNINativeMethod methods[] = {
        {"threadDemoJava", "()V", (void *)threadDemoC}
};

static const char* className = "kim/hsl/thread/MainActivity";

int JNI_OnLoad(JavaVM *vm , void* reserved){

    // 1 . 记录 Java 虚拟机指针
    _vm = vm;


    // 2 . 动态注册方法

    //获取 JNIEnv 指针
    JNIEnv *env = nullptr;
    int registerResult = vm->GetEnv( (void **) &env, JNI_VERSION_1_6 );
    if(registerResult != JNI_OK){
        return -1;
    }

    //进行动态注册
    jclass jclazz = env->FindClass(className);
    env->RegisterNatives(jclazz, methods, sizeof(methods) / sizeof(JNINativeMethod));

    return JNI_VERSION_1_6;
}

附录

https://upload-images.jianshu.io/upload_images/5713484-489382d33286e74e.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android JNI学习路线可以按照以下步骤进行: 1. 了解JNI的基本概念和作用:JNIJava Native Interface)是Java提供的一种机制,用于实现Java与其他编程语言(如C、C++)之间的交互。它允许在Java代码中调用本地代码(Native Code),并且可以在本地代码中调用Java代码。 2. 学习JNI的基本语法和规则:JNI使用一组特定的函数和数据类型来实现Java与本地代码之间的交互。你需要学习如何声明本地方法、如何在Java代码中调用本地方法、如何在本地代码中调用Java方法等。 3. 学习JNI的数据类型映射:JNI提供了一套数据类型映射规则,用于将Java数据类型映射到本地代码中的数据类型。你需要学习如何处理基本数据类型、对象类型、数组类型等。 4. 学习JNI的异常处理:在JNI中,Java代码和本地代码之间的异常处理是非常重要的。你需要学习如何在本地代码中抛出异常、如何在Java代码中捕获异常等。 5. 学习JNI的线程处理:JNI允许在本地代码中创建和操作线程。你需要学习如何创建和销毁线程、如何在线程之间进行通信等。 6. 学习JNI的性能优化:JNI涉及到Java代码和本地代码之间的频繁切换,因此性能优化是非常重要的。你需要学习如何减少JNI调用的次数、如何避免不必要的数据拷贝等。 7. 学习JNI的调试和测试:在开发JNI程序时,调试和测试是非常重要的。你需要学习如何使用调试器调试本地代码、如何进行单元测试等。 8. 学习JNI的进阶主题:一旦掌握了基本的JNI知识,你可以进一步学习JNI的进阶主题,如JNIJava虚拟机的交互、JNI与动态链接库的交互、JNI与多线程的交互等。 总结起来,Android JNI学习路线包括了基本概念、基本语法、数据类型映射、异常处理、线程处理、性能优化、调试和测试以及进阶主题等内容。通过系统地学习这些知识,你将能够更好地理解和应用JNI技术。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值