您的位置:首页 > 其它

有关ndk、jni开发流程、数据类型、数组操作

2014-08-06 15:48 323 查看
转自:http://zhiwei.neatooo.com/blog/detail?blog=513835c2bb0201464b000004

AndroidNDK开发简介

其实NDK的开发并不复杂,就入门而言甚至可以说是easyjob,觉得它难是难于C/C++代码的编写与调试。这个是我最近从事NDK开发的一点感受!

首先,我们要弄懂几个概念,何为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开发之Jni的数据类型

在前面的一篇博客《AndroidNDK开发简介》,我简单地说明了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
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

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数组),然后释放所有相关的资源,避免发生内存泄漏。
http://zhiwei.neatooo.com/blog/detail?blog=5142d8da75363a521d000006

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

构造一个Java对象的实例

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;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: