有关ndk、jni开发流程、数据类型、数组操作
2014-08-06 15:48
323 查看
转自:http://zhiwei.neatooo.com/blog/detail?blog=513835c2bb0201464b000004
首先,我们要弄懂几个概念,何为NDK,它和SDK以及JNI有什么关系?请前看下图:
JNI(JavaNativeInterface),Java的本地接口
JNI是Java众多开发技术中的一门,意在利用本地代码,为Java程序提供更高效,更灵活的拓展。应用场景包括:对运行效率敏感的算法实现、跨平台应用移植、调用系统的底层驱动、调用硬件等。尽管Java一贯以其良好的跨平台性而著称,但真正的跨平台之王,应该是C/C++,因为当前世上90%的系统都是基于C/C++编写的。Java的跨平台,是以牺牲效率换来对多种平台的兼容性,因而JNI可以说是Java短板的补充!举一例子说明,当前流行的移动操作系统Android,一直被说系统操作的流畅性不如IOS,原因在于Android的App是基于Java开发的,IOS的是基于Object-C开发的,区别在于同样的操作,在IOS上一条指令完成,在Android上则需要多大三条指令才能完成(数据来自于网络,不一定准确)!于是在AndroidJellyBean版本中,Google为其引入ProjectButter(黄油计划),在应用层大量使用了本地库,并优化了系统的架构,以提升Android系统整体的操作反应!
咔咔,JNI的介绍就先说到这里,总之,JNI是一门技术,是JavaCode和C/C++Code联系的桥梁!
JNI开发的流程
1、编写JavaCode,如下面的例子:
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toast.makeText(getApplicationContext(),sayHellow(),Toast.LENGTH_LONG).show();
}
publicnativeStringsayHellow();//调用本地方法
static{
System.loadLibrary("Scgps_Client");//加载本地共享库
}
}
2、编写C/C++Code,如下面的例子:
#include"string.h"
#include"jni.h"
JNIEXPORTjstringJNICALLJava_com_scgps_client_MainActivity_sayHellow(JNIEnv*env,jobjectthiz)
{
constchar*ret="HellowFormNdk";
return(*env)->NewStringUTF(env,ret);
}
3、编译C/C++Code,成功并得到本地共享库
本地共享库是Linux下的叫法,文件扩展名是.so,windows下叫动态链接库,文件扩展名是.dll。前面说到C/C++才是跨平台之王,这就是其中的道理,面对不同的平台,编译不同的结果。相对于Java的一次编译到处运行的跨平台性牺牲运行效率,C/C++的跨平台性则是牺牲编译时间以及编译的难度。这里的编译难度是指为适应不同平台而做的编译过程的调整,这个活的难度可大可小,还不一定成功,视乎平台的兼容性以及支持。说到这里,难免会有人喷了:说什么跨平台性,这么复杂还不稳定!的确C/C++的跨平台性是有局限性的,但是纵观当前的各种平台和系统,有哪家是不支持C/C++本地开发的?只是各自提供的底层API和编译条件不同而已,只需要调整一下C/C++的编译代码,通过编译即可运行,难道也不是一件美事?
4、编译并打包Java
把本地共享库放置到Java项目的指定目录,一般是libs文件件,Android的项目是libs/armeabi(armeabi是对应的平台,后面会详讲),然后编译Java的代码即可运行!
NDK,(Nativedevelopkit),本地开发工具包
NDK是Google为Android进行本地开发而放出的一个本地开发工具,包括Android的NativeAPI、公共库以及编译工具,注意,NDK需要Android1.5版本以上的支持哦。
按照上图的解说,NDK处在开发流程的编译环节,对,简单来说,NDK是JNI开发的一个扩展工具包!针对Android平台,其支持的设备型号繁多,单单就设备的核心CPU而言,都有三大类:ARM、x86和MIPS,况且ARM又分为ARMv5和ARMv7等等,为何Android又能适配如此之多的设备?接着JNI开发流程的话,利用NDK,我们可以针对不同的手机设备,编译出对应可运行的本地共享库了,至于如何使用NDK进行编译、开发,我们留作下次再进行探讨。
SDK,(StandardDevelopKit),标准开发包
SDK是Google提供的Android标准开发工具包,里面包含了完整的API文档,各Android版本的开发库,Android的虚拟机以及Android的打包工具等。众所周知,Android的应用开发语言是Java,App的运行时是DelvikRuntime,属于JVM的改良版本,官方说Delvik
VM更适用于移动设备。一般而言,由于Google的SDK提供了强大又完善的API,开发一般需求的应用,SDK足矣。然而前面已经说过,Java的运行效率引发了不少问题,因而才有了JNI技术的存在,那SDK和NDK的关系是怎样的呢?见下图解说,可以说,NDK是SDK的一个补充。
SDK,JNI,NDK的开发流程
这个开发流程大致与JNI的开发流程差不多,下面我再详细说明一下每个环节:
SDK开发,编写Java代码,调用各种Android的API实现功能,编写含有native关键字的代码开始JNI;
JNI开发,按照JNI编码规范,编写与Java交互的本地代码,一般就是数据类型的转换,把C/C++的数据类转换成Java能识别的,或反过来。也因为这样子,我认为JNI其实就是Adapter,作为数据转换层而存在,具体JNI的一般操作,我之后再分享;
C/C++开发,编码实现业务逻辑,或调用NDK提供的本地API或库,完成Android平台上特定功能的开发、封装;
NDK编译,编写.mk文件,编译调试,最后修改.mk文件,针对特定的平台(ARM/x86)做编译结果的优化;
最后就是SDK编译、打包,上真机调试了...
http://zhiwei.neatooo.com/blog/detail?blog=514294b275363a521d000003
AndroidNDK开发简介》,我简单地说明了AndroidNDK开发的流程,以及其重要的一环:JNI层得开发。今天我再详细说明一下自己的学习经验。
JNI是Java代码和C/C++代码通信的桥梁,其角色在某种意义上就是一个翻译员,从设计模式来看叫适配器。
两者的沟通,首要的一定要对嘴型,对channel,沟通才能到位。计算机程序的基本组成,从狭义来讲,就是数据结构+算法。由于Java和C/C++是两种不同的编程语言,它们各自拥有自家定义的数据类型和结构。JNI的第一步就是统一转换其中一方的数据类型,这就好比我们跟外国友人沟通,我们得说英语一样子。下表是Java的8大基本类型,在Jni层对应的数据描述:
复杂一点的对象类型,其对应的数据描述如下图:
这里补充说明一下:
1.Java中的返回值void和JNI中的void是完全对应的
2.Java中的基本数据类型(boolean,byte,char,short,int,long,float,double),在JNI中对应的数据类型只要在前面加上j就对应了(jboolean,jbyte,jchar,jshort,jint,jlong,jfloat,jdouble)
3.Java中的对象,包括类库中定义的类、接口以及自定义的类接口,都对应于JNI中的jobject
4.Java中基本数据类型的数组对应与JNI中的jarray类型。(type就是上面说的8种基本数据类型)
5.Java中对象的数组对应于JNI中的jobjectArray类型。(在Java中一切对象、接口以及数组都是对象)
关于数据类型的转换,JNI还提供的强悍的函数库来支持。对于基本的类型的转换,我们先来复习一下,先关注一下Java基本类型的精度。
Java的基本数据类型是不存在有符号和无符号这种概念的,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变。
像byte的范围是-128到127,你想要变为0到255怎么办,,跟0XFF做与运算就可以了:b&0XFF
如byteb,如果你想赋值它值255,那是不行的,就算赋值了,b的值也是255对256求模后的值-1,即b=-1,然后b&0XFF结果即为255,这个与运算后的结果会隐式转换为int类型的,因为byte放不下了,与运算还是很快的,,比加减法还快的。
所以Jni层使用Java的基本类型数据,对于上面八种基本的数据类型,jni层的c/c++代码可以用强制直接转换成对应长度的c/c++类型数据。
如:unsignedchartmp=(unsignedchar)m_jboolean;
unsignedshorttmp=(unsignedshort)m_jchar;
或者同长度类型的数据,可以直接赋值,inttmp=m_jint;
http://zhiwei.neatooo.com/blog/detail?blog=5142c98375363a521d000005
在C/C++中,jintArray不能用下标对其进行直接存取,必须用到JNI中提供的接口函数进行操作。为了存取Java简单类型的数组,就要要使用GetXXXArrayElements函数(见表),XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。
使用SetXXXArrayRegion与GetXXXArrayRegion就是以复制的方式设置与取出Array数组中的某个值。
最后得特别说明一下,当你使用对数组进行访问后,要确保调用相应的ReleaseXXXArrayElements函数,参数是对应Java数组和GetXXXArrayElements返回的指针,如果必要的话,这个释放函数会复制你做的任何变化(这样它们就反射到java数组),然后释放所有相关的资源,避免发生内存泄漏。
http://zhiwei.neatooo.com/blog/detail?blog=5142d8da75363a521d000006
下表列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。
构造一个Java对象的实例
首先是获得一个Java类的class引用(*env)->FindClass(env,"Lpackagename/classname;");请注意参数:Lpackagename/classname;,L代表这是在描述一个对象类型,packagename/classname是该对象耳朵class路径,请注意一定要以分号(;)结束!
然后是获取函数的id,jmethodIDid=env->GetMethodID(cls,"","(D)V");第一个是刚刚获得的class引用,第二个是方法的名称,最后一个就是方法的签名了
还是不懂?我曾经如此,请接着看...
第一个变量name是Java中函数的名字。
第二个变量signature,用字符串是描述了函数的参数和返回值
第三个变量fnPtr是函数指针,指向C函数。
其中比较难以理解的是第二个参数,例如
"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"
实际上这些字符是与函数的参数类型一一对应的。
"()"中的字符表示参数,后面的则代表返回值。例如"()V"就表示voidFunc();
"(II)V"表示voidFunc(int,int);
那其他情况呢?请查看下表:
稍稍补充一下:
1、方法参数或者返回值为java中的对象时,签名中必须以“L”加上其路径,不过此路径必须以“/”分开,自定义的对象也使用本规则
比如说java.lang.String为“java/lang/String”,com.nedu.jni.helloword.Student为"Lcom/nedu/jni/helloword/Student;"
2、方法参数或者返回值为数组类型时,请前加上[
例如[I表示int[],[[[D表示double[][][],即几维数组就加几个[
其参数的意义:
env-->JNIEnv
cls-->第一步获取的jclass
"callback"-->要调用的方法名
"(I)V"-->方法的Signature,签名同前面的JNI规则。
使用CallVoidMethod方法调用方法。参数的意义:
env-->JNIEnv
obj-->通过本地方法穿过来的jobject
mid-->要调用的MethodID(即第二步获得的MethodID)
depth-->方法需要的参数(对应方法的需求,添加相应的参数)
注:这里使用的是CallVoidMethod方法调用,因为没有返回值,如果有返回值的话使用对应的方法,在后面会提到。
现在稍稍明白文章开始构造Java对象那个实例了吧?让我们继续深入一下:
这里使用GetStringUTFChars方法将传进来的prompt(jstring类型)转换成为UTF-8的格式,就能够在本地方法中使用了。
注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出。
下面是Jni访问String对象的一些方法:
·GetStringUTFChars将jstring转换成为UTF-8格式的char*
·GetStringChars将jstring转换成为Unicode格式的char*
·ReleaseStringUTFChars释放指向UTF-8格式的char*的指针
·ReleaseStringChars释放指向Unicode格式的char*的指针
·NewStringUTF创建一个UTF-8格式的String对象
·NewString创建一个Unicode格式的String对象
·GetStringUTFLength获取UTF-8格式的char*的长度
·GetStringLength获取Unicode格式的char*的长度
下面提供两个String对象和char*互转的方法:
AndroidNDK开发简介
其实NDK的开发并不复杂,就入门而言甚至可以说是easyjob,觉得它难是难于C/C++代码的编写与调试。这个是我最近从事NDK开发的一点感受!首先,我们要弄懂几个概念,何为NDK,它和SDK以及JNI有什么关系?请前看下图:
JNI(JavaNativeInterface),Java的本地接口
咔咔,JNI的介绍就先说到这里,总之,JNI是一门技术,是JavaCode和C/C++Code联系的桥梁!
JNI开发的流程
1、编写JavaCode,如下面的例子:
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toast.makeText(getApplicationContext(),sayHellow(),Toast.LENGTH_LONG).show();
}
publicnativeStringsayHellow();//调用本地方法
static{
System.loadLibrary("Scgps_Client");//加载本地共享库
}
}
2、编写C/C++Code,如下面的例子:
#include"string.h"
#include"jni.h"
JNIEXPORTjstringJNICALLJava_com_scgps_client_MainActivity_sayHellow(JNIEnv*env,jobjectthiz)
{
constchar*ret="HellowFormNdk";
return(*env)->NewStringUTF(env,ret);
}
3、编译C/C++Code,成功并得到本地共享库
本地共享库是Linux下的叫法,文件扩展名是.so,windows下叫动态链接库,文件扩展名是.dll。前面说到C/C++才是跨平台之王,这就是其中的道理,面对不同的平台,编译不同的结果。相对于Java的一次编译到处运行的跨平台性牺牲运行效率,C/C++的跨平台性则是牺牲编译时间以及编译的难度。这里的编译难度是指为适应不同平台而做的编译过程的调整,这个活的难度可大可小,还不一定成功,视乎平台的兼容性以及支持。说到这里,难免会有人喷了:说什么跨平台性,这么复杂还不稳定!的确C/C++的跨平台性是有局限性的,但是纵观当前的各种平台和系统,有哪家是不支持C/C++本地开发的?只是各自提供的底层API和编译条件不同而已,只需要调整一下C/C++的编译代码,通过编译即可运行,难道也不是一件美事?
4、编译并打包Java
把本地共享库放置到Java项目的指定目录,一般是libs文件件,Android的项目是libs/armeabi(armeabi是对应的平台,后面会详讲),然后编译Java的代码即可运行!
NDK,(Nativedevelopkit),本地开发工具包
按照上图的解说,NDK处在开发流程的编译环节,对,简单来说,NDK是JNI开发的一个扩展工具包!针对Android平台,其支持的设备型号繁多,单单就设备的核心CPU而言,都有三大类:ARM、x86和MIPS,况且ARM又分为ARMv5和ARMv7等等,为何Android又能适配如此之多的设备?接着JNI开发流程的话,利用NDK,我们可以针对不同的手机设备,编译出对应可运行的本地共享库了,至于如何使用NDK进行编译、开发,我们留作下次再进行探讨。
SDK,(StandardDevelopKit),标准开发包
VM更适用于移动设备。一般而言,由于Google的SDK提供了强大又完善的API,开发一般需求的应用,SDK足矣。然而前面已经说过,Java的运行效率引发了不少问题,因而才有了JNI技术的存在,那SDK和NDK的关系是怎样的呢?见下图解说,可以说,NDK是SDK的一个补充。
SDK,JNI,NDK的开发流程
这个开发流程大致与JNI的开发流程差不多,下面我再详细说明一下每个环节:
SDK开发,编写Java代码,调用各种Android的API实现功能,编写含有native关键字的代码开始JNI;
JNI开发,按照
C/C++开发,编码实现业务逻辑,或调用NDK提供的本地API或库,完成Android平台上特定功能的开发、封装;
NDK编译,编写.mk文件,编译调试,最后修改.mk文件,针对特定的平台(ARM/x86)做编译结果的优化;
最后就是SDK编译、打包,上真机调试了...
AndroidNDK开发之Jni的数据类型
在前面的一篇博客《JNI是Java代码和C/C++代码通信的桥梁,其角色在某种意义上就是一个翻译员,从设计模式来看叫适配器。
两者的沟通,首要的一定要对嘴型,对channel,沟通才能到位。计算机程序的基本组成,从狭义来讲,就是数据结构+算法。由于Java和C/C++是两种不同的编程语言,它们各自拥有自家定义的数据类型和结构。JNI的第一步就是统一转换其中一方的数据类型,这就好比我们跟外国友人沟通,我们得说英语一样子。下表是Java的8大基本类型,在Jni层对应的数据描述:
Java | Native(jni.h) |
boolean | jboolean |
byte | jbyte |
char | jchar |
short | jshort |
int | jint |
long | jlong |
float | jfloat |
double | jdouble |
这里补充说明一下:
1.Java中的返回值void和JNI中的void是完全对应的
2.Java中的基本数据类型(boolean,byte,char,short,int,long,float,double),在JNI中对应的数据类型只要在前面加上j就对应了(jboolean,jbyte,jchar,jshort,jint,jlong,jfloat,jdouble)
3.Java中的对象,包括类库中定义的类、接口以及自定义的类接口,都对应于JNI中的jobject
4.Java中基本数据类型的数组对应与JNI中的jarray类型。(type就是上面说的8种基本数据类型)
5.Java中对象的数组对应于JNI中的jobjectArray类型。(在Java中一切对象、接口以及数组都是对象)
关于数据类型的转换,JNI还提供的强悍的函数库来支持。对于基本的类型的转换,我们先来复习一下,先关注一下Java基本类型的精度。
类型 | 字节数 | 范围/精度 |
float | 4 | 32位IEEE754单精度 |
double | 8 | 64位IEEE754双精度 |
byte | 1 | -128到127 |
short | 2 | -32,768到32,767 |
int | 4 | -2,147,483,648到2,147,483,647 |
long | 8 | -9,223,372,036,854,775,808到9,223,372,036,854,775,807 |
char | 2 | 整个Unicode字符集 |
boolean | 1 | True或者false |
像byte的范围是-128到127,你想要变为0到255怎么办,,跟0XFF做与运算就可以了:b&0XFF
如byteb,如果你想赋值它值255,那是不行的,就算赋值了,b的值也是255对256求模后的值-1,即b=-1,然后b&0XFF结果即为255,这个与运算后的结果会隐式转换为int类型的,因为byte放不下了,与运算还是很快的,,比加减法还快的。
所以Jni层使用Java的基本类型数据,对于上面八种基本的数据类型,jni层的c/c++代码可以用强制直接转换成对应长度的c/c++类型数据。
如:unsignedchartmp=(unsignedchar)m_jboolean;
unsignedshorttmp=(unsignedshort)m_jchar;
或者同长度类型的数据,可以直接赋值,inttmp=m_jint;
AndroidNDK开发之数组类型的操作
Jni可以通过JNIEnv提供的方法,对传过来的Java数组进行相应的操作。它提供了两种函数:一种是操作Java的简单型数组的,另一种是操作对象类型数组的。操作Java的简单型数组
因为速度的原因,简单类型的Java数组,会作为指向本地类型的指针暴露给本地代码调用。因此,它们能作为常规的数组存取。这个指针是指向实际的Java数组或者Java数组的拷贝的指针。另外,数组的布置保证匹配本地类型。在C/C++中,jintArray不能用下标对其进行直接存取,必须用到JNI中提供的接口函数进行操作。为了存取Java简单类型的数组,就要要使用GetXXXArrayElements函数(见表),XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。
函数 | Java数组类型 | 本地类型 |
GetBooleanArrayElements | jbooleanArray | jboolean |
GetByteArrayElements | jbyteArray | jbyte |
GetCharArrayElements | jcharArray | jchar |
GetShortArrayElements | jshortArray | jshort |
GetIntArrayElements | jintArray | jint |
GetLongArrayElements | jlongArray | jlong |
GetFloatArrayElements | jfloatArray | jfloat |
GetDoubleArrayElements | jdoubleArray | jdouble |
JNIEXPORTjintJNICALLJava_IntArray_sumArray(JNIEnv*env,jobjectobj,jintArrayarr)
{
jint*carr;
carr=(*env)->GetIntArrayElements(env,arr,false);//获得Java数组arr的引用的指针
if(carr==NULL){
return0;/*exceptionoccurred*/
}
jintsum=0;
for(inti=0;i<10;i++){
sum+=carr[i];
}
(*env)->ReleaseIntArrayElements(env,arr,carr,0);
returnsum;
}
操作对象类型数组
在C/C++代码中,int类型的数组对应JNI中的jintArray,而类似字符串数组这种类型的,在Jni里对应的使用jobjectArray来声明,下面是存取访问jobjectArray的方法简介:GetObjectArrayElement(JNIEnv*env,jobjectArrayarray,jsizeindex)
array:areferencetothejava.lang.Objectarrayfromwhichtheelementwillbeaccessed.
index:thearrayindex
功能:返回对应索引值的object.返回的是一个数组元素的值。
SetObjectArrayElement(JNIEnv*env,jobjectArrayarray,jsizeindex,jobjectvalue)
array:areferencetoanarraywhoseelementwillbeaccessed.
index:indexofthearrayelementtobeaccessed.
value:thenewvalueofthearrayelement.
功能:用来设置对应索引元素的值。
Get/SetXXXArrayRegion函数说明
GetIntArrayRegion(array,jsizestart,jsizelen,*buf)
array:areferencetoanarraywhoseelementsaretobecopied.
start:thestartingindexofthearrayelementstobecopied.
len:thenumberofelementstobecopied.
buf:thedestinationbuffer.
功能:把jintArray中的元素复制到buffer中。
SetIntArrayRegion(array,jsizestart,jsizelen,*buf)
array:areferencetoaprimitivearraytowhichtheelementstobecopied.
start:thestartingindexintheprimitivearray.
len:thenumberofelementstobecopied.
buf:thesourcebuffer.
功能:把buf中的元素copy到jintArray中去。
使用SetXXXArrayRegion与GetXXXArrayRegion就是以复制的方式设置与取出Array数组中的某个值。
关于二维数组和String数组
在Jni中,二维数组和String数组都被视为object数组,因为Array和String被视为object。下面例子实现了构造并返回一个二维int数组:JNIEXPORTjobjectArrayJNICALLJava_ObjectArrayTest_initInt2DArray(JNIEnv*env,jclasscls,intsize)
{
jobjectArrayresult;
jclassintArrCls=(*env)->FindClass(env,"[I");//int数组的class
result=(*env)->NewObjectArray(env,size,intArrCls,NULL);//二维int数组的实例
for(inti=0;i<size;i++){//初始化
jinttmp[256];/*makesureitislargeenough!*/
for(intj=0;j<size;j++){
tmp[j]=i+j;
}
jintArrayiarr=(*env)->NewIntArray(env,size);
(*env)->SetIntArrayRegion(env,iarr,0,size,tmp);//将tmp复制到iarr中
(*env)->SetObjectArrayElement(env,result,i,iarr);
(*env)->DeleteLocalRef(env,iarr);
}
returnresult;
}
最后得特别说明一下,当你使用对数组进行访问后,要确保调用相应的ReleaseXXXArrayElements函数,参数是对应Java数组和GetXXXArrayElements返回的指针,如果必要的话,这个释放函数会复制你做的任何变化(这样它们就反射到java数组),然后释放所有相关的资源,避免发生内存泄漏。
AndroidNDK开发之Jni调用Java对象
本地代码中使用Java对象
通过使用合适的JNI函数,你可以创建Java对象,get、set静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数。下表列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。
函数 | 描述 |
GetFieldID | 得到一个实例的域的ID |
GetStaticFieldID | 得到一个静态的域的ID |
GetMethodID | 得到一个实例的方法的ID |
GetStaticMethodID | 得到一个静态方法的ID |
jclasscls=(*env)->FindClass(env,"Lpackagename/classname;");//创建一个class的引用
jmethodIDid=(*env)->GetMethodID(env,cls,"","(D)V");//注意这里方法的名称是"",它表示这是一个构造函数,而且构造参数是double型的
jobjectobj=(*env)->NewObjectA(env,cls,id,args);//获得一实例,args是构造函数的参数,它是一个jvalue*类型。
首先是获得一个Java类的class引用(*env)->FindClass(env,"Lpackagename/classname;");请注意参数:Lpackagename/classname;,L代表这是在描述一个对象类型,packagename/classname是该对象耳朵class路径,请注意一定要以分号(;)结束!
然后是获取函数的id,jmethodIDid=env->GetMethodID(cls,"","(D)V");第一个是刚刚获得的class引用,第二个是方法的名称,最后一个就是方法的签名了
还是不懂?我曾经如此,请接着看...
难理解的函数签名
JNINativeMethod的定义如下:typedefstruct{
constchar*name;
constchar*signature;
void*fnPtr;
}JNINativeMethod;
第一个变量name是Java中函数的名字。
第二个变量signature,用字符串是描述了函数的参数和返回值
第三个变量fnPtr是函数指针,指向C函数。
其中比较难以理解的是第二个参数,例如
"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/String;)V"
实际上这些字符是与函数的参数类型一一对应的。
"()"中的字符表示参数,后面的则代表返回值。例如"()V"就表示voidFunc();
"(II)V"表示voidFunc(int,int);
那其他情况呢?请查看下表:
类型 | 符号 |
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | L |
float | F |
double | D |
void | V |
object对象 | LClassName;L类名; |
Arrays | [array-type[数组类型 |
methods方法 | (argument-types)return-type(参数类型)返回类型 |
1、方法参数或者返回值为java中的对象时,签名中必须以“L”加上其路径,不过此路径必须以“/”分开,自定义的对象也使用本规则
比如说java.lang.String为“java/lang/String”,com.nedu.jni.helloword.Student为"Lcom/nedu/jni/helloword/Student;"
2、方法参数或者返回值为数组类型时,请前加上[
例如[I表示int[],[[[D表示double[][][],即几维数组就加几个[
在本地方法中调用Java对象的方法
1、获取你需要访问的Java对象的类:
jclasscls=(*env)->GetObjectClass(env,obj);//使用GetObjectClass方法获取obj对应的jclass。
jclasscls=(*env)->FindClass(“android/util/log”)//直接搜索类名,需要是static修饰的类。
2、获取MethodID:
jmethodIDmid=(*env)->GetMethodID(env,cls,"callback","(I)V");//GetStaticMethodID(…),获取静态方法的ID使用GetMethdoID方法获取你要使用的方法的MethdoID
其参数的意义:
env-->JNIEnv
cls-->第一步获取的jclass
"callback"-->要调用的方法名
"(I)V"-->方法的Signature,签名同前面的JNI规则。
3、调用方法:
(*env)->CallVoidMethod(env,obj,mid,depth);//CallStaticIntMethod(….),调用静态方法
使用CallVoidMethod方法调用方法。参数的意义:
env-->JNIEnv
obj-->通过本地方法穿过来的jobject
mid-->要调用的MethodID(即第二步获得的MethodID)
depth-->方法需要的参数(对应方法的需求,添加相应的参数)
注:这里使用的是CallVoidMethod方法调用,因为没有返回值,如果有返回值的话使用对应的方法,在后面会提到。
CallVoidMethodCallStaticVoidMethod
CallIntMethodCallStaticVoidMethod
CallBooleanMethodCallStaticVoidMethod
CallByteMethodCallStaticVoidMethod
现在稍稍明白文章开始构造Java对象那个实例了吧?让我们继续深入一下:
Jni操作Java的String对象
从java程序中传过去的String对象在本地方法中对应的是jstring类型,jstring类型和c中的char*不同,所以如果你直接当做char*使用的话,就会出错。因此在使用之前需要将jstring转换成为c/c++中的char*,这里使用JNIEnv提供的方法转换。constchar*str=(*env)->GetStringUTFChars(env,jstr,0);
(*env)->ReleaseStringUTFChars(env,jstr,str);
这里使用GetStringUTFChars方法将传进来的prompt(jstring类型)转换成为UTF-8的格式,就能够在本地方法中使用了。
注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出。
下面是Jni访问String对象的一些方法:
·GetStringUTFChars将jstring转换成为UTF-8格式的char*
·GetStringChars将jstring转换成为Unicode格式的char*
·ReleaseStringUTFChars释放指向UTF-8格式的char*的指针
·ReleaseStringChars释放指向Unicode格式的char*的指针
·NewStringUTF创建一个UTF-8格式的String对象
·NewString创建一个Unicode格式的String对象
·GetStringUTFLength获取UTF-8格式的char*的长度
·GetStringLength获取Unicode格式的char*的长度
下面提供两个String对象和char*互转的方法:
/*c/c++stringturntojavajstring*/
jstringcharToJstring(JNIEnv*env,constchar*pat)
{
jclassstrClass=(*env)->FindClass(env,"java/lang/String");
jmethodIDctorID=(*env)->GetMethodID(env,strClass,"","([BLjava/lang/String;)V");
jbyteArraybytes=(*env)->NewByteArray(env,strlen(pat));
(*env)->SetByteArrayRegion(env,bytes,0,strlen(pat),(jbyte*)pat);
jstringencoding=(*env)->NewStringUTF(env,"UTF-8");
return(jstring)(*env)->NewObject(env,strClass,ctorID,bytes,encoding);
}
/*javajstringturntoc/c++char**/
char*jstringToChar(JNIEnv*env,jstringjstr)
{
char*pStr=NULL;
jclassjstrObj=(*env)->FindClass(env,"java/lang/String");
jstringencode=(*env)->NewStringUTF(env,"utf-8");
jmethodIDmethodId=(*env)->GetMethodID(env,jstrObj,"getBytes","(Ljava/lang/String;)[B");
jbyteArraybyteArray=(jbyteArray)(*env)->CallObjectMethod(env,jstr,methodId,encode);
jsizestrLen=(*env)->GetArrayLength(env,byteArray);
jbyte*jBuf=(*env)->GetByteArrayElements(env,byteArray,JNI_FALSE);
if(jBuf>0)
{
pStr=(char*)malloc(strLen+1);
if(!pStr)
{
returnNULL;
}
memcpy(pStr,jBuf,strLen);
pStr[strLen]=0;
}
env->ReleaseByteArrayElements(byteArray,jBuf,0);
returnpStr;
}
相关文章推荐
- JNI数据类型的详解--Android的NDK开发(3)
- Android的NDK开发(3)————JNI数据类型的详解
- Android的NDK开发(3)————JNI数据类型的详解
- Android的NDK开发(3)————JNI数据类型的详解
- JNI/NDK开发指南(三)——JNI数据类型及与Java数据类型的映射关系
- Android的NDK开发(3)————JNI数据类型的详解
- Android的NDK开发(3)————JNI数据类型的详解
- NDK开发 - JNI数据类型与Java数据类型映射关系
- NDK开发(三):JNI数据类型的详解
- Android的NDK开发(3)————JNI数据类型的详解
- Android的NDK开发(3)——JNI数据类型的详解
- JNI/NDK开发指南(四)--访问数组(基本类型数组与对象数)
- Android的NDK开发(3)————JNI数据类型的详解
- Android的NDK开发(3)——JNI数据类型的详解
- Android的NDK开发(3)————JNI数据类型的详解
- JNI/NDK开发指南(三)——JNI数据类型及与Java数据类型的映射关系
- (4.1.27.5) JNI/NDK开发指南(二)——JNI数据类型及与Java数据类型的映射关系
- JNI/NDK开发指南(三)——JNI数据类型及与Java数据类型的映射关系
- 【Android开发】NDK开发(2)-jni数据类型
- Android的NDK开发(3)————JNI数据类型的详解