赞
踩
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
JNI (java native interface)即java本地开发接口,JNI 是一个协议,这个协议用来沟通java代码和外部的本地代码(c/c++),通过这个协议java代码就可以调用外部的c/c++代码,外部的c/c++代码也可以调用java代码,使得Java代码和其他语言写的代码(如C/C++代码)进行交互。
使用JNI好处主要有以下三点:
1、Java语言提供的类库无法满足要求,且在数学运算,实时渲染的游戏上,音视频处理等方面上与C/C++相比效率稍低。
2、Java语言无法直接操作硬件,C/C++代码不仅能操作硬件而且还能发挥硬件最佳性能。
3、使用Java调用本地的C/C++代码所写的库,省去了重复开发的麻烦,并且可以利用很多开源的库提高程序效率。
在项目下的gradle.properties添加:android.useDeprecatedNdk=true
1、首先新建个 java 类
public class JniTest{
static {
//loadLibrary传入的是so库名称,它默认路径是/data/data/+包名+/lib,安装时库文件默认会被复制到该目录下
System.loadLibrary("jary");
//指定加载so库的路径
System.load(EnvironmentUtils.getPackageName() + "/libs/armeabi-v7a/libjary.so");
}
public native String getString();
}
然后重新编译下Project: Build–>Make Project,重新编译之后就可以在对应的文件夹看到编译后的 JniTest.class,即在build下intermediates\classes\debug\jary\com\myjnitest。这个路径根据Android studio版本的不同,会有不同的区别,如AS 2021.3.1版本,生产的class在如下路径:
build\intermediates\javac\debug\classes jary.com.myjnitest.JniTest
2、生成 .h 的文件
在 studio 打开 Terminal 命令行工具,在命令行中先进入到工程的 main 目录下,输入命令:
javah -d jni -classpath 编译后的 class 文件的绝对路径。
例如:javah -d jni -classpath ‘C:\ASworkspace\MyJniTest\app\build\intermediates\classes\debug’ jary.com.myjnitest.JniTest
在包名处不能用“ \ ” ,要用“.”的形式,且和前面要有一个空格。编译后的class文件一定要在包的目录结构内,即\jary\com\myjnitest\,否则会有找不类的错误。
命令中参数jni可以不用,-classpath后的地址用单引号号括起来,如果路径中有空格就用双引号。
命令执行后会在 main 目录下(在哪个目录下执行就会在哪个目录下生成 .h 文件)自动生成 “jni” 文件夹(如果使用了参数-jni),同时生成一个 .h 的文件。在jni文件夹下编写C文件,#include 上面生成的 .h文件,按照生成的 .h文件里的方法名,在c文件中实现。
3、到module的build.gradle中进行配置
在android节点下:
externalNativeBuild {
cmake {
path 'CMakeLists.txt'
}
}
在defaultConfig节点 添加如下代码
ndk{
abiFilters "armeabi" //表示生成哪些平台的so
}
然后创建 CMake 构建脚本,在应用模块下 new 一个 file 文件,命名为 CMakeLists.txt 即可。
内容可直接复制新建项目中生成的,注意将下图 1,3 点改为你自己定义的 moduleName , 第 2 点改为你刚刚创建源文件的路径。
配置完成后就可以编译了,编译生成的so库在在build下intermediates\cmake里。
上面介绍了静态注册native方法的过程,就是Java层声明的nativ方法和JNI函数一一对应。刚开始做JNI的前期,可能会遵守静态注册的流程:
1、编写带有native方法的Java类
2、使用Javah命令生成.h头文件
3、编写代码实现头文件中的方法
这样的单调的标准流程,而且还要忍受这么"长"的函数名。那有没有更简单的方式呢?比如让Java层的native方法和任意JNI函数连接起来?
答案是有的——动态注册,也就是通过RegisterNatives方法把C/C++中的方法映射到Java中的native方法,而无需遵循特定的方法命名格式。
方法一(使用反射的方式,在C代码中调用Java方法)
//jclass (FindClass)(JNIEnv, const char*); 拿到类的字节码
//第二个参数是类的包名
jclass clazz = (env)->FindClass(env, “com/itheima/ccalljava/MainActivity”);
//jmethodID (GetMethodID)(JNIEnv, jclass, const char, const char*); 拿到方法
//第三个参数是要调用的java方法的名字,第四个参数是被调用的java方法的签名(用javap命令来查询)
//参数四:括号里面的是show方法的参数的签名,外面是V是返回值的签名-----即方法参数是String类型,返回值类型是viod
jmethodID methodID = (*env)->GetMethodID(env, clazz, “show”, “(Ljava/lang/String;)V”);
//void (CallVoidMethod)(JNIEnv, jobject, jmethodID, …); 调用方法
//jmethodID后面就是调用方法的参数,因为C与java的字符串不同,
//故用 (env)->NewStringUTF(env, char cstr)将C的字符串转为jstring
(*env)->CallVoidMethod(env, obj, methodID, (*env)->NewStringUTF(env, “是时候再”));
方法二(与一不同的是:获取jclass的方式不同)
//jclass
jclass cls = (*env)->GetObjectClass(env, obj);
//jmethodID
jmethodID mid = (*env)->GetMethodID(env, cls, "genRandomInt", "(I)I");
//调用方法
//规则:Call<Type>Method Type是返回值类型 如:CallObjectMethod返回的是Object,CallVoidMethod没有返回值
jint random = (*env)->CallIntMethod(env, obj, mid, 200);
被访问的java方法如果有多个参数,如:
public void show(int j , String str ,int k)
在jni里
// I:int类类型签名,Ljava/lang/String;是String的签名
jmethodID methodID = (*env)->GetMethodID(env, clazz, “show”, “(ILjava/lang/String;I)V”);
访问静态方法
JNIEXPORT void JNICALL Java_com_tz_jni_TestNative_accessStaticMethod(JNIEnv * env, jobject cls){
//如果native方法为static,jobject为子类jclass的实例,也就是native方法所属的类的Class实例,即cls就是jclass
//故如果是调用该类里面的方法,获取jclass就可以不用写了
//jclass
//jclass cls = (*env)->GetObjectClass(env, obj);
//jmethodID
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;");
//调用
//规则:CallStatic<Type>Method
jstring uuid = (*env)->CallStaticObjectMethod(env, cls, mid);
//jstring转为C字符串
char *uuid_str = (*env)->GetStringUTFChars(env, uuid, NULL);
}
访问构造方法
// jclass Date
jclass cls = (*env)->FindClass(env, "java/util/Date");
//构造方法jmethodID,第3个参数<init>表示是构造方法
jmethodID contructor_mid = (*env)->GetMethodID(env, cls, "<init>","()V");
//实例化一个Date对象
jobject date_obj = (*env)->NewObject(env, cls, contructor_mid);
//调用Date里的getTime方法
jmethodID mid = (*env)->GetMethodID(env, cls, "getTime", "()L");
jlong time = (*env)->CallLongMethod(env, date_obj, mid);
调用父类的方法
在java文件中有一个属性 Hunman hunman = new Man( );//Hunman是Man的父类
sayHi( )是Hunman的一个方法,Man覆写了sayHi()方法。
JNIEXPORT void JNICALL Java_com_tz_jni_TestNative_callNonvirtualMethod(JNIEnv * env, jobject obj){ //获取一个Hunman对象 jclass cls = (*env)->GetObjectClass(env, obj); jfieldID fid = (*env)->GetFieldID(env, cls, "human", "Lcom/tz/jni/Hunman ;"); jobject human_obj = (*env)->GetObjectField(env, obj, fid); //取得obj中Hunman 的对象 //执行sayHi方法 jclass human_cls = (*env)->FindClass(env, "com/tz/jni/Human");//注意:传父类的类名 jmethodID mid = (*env)->GetMethodID(env, human_cls, "sayHi", "()Ljava/lang/String;"); //执行子类覆盖的方法 //jstring jstr = (*env)->CallObjectMethod(env, human_obj, mid); //执行父类的方法 jstring jstr = (*env)->CallNonvirtualObjectMethod(env, human_obj, human_cls, mid); //jstring->char* char * str = (*env)->GetStringUTFChars(env, jstr, NULL); }
注意
1、方法参数或者返回值为java中的对象时,签名中必须以“L”加上其路径,不过此路径必须以“/”分开,自定义的对象也使用本规则
比如说 java.lang.String为“java/lang/String”,com.nedu.jni.helloword.Student为"Lcom /nedu/jni/helloword/Student;"
2、方法参数或者返回值为数组类型时,请前加上[
例如[I表示 int[],[[[D表示 double[][][],即几维数组就加几个[
例子:在C中调用java属性,修改该属性并返回修改后的值
//得到class jclass cls = (*env)->GetObjectClass(env, obj); //jfieldID 取得属性的id //属性 第三个参数是要调用的java中属性的名称, //第四个参数是属性的签名,如果是对象属性,则需要L加上类型路径 jfieldID fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;"); //获取key属性的值,这例子里key在java中是字符串String类型 //注意:获取数据的规则如下(*env)->Get<Type>Field(); //如果key是int类型的这样获取:(*env)->GetIntField(); //key在java中是对象属性则是下面的写法 jstring jstr = (*env)->GetObjectField(env, obj, fid); //jstring转为 C/C++字符串 char *str = (*env)->GetStringUTFChars(env, jstr, NULL); //拼接字符串 char text[50] = "super "; strcat(text,str); //拼接完成之后,从C字符串转为jstring jstr = (*env)->NewStringUTF(env, text); //修改key的属性 //注意规则:Set<Type>Field (*env)->SetObjectField(env, obj, fid, jstr); //修改key的属性 return jstr;
访问静态属性
//jclass
jclass cls = (*env)->GetObjectClass(env, obj);
//jfieldID
jfieldID fid = (*env)->GetStaticFieldID(env, cls, "count", "I");
//获取静态属性的值
//规则:GetStatic<Type>Field
jint count = (*env)->GetStaticIntField(env, cls, fid);
count += 10;
//修改属性的值
//规则:SetStatic<Type>Field
(*env)->SetStaticIntField(env, cls, fid, count);
数组处理
//比较器 int compare(jint *a, jint * b){ return (*b) - (*a); } //传入int数组,对数组进行排序 JNIEXPORT void JNICALL Java_com_tz_jni_TestNative_giveArray(JNIEnv * env, jobject obj, jintArray arr){ //基本数据类型,传值 //引用类型,传引用 //arr,是指向Java数组的指针 //Java的int数组(jintArray)->C int数组 jint *elems = (*env)->GetIntArrayElements(env, arr, NULL); //数组的长度 int len = (*env)->GetArrayLength(env, arr); //对(jint)long数组进行排序。qsort( )方法是C函数库里的排序函数 qsort(elems, len, sizeof(jint), compare); //同步,将排好序的C数组同步到java数组 //释放数组的元素 //第四个参数:mode参数 //mode = 0,Java数组进行更新,并且释放C/C++数组 //mode = JNI_ABORT,Java数组不进行更新,但是释放C/C++数组 //mode = JNI_COMMIT,Java数组进行更新,不释放C/C++数组(函数执行完,数组还是会释放) (*env)->ReleaseIntArrayElements(env, arr, elems, JNI_COMMIT); } //返回指定大小的数组 JNIEXPORT jintArray JNICALL Java_com_tz_jni_TestNative_getArray(JNIEnv * env, jobject obj,jint len){ jintArray jint_arr = (*env)->NewIntArray(env, len); //赋值 //jintArray -> jint * (将jintArray转成C int数组) jint * elems = (*env)->GetIntArrayElements(env, jint_arr, NULL); int i = 0; //循环赋值 for (; i < len; i++){ elems[i] = i; } //同步 把C数组同步到JNI数组 (*env)->ReleaseIntArrayElements(env, jint_arr, elems, 0); return jint_arr; }
中文字符串乱码问题
JNIEXPORT jstring JNICALL Java_com_tz_jni_TestNative_chineseChars(JNIEnv * env, jobject obj,jstring jstr){ //jstring转C字符串 //char * str = (*env)->GetStringUTFChars(env, jstr, NULL); char *cstr = "我说中文"; //C字符串->jstring //如果直接 return (*env)->NewStringUTF(env, cstr); 在java中拿到返回值是乱码 // 因为NewStringUTF中UTF是UTF16编码 //原理:调用Java的转码API,返回GB2312中文编码字符串 //使用这个构造方法,完成转码 //public String(byte bytes[], String charsetName),使用String类的构造方法并指定编码格式 //执行这个构造方法需要三个东西 //1.jmethodID //2.byte数组(jbyteArray)参数 //3.charsetName参数(jstring) //String类的jclass jclass str_cls = (*env)->FindClass(env, "java/lang/String"); //取得构造方法id jmethodID constructor_mid = (*env)->GetMethodID(env, str_cls, "<init>", "([BLjava/lang/String;)V"); //char * -> char[] ->jbyteArray -> jbyte * ,new一个jbyteArray jbyteArray bytes = (*env)->NewByteArray(env, strlen(cstr)); //bytes数组赋值,把C字符串复制到上面new出来的jbyteArray (*env)->SetByteArrayRegion(env, bytes, 0, strlen(cstr), cstr); //charsetName="GB2312",把"GB2312"转成jstring jstring charsetName = (*env)->NewStringUTF(env, "GB2312"); //返回GB2312中文编码jstring return (*env)->NewObject(env, str_cls, constructor_mid, bytes, charsetName); }
最后,贴出Java数据类型的签名对照表:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。