前言
不知不觉毕业快 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
层的方法:
我们来观察下 Native 层的方法体:
JNIEXPORT jstring JNICALL
Java_com_github_asdemo_MainActivity_getNDKString(JNIEnv *env, jobject instance)
JNIEXPORT
类似 Java
中的关键字,jstring
是返回值的类型,跟 Java
中 String
多了 j
,接下来会具体看看 Native
层有哪些返回值的类型。JNICALL
关键字后面跟着方法名 Java_com_github_asdemo_MainActivity_getNDKString
, Native
层的方法规范如下:
方法名 = Java_ + 包名 + 当前调用的Class名 + Java层的方法名
JNI的数据类型
家喻户晓,Java 存在两种数据类型:基本类型,引用类型。那么 JNI 也有两种数据类型:
primitive types (基本数据类型)如 jint,jbyte,jlong,jfloat 等
reference types (引用类型)如 jstring,类,数组 等
1、primitive type 映射表如下:
2、reference types 映射表如下:
注意:引用类型是不能直接使用的,需要根据JNI
函数进行相应的转换后,才能使用;多维数组(包含二维数组)都是引用类型,需要使用 jobjectArray 类型存取。
如获取一维数组的引用:
// TODO
//获取一维数组 的类引用 jintArray类型
jclass intArrayClass = env->FindClass("[I");
//构造一个指向jintArray类一维数组的对象数组,该对象数组初始大小为dimion
jobjectArray objectIntArray = env->NewObjectArray(dimon, intArrayClass, NULL);
"[I"
表示类的描述符,后面会具体说明。
引用类型的【继承关系】如下图,我们可以对类型进行转换:
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、基本类型的域描述符如下表:
注意 Java 类型中的 long 类型对应的域描述符为 J ;Java 类型中的 boolean 类型对应的域描述符为 Z
2、引用类型的域描述符
引用类型的域描述符一般为: [ + 该类的类描述符 + ;(分号必不可少)
如:
Object[ ] 类型的域描述符为 [Ljava/lang/Object;
再如:
新建了 Student
实体类的域描述符为:
Student[ ] 域描述符为 [Lcom/github/asdemo/Student;
多维数组的域描述符为 : n个[ + 该类型的域描述符
如:
int [][] 描述符为 [[I
float [][][] 描述符为 [[[F
JNI方法描述符
方法描述符就是 括号(括号内顺序的参数类型域描述符,括号外返回值类型域描述符)
如下几个例子:
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;
}
运行效果图:
操作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
对象。
运行的效果如下图:
操作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;
}
效果图下图:
在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");
不要使用 [ + ; 的方式,不然会报错。
既然讲解了在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
技术。在此提前祝贺大家端午快乐!又是一年端午,团团圆圆,和和美美!
源码地址
Android学习群欢迎你的加入,一同学习 kotlin