JNI进阶篇一

前言

不知不觉毕业快 2 年了,经历过了太多的事,许多都发生了改变,唯有爱你的心不变,始终坚持,不知未来何如 . . .

JNI

由于项目当中需要与底层(C&C++)交互,所以和你们一起来学习 JNI 的相关知识。

直接开撸 . . .

有关 NDK 的开发环境搭建,强力推荐以下链接参考:

Android NDK 开发(五)AndroidStudio 2.2 NDK的开发环境搭建

JNI案列

获取字符串

Java 层原型方法:

    //获取 native 字符串
    public native String getNDKString();

Native 层该方法实现 :

extern "C" //不能省略
JNIEXPORT jstring JNICALL
Java_com_github_asdemo_MainActivity_getNDKString(JNIEnv *env, jobject instance) {

    // TODO
    jstring str = env->NewStringUTF("hello word");

    return str;
}

注意 extern "C" 不能省略。自动生成 Native 层的方法:

JNI进阶篇一_第1张图片
native

我们来观察下 Native 层的方法体:

JNIEXPORT jstring JNICALL
Java_com_github_asdemo_MainActivity_getNDKString(JNIEnv *env, jobject instance)

JNIEXPORT 类似 Java 中的关键字,jstring 是返回值的类型,跟 JavaString 多了 j,接下来会具体看看 Native 层有哪些返回值的类型。JNICALL 关键字后面跟着方法名 Java_com_github_asdemo_MainActivity_getNDKStringNative 层的方法规范如下:

方法名 = Java_ + 包名 + 当前调用的Class名 + Java层的方法名

JNI的数据类型

家喻户晓,Java 存在两种数据类型:基本类型,引用类型。那么 JNI 也有两种数据类型:

  • primitive types (基本数据类型)如 jint,jbyte,jlong,jfloat 等

  • reference types (引用类型)如 jstring,类,数组 等

1、primitive type 映射表如下:

JNI进阶篇一_第2张图片
type

2、reference types 映射表如下:

JNI进阶篇一_第3张图片
reference

注意:引用类型是不能直接使用的,需要根据JNI函数进行相应的转换后,才能使用;多维数组(包含二维数组)都是引用类型,需要使用 jobjectArray 类型存取

如获取一维数组的引用:

    // TODO
    //获取一维数组 的类引用  jintArray类型
    jclass intArrayClass = env->FindClass("[I");

    //构造一个指向jintArray类一维数组的对象数组,该对象数组初始大小为dimion
    jobjectArray objectIntArray = env->NewObjectArray(dimon, intArrayClass, NULL);

"[I"表示类的描述符,后面会具体说明。

引用类型的【继承关系】如下图,我们可以对类型进行转换:

JNI进阶篇一_第4张图片
extend

JNI类描述符

类描述符就是类的完整名称(包名 + 类名),并且将 . 分隔符换成 / 分隔符,如 Java 中的 java.lang.String 类,那么它的描述符为 java/lang/String,如下:

    jclass strClass=env->FindClass("java/lang/String");

当然我们也可以使用域描述符(接下来会具体说明)替换

    //注意L与;号
    jclass strClass=env->FindClass("Ljava/lang/String;");

数组类型的描述符 = [ + 其类型的域描述符 ,例如:

      int [ ]     其描述符为[I
      float [ ]   其描述符为[F
      long [ ]    其描述符为[J
      String [ ]  其描述符为[Ljava/lang/String;

JNI域描述符

1、基本类型的域描述符如下表:

JNI进阶篇一_第5张图片
field

注意 Java 类型中的 long 类型对应的域描述符为 JJava 类型中的 boolean 类型对应的域描述符为 Z

2、引用类型的域描述符

引用类型的域描述符一般为: [ + 该类的类描述符 + ;(分号必不可少)

如:

Object[ ] 类型的域描述符为 [Ljava/lang/Object;

再如:

JNI进阶篇一_第6张图片
student

新建了 Student 实体类的域描述符为:

Student[ ]  域描述符为  [Lcom/github/asdemo/Student;

多维数组的域描述符为 : n个[ + 该类型的域描述符

如:

int [][] 描述符为 [[I

float [][][] 描述符为 [[[F

JNI方法描述符

方法描述符就是 括号(括号内顺序的参数类型域描述符,括号外返回值类型域描述符)

如下几个例子:

method

Java层方法——————————JNI函数签名
String getString()———————Ljava/lang/String;
float f(int I,String n,Object o)——(ILjava/lang/String;Ljava/lang/Object;)F
void set(long[])————————([J)V

了解了描述符,接下来具体撸码实战下。

获取int型二维数组(int a[][])

extern "C" //注意添加
JNIEXPORT jobjectArray JNICALL
Java_com_github_asdemo_MainActivity_getNDKTwoArray(JNIEnv *env, jobject instance, jint size) {
    // TODO
    //获取一维数组 的类引用  jintArray类型
    jclass intArrayClass = env->FindClass("[I");

    //构造一个指向jintArray类一维数组的对象数组,该对象数组初始大小为size
    jobjectArray objectIntArray = env->NewObjectArray(size, intArrayClass, NULL);

    //构建dimion个一维数组,并且将其引用赋值给obejctIntArray对象数组
    for (int i = 0; i < size; i++) {

        //构建 jintArray 一维数组
        jintArray intArray = env->NewIntArray(3);

        //初始化一个容器,假设 大小  = size ;
        jint temp[3];

        for (int j = 0; j < 3; j++) {
            temp[j] = i * 2 + j;
        }

        //给intArray数组设置值
        env->SetIntArrayRegion(intArray, 0, 3, temp);

        //给objectIntArray对象数组赋值,即保持对jint一维数组的引用
        env->SetObjectArrayElement(objectIntArray, i, intArray);

        //删除局部引用
        env->DeleteLocalRef(intArray);
    }
    return objectIntArray;
}

运行效果图:

array

操作Java层的类(读取/设置类的属性)

操作Java层的类有点反射的味道,下面看看具体的一个操作:

Java层的原型方法:

    Student student = new Student();
    student.setName("hello word");
    //更改name的属性
    setStudentName(student);

    //设置实体类 Student 的 name 值
    public native void setStudentName(Student student);

Native层该方法的实现为:

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

    // TODO
    //获得Java层该对象实例的类引用,即Student类引用
    jclass studentClass = env->GetObjectClass(student);

    //获得属性句柄     等同于 env->FindClass("java/lang/String")
    jfieldID nameFieldId = env->GetFieldID(studentClass, "name", "Ljava/lang/String;");

    if (nameFieldId == NULL) {
        //__android_log_print(ANDROID_LOG_DEBUG, "-------", "", NULL); //打印字符串
        return;
    }

    //获取该属性的值  注意第一个参数传入的是jobject obj对象
    jstring nameStr = (jstring) env->GetObjectField(student, nameFieldId);

    const char *c_name = env->GetStringUTFChars(nameStr, NULL);

    //打印字符串
    __android_log_print(ANDROID_LOG_DEBUG, "-------", c_name, NULL);

    env->ReleaseStringUTFChars(nameStr, c_name);

    //给name字段设置新值

    char *c_new_name = "i am new name";

    jstring cName = env->NewStringUTF(c_new_name);
    
    env->SetObjectField(student, nameFieldId, cName);
}

注意 GetObjectField ,SetObjectField方法第一个参数 jobject obj传入的是 jobject对象

运行的效果如下图:

JNI进阶篇一_第7张图片
name

操作Java层的类(回调Java层的方法)

Java层的原型方法:

    //java层的方法
    public String javaMethod(String name, float price, int number) {
        return name + "获得:" + price * number + "¥";
    }

    //回调Java层的方法
    public native String  callJavaMethod();

    //调用Native层的callJavaMethod方法
    Toast.makeText(MainActivity.this, "" + callJavaMethod(), Toast.LENGTH_SHORT).show();

Native层该方法的实现为:

extern "C"
JNIEXPORT jstring JNICALL
Java_com_github_asdemo_MainActivity_callJavaMethod(JNIEnv *env, jobject instance) {
    // TODO
    //获得Java层该对象实例的类引用,即MainActivity类引用
    jclass activityClass = env->GetObjectClass(instance);

    //获取到方法的(句柄)域ID
    jmethodID javaMethodFieldId = env->GetMethodID(activityClass, "javaMethod", "(Ljava/lang/String;FI)Ljava/lang/String;");

    if (javaMethodFieldId == NULL) {
        return env->NewStringUTF("error");
    }
    //设置名称
    jstring name_str = env->NewStringUTF("wen shu");
    //单价
    jfloat price = 55.88f;
    //数量
    jint number = 8;
    //获取返回值
    jstring str = (jstring) env->CallObjectMethod(instance, javaMethodFieldId, name_str, price, number);

    return str;
}

效果图下图:

JNI进阶篇一_第8张图片
call

在Native层返回一个对象

我这里就以 Student 学生类为例。

Java层的Student类:

public class Student {

    private String name;
    private int age;
    
    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Java层的原型方法:

    //native层返回Student对象
    public native Student getNativeStudent();

Navite层该方法的实现为:

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

    // TODO
    //获取类的引用 com/github/asdemo/Student 
    jclass studentClass = env->FindClass("com/github/asdemo/Student");

    //获取该类的构造方法 函数名为  返回类型必须为 void 即 V
    jmethodID constructMethodId = env->GetMethodID(studentClass, "", "(Ljava/lang/String;I)V");

    jstring name = env->NewStringUTF("王者荣耀");

    jint age = 30;

    //创建一个新的对象
    jobject newObj = env->NewObject(studentClass, constructMethodId, name, age);

    return newObj;
}

注意 FindClass("com/github/asdemo/Student");不要使用 [ + ; 的方式,不然会报错。

JNI进阶篇一_第9张图片
stu_obj

既然讲解了在Native层返回一个对象,那么传递一个对象到Native层又是怎样实现的呢?

Java层传递一个对象至Native层

Jave层的原型方法:

    Student student = new Student();
    student.setName("端午快乐");
    student.setAge(66);
    saveStudentToNative(student);
    
    //传递java对象到native层
    public native void saveStudentToNative(Student student);

Native层该方法的实现为:

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

    // TODO
    //获取到类的引用
    jclass stu_cls = env->GetObjectClass(student);

    if (stu_cls == NULL) {
        //__android_log_print(ANDROID_LOG_DEBUG, "-------", "", NULL);//打印信息
        return;
    }

    //获取name属性的引用
    jfieldID nameFieldID = env->GetFieldID(stu_cls, "name", "Ljava/lang/String;");

    jfieldID ageFieldID = env->GetFieldID(stu_cls, "age", "I");

    //获取name属性值
    jstring name = (jstring) env->GetObjectField(student, nameFieldID);

    jint age = env->GetIntField(student, ageFieldID);

    const char *c_name = env->GetStringUTFChars(name, NULL);

    __android_log_print(ANDROID_LOG_DEBUG, "-------", c_name, NULL);

    __android_log_print(ANDROID_LOG_DEBUG, "-------", "%d", age, NULL);

    //释放引用
    env->ReleaseStringUTFChars(name, c_name);
}

Native层返回集合对象(ArrayList)

Java层的原型方法:

    //Native返回集合对象
    public native ArrayList getNativeStudents();

Native层该方法的实现为:

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

    // TODO
    //获取集合类的引用
    jclass list_cls = env->FindClass("java/util/ArrayList");

    if (list_cls == NULL) {
        //空处理
    }

    //获取集合的构造函数ID
    jmethodID list_construct = env->GetMethodID(list_cls, "", "()V");

    //获取集合对象
    jobject list_obj = env->NewObject(list_cls, list_construct);

    //获取 ArrayList 的 add 方法
    // public boolean add(E e)
    // E表示泛型可以用Object代替
    jmethodID list_add = env->GetMethodID(list_cls, "add", "(Ljava/lang/Object;)Z");

    //获取到Student类
    jclass stu_cls = env->FindClass("com/github/asdemo/Student");
    //获取Student的构造函数ID
    jmethodID stu_construct = env->GetMethodID(stu_cls, "", "(Ljava/lang/String;I)V");

    //添加集合元素
    for (int i = 0; i < 3; i++) {
        jstring name = env->NewStringUTF("端午快乐");
        jint age = 66;
        //调用该对象的构造函数来new一个Student实例
        jobject stu_obj = env->NewObject(stu_cls, stu_construct, name, age);

        //添加集合数据 添加一个Student对象
        env->CallBooleanMethod(list_obj, list_add, stu_obj);
    }

    return list_obj;
}

效果如下图:

JNI进阶篇一_第10张图片
array

文章到这里差不多结束了,如果你喜欢,请跟我一起进一步学习 JNI 技术。在此提前祝贺大家端午快乐!又是一年端午,团团圆圆,和和美美!

源码地址

Android学习群欢迎你的加入,一同学习 kotlin

JNI进阶篇一_第11张图片
qq

你可能感兴趣的:(JNI进阶篇一)