JNI实用笔记

好久没用过JNI了,复习一下基本用法。

cmake配置

构建方式从ndk-build转到cmake,目前感觉良好。
cmake配置:

#设置最小 cmake 版本
cmake_minimum_required(VERSION 3.4.1)
project (start)
#命名start动态库,最终生成libstart.so
add_library(start SHARED src/main/cpp/start.cpp)
#查找本地库
find_library(xter start)
#原生库及log日志相关库
target_link_libraries(start ${log-lib})

主要是add_library和target_link_libraries的使用需要注意;
add_library用于命名并生成库,这里是单文件,如果有多文件,可以使用类似

file(GLOB_RECURSE SRC_C "*.cpp")
add_library(start SHARED ${SRC_C})

的方式来配置,以及如果需要生成多个库,再次添加add_library配置即可;
target_link_libraries用于配置上面生成的库的链接库,可以简单理解为配置"依赖",这里用到了log日志库,所以需要配置日志库,同理,如果有其他库需要配置依赖,再次添加类似配置即可以。

更详细的配置指令可以查阅cmake官方文档

常用方法

创建数组:

  • NewXXXArray
    如NewIntArray、NewFloatArray等基本数据类型数组,或NewObjectArray建立对象数组;

对数组进行赋值:

  • SetXXXArrayRegion
    对基本数据类型的数组进行赋值,如SetIntArrayRegion、SetFloatArrayRegion;
  • SetObjectArrayElement
    对对象数组进行赋值

获取数组值:

  • GetXXXArrayElements
    获取基本数据类型数组的指针,如GetIntArrayElements,一般需要配合相应的释放方法如ReleaseIntArrayElements使用;
  • GetObjectArrayElement
    获取对象数组的指针,使用方法同上;
  • GetXXXArrayRegion
    获取(复制)数组的某范围的数组指针;

创建对象:

  1. FindClass
    查找对象路径,必须要完整类路径,如FindClass(“java/util/ArrayList”);
  2. GetMethodID
    获取类中的方法,如GetMethodID(cls_ArrayList, “”, “()V”);
  3. NewObject/AllocObject
    调用构造方法创建对象,如NewObject(cls_ArrayList, construct, “”);
    对于某些类,也可直接使用AllocObject创建对象;
  4. GetFieldID/GetStaticFieldID
    获取某成员变量,如GetFieldID(jc, “address”, “java/lang/String”);
    GetStaticFieldID是获取静态成员变量,使用方法相同;
  5. SetObjectField/SetStaticObjectField
    对成员变量赋值,如SetObjectField(profile, field_address, env->NewStringUTF(“问就是在故宫”));
  6. CallXXXMethod
    调用类的方法,如CallBooleanMethod、CallVoidMethod等,每个数据类型还有扩展的派生方法如CallBooleanMethodA、CallBooleanMethodV,作用是一样的,只是传参方式不同;

使用场景

数组的建立
public static native int[] getIntArray(int size);

使用NewXXXArrray新建数组,然后赋值;

JNIEXPORT jintArray JNICALL
Java_com_xter_jnipractice_SomeFunc_getIntArray(JNIEnv *env, jclass o, 
jint size) {
    
       
    jintArray array;    
    array = env->NewIntArray(size);    
    for (int i = 0; i < size; i++) {
    
            
        env->SetIntArrayRegion(array, i, 1, &i);    
    }   
    return array;
}

重点关注SetIntArrayRegion的使用,其他数据类型的数组使用方法相同:

void SetIntArrayRegion(jintArray array, jsize start, jsize len, const jint* buf)
void SetCharArrayRegion(jcharArray array, jsize start, jsize len, const jchar* buf)
void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, const jbyte* buf)
void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, const jfloat* buf)
void SetLongArrayRegion(jlongArray array, jsize start, jsize len, const jlong* buf)
void SetShortArrayRegion(jshortArray array, jsize start, jsize len, const jshort* buf)
void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, const jdouble* buf)
void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, const jboolean* buf)
字符串使用
public static native String[] getStringArray(int size);

对于一般场景下,NewStringUTF足够使用,传入char*即可;
这里是创建字符串数组,需要先声明对象类型为String;

JNIEXPORT jobjectArray JNICALL
Java_com_xter_jnipractice_SomeFunc_getStringArray(JNIEnv *env, jclass o, 
jint size) {
    
       
    jclass jc = env->FindClass("java/lang/String");   
    jobjectArray array;    
    array = env->NewObjectArray(size, jc, NULL);    
    const char *s[] = {
    
    "我", "你", "他", "它", "她"};    
    for (int i = 0; i < size; i++) {
    
           
        jstring ss = env->NewStringUTF(s[i % 5]);        
        env->SetObjectArrayElement(array, i, ss);    
    }    
    return array;
}
创建复杂对象

自定义对象为:

public class Profile {
    
    
	public String address;
	public String school;
	public String degree;
	public String phoneNumber;
	public int salary;
	public ArrayList<String> experience;
}

native方法为:

	public static native Profile genProfile();

那么根据上面的步骤,先找到类路径,并得到其各成员变量:

    jclass jc = env->FindClass("com/xter/jnipractice/entity/Profile");
    jfieldID field_address = env->GetFieldID(jc, "address", "java/lang/String");
    jfieldID field_school = env->GetFieldID(jc, "school", "java/lang/String");
    jfieldID field_degree = env->GetFieldID(jc, "degree", "java/lang/String");
    jfieldID field_phoneNumber = env->GetFieldID(jc, "phoneNumber", "java/lang/String");
    jfieldID field_salary = env->GetFieldID(jc, "salary", "I");
    jfieldID field_experience = env->GetFieldID(jc, "experience", "java/util/ArrayList");

注意GetFieldID第三个参数传入的类型描述符;
基本数据类型都是首字母大写,除了2个特殊的——boolean->Z,long->J;
Void对应的是V,数组对应的[;
其他引用类型则是“L类全名;“形式,如"Ljava/lang/String;”,不过经测试GetFieldID这里的验证并没有那么严格,所以直接使用"java/lang/String"也是可以的;

    jclass cls_ArrayList = env->FindClass("java/util/ArrayList");
    jmethodID construct = env->GetMethodID(cls_ArrayList, "<init>", "()V");
    jobject obj_ArrayList = env->NewObject(cls_ArrayList, construct, "");
    jmethodID arrayList_add = env->GetMethodID(cls_ArrayList, "add", "(Ljava/lang/Object;)Z");
    env->CallBooleanMethod(obj_ArrayList, arrayList_add, env->NewStringUTF("长城贴砖"));
    env->CallBooleanMethod(obj_ArrayList, arrayList_add, env->NewStringUTF("氢弹抛光"));
    env->CallBooleanMethod(obj_ArrayList, arrayList_add, env->NewStringUTF("太空电梯"));

接着创建List集合,同样是先调用其构造方法创建List对象,然后调用其add方法添加值;
这里注意GetMethodID最后一个参数同样是传入类型描述签名,为“(参数类型)返回类型”格式,这里的list的add方法的参数类型为Object,返回值类型为boolea所以写成"(Ljava/lang/Object;)Z";
以及上面的构造方法是无参,因此NewObject最后的参数传了空,如果是调用的有参,那么就传相应参数即可,如:

    jclass cls_ArrayList = env->FindClass("java/util/ArrayList");
    jmethodID construct = env->GetMethodID(cls_ArrayList, "<init>", "(I)V");
    jobject obj_ArrayList = env->NewObject(cls_ArrayList, construct, 3);

这里调用了ArrayList另一个有参构造方法,传入了int;
最后设置自定义对象的值:

    jobject profile = env->AllocObject(jc);
    jstring address = env->NewStringUTF("问就是在故宫");
    env->SetObjectField(profile, field_address, address);
    env->SetObjectField(profile, field_experience, obj_ArrayList);
操作byte数组

自定义对象:

public class Secret {
    
    
	public byte[] order = new byte[1];
	public byte[] len = new byte[4];
	public byte[] plan;
}

native方法为:

	public static native Secret genSecret(byte[] buffer);

传入数据为:

		byte[] b1 = BytesUtils.intTo1Bytes(1);
		byte[] b2 = BytesUtils.intTo4Bytes(45);
		byte[] b3 = "委员称香港的教育必须向祖国学习".getBytes();
		byte[] buffer = BytesUtils.mergerBytes(b1,b2,b3);
		Log.i("jni", "Secret=" + SomeFunc.genSecret(buffer));

开始构建对象,首先同样是寻找对象,得到各成员变量:

    jclass jc = env->FindClass("com/xter/jnipractice/entity/Secret");
    jfieldID field_order = env->GetFieldID(jc, "order", "[B");
    jfieldID field_len = env->GetFieldID(jc, "len", "[B");
    jfieldID field_plan = env->GetFieldID(jc, "plan", "[B");

接着构建buffer,从源byte[]中取到(复制)值:

    jbyte *bufOrder = (jbyte *) malloc(sizeof(jbyte) * 1);
    jbyte *bufLen = (jbyte *) malloc(sizeof(jbyte) * 4);

    memset(bufOrder, 0, sizeof(jbyte) * 1);
    memset(bufLen, 0, sizeof(jbyte) * 4);

    env->GetByteArrayRegion(buffer, 0, 1, bufOrder);
    env->GetByteArrayRegion(buffer, 1, 4, bufLen);
    unsigned int len = byte2int(bufLen, 4);
    jbyte *bufPlan = (jbyte *) malloc(sizeof(jbyte) * len);
    LOGD("content len = %d",len);
    memset(bufPlan, 0, sizeof(jbyte) * len);
    env->GetByteArrayRegion(buffer, 5, len, bufPlan);

然后创建byte[],以buffer赋值:

    jbyteArray arrayOrder = env->NewByteArray(1);
    jbyteArray arrayLen = env->NewByteArray(4);
    jbyteArray arrayPlan = env->NewByteArray(len);

    env->SetByteArrayRegion(arrayOrder, 0, 1, bufOrder);
    env->SetByteArrayRegion(arrayLen, 0, 4, bufLen);
    env->SetByteArrayRegion(arrayPlan, 0, len, bufPlan);

最后一一为对象赋值:

    jobject secret = env->AllocObject(jc);
    env->SetObjectField(secret, field_order, arrayOrder);
    env->SetObjectField(secret, field_len, arrayLen);
    env->SetObjectField(secret, field_plan, arrayPlan);

整体记录一下常用方法,源码见传送门

猜你喜欢

转载自blog.csdn.net/ifmylove2011/article/details/115300578