标签:
jni 2:
转载自:http://blog.csdn.net/xcy2011sky/article/details/40889533
在Android的应用层开发过程中会经常使用ndk编译本地库。在刚开始接触jni时,我使用javah自动生成jni的头文件,但是在后期看android的源码过程中,
又发现了另外一种jni的注册方法。为此这个问题,专门查找各种资料终于弄明白了其中的原理。
1.JNI的注册方法
jni的注册分为静态注册和动态注册。
静态注册:
这种方法就是根据java的函数名来找对应的JNI函数,主要使用java本身自带的javah 来生成。
注册流程:
这种方法是如何找到对应的jni函数的呢,这个过程如下:
当java层调用native_init函数是,它从对应的jni的库中寻找Java_package_classname_native_init函数,如果没有找到
就报错,如果找到了,就把native_init 和Java_package_classname_native_init 关联在一起,保存在jni的函数指针中。以后在调用native_init 就会直接跳转到
对应的函数中。
这种方法的不足之处:
动态注册:
动态注册则是在编写native库时,就吧java的natvie方法和nativie的函数,通过一个关联的结构体关联在一起 JNINativeMethod
那android是如何使用这个结构体呢。看一下这个例子
其中name="processDirectory" ,signature="(Ljava/lang/String;Landroid/media/MediaScannerClient;)V" 函数指针 就是本地库函数名:
通过一个结构体就对应起来啦。那是如何注册到系统里面呢。看下面这个函数:
通过调用AndroidRuntime里面的注册函数把gMethod注册到系统里面呢。
如函数的注释所说,这儿只是简单的注册,它会在android_meida_MediaPlayer.cpp 的JNI_OnLoad 函数中调用。我们看一下这个函数:
由此可见,这meida相关的native的函数都在此调用啦。
其实这就是动态注册的流程:
当java层通过System.loadLibrary时,加载了JNI动态库后接着就会查找一个叫JNI_OnLoad的函数,如果有就调用太,这样就完成注册工作。
2.数据类型转化
上面将讲清楚注册过程,那边参数是如何传递呢。参数又分为基本类型和复杂类型:
在jni.h中有如下定义:
我从其他文章中找了2个表可以很清楚的说明参数的对应关系
表A
Java类型 | 本地类型 | 描述 |
boolean | jboolean | C/C++8位整型 |
byte | jbyte | C/C++带符号的8位整型 |
char | jchar | C/C++无符号的16位整型 |
short | jshort | C/C++带符号的16位整型 |
int | jint | C/C++带符号的32位整型 |
long | jlong | C/C++带符号的64位整型e |
float | jfloat | C/C++32位浮点型 |
double | jdouble | C/C++64位浮点型 |
Object | jobject | 任何Java对象,或者没有对应java类型的对象 |
Class | jclass | Class对象 |
String | jstring | 字符串对象 |
Object[] | jobjectArray | 任何对象的数组 |
boolean[] | jbooleanArray | 布尔型数组 |
byte[] | jbyteArray | 比特型数组 |
char[] | jcharArray | 字符型数组 |
short[] | jshortArray | 短整型数组 |
int[] | jintArray | 整型数组 |
long[] | jlongArray | 长整型数组 |
float[] | jfloatArray | 浮点型数组 |
double[] | jdoubleArray | 双浮点型数组 |
在本地代码中如何使用这些参数呢。针对基本类型,我们可以直接转化使用既可,对已数组和对象我们分别采用如下方式:
使用数组:
JNI通过JNIEnv提供的操作Java数组的功能。它提供了两个函数:一个是操作java的简单型数组的,另一个是操作对象类型数组的。
因为速度的原因,简单类型的数组作为指向本地类型的指针暴露给本地代码。因此,它们能作为常规的数组存取。这个指针是指向实际的Java数组或者Java数组的拷贝的指针。另外,数组的布置保证匹配本地类型。
为了存取Java简单类型的数组,你就要要使用GetXXXArrayElements函数(见表B),XXX代表了数组的类型。这个函数把Java数组看成参数,返回一个指向对应的本地类型的数组的指针。
表B
函数 | 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 |
JNI数组存取函数
当你对数组的存取完成后,要确保调用相应的Relea***XXArrayElements函数,参数是对应Java数组和 GetXXXArrayElements返回的指针。如果必要的话,这个释放函数会复制你做的任何变化(这样它们就反射到java数组),然后释放所有相 关的资源。
为了使用java对象的数组,你必须使用GetObjectArrayElement函数和SetObjectArrayElement函数,分别去get,set数组的元素。GetArrayLength函数会返回数组的长度。
清单D包含了一个简单的类,它演示了本地代码如何使用Java数组。这个本地实现循环遍历一个整型(int)数组,返回这些元素的总和。为简单起见,这个清单包含了java代码和本地实现。我已经省略了头文件,它可以很方便地通过javah得到。
在本地代码中访问JNI
使用对象
JNI提供的另外一个功能是在本地代码中使用Java对象。通过使用合适的JNI函数,你可以创建Java对象,get、set 静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或 方法的ID是任何处理域和方法的函数的必须参数。
表C列出了用以得到静态(static)和实例(instance)的域与方法的JNI函数。每个函数接受(作为参数)域或方法的类,它们的名称,符号和它们对应返回的jfieldID或jmethodID。
表C
函数 | 描述 |
GetFieldID | 得到一个实例的域的ID |
GetStaticFieldID | 得到一个静态的域的ID |
GetMethodID | 得到一个实例的方法的ID |
GetStaticMethodID | 得到一个静态方法的ID |
※域和方法的函数
如果你有了一个类的实例,它就可以通过方法GetObjectClass得到,或者如果你没有这个类的实例,可以通过FindClass得到。符号是从域的类型或者方法的参数,返回值得到字符串,如表D所示。
表D
Java 类型 | 符号 |
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | L |
float | F |
double | D |
void | V |
objects对象 | Lfully-qualified-class-name;L类名 |
Arrays数组 | [array-type [数组类型 |
methods方法 | (argument-types)return-type(参数类型)返回类型 |
※确定域和方法的符号
一旦你有了类和方法或者域的ID,你就能把它保存下来以后使用,而没有必要重复去获取。
有几个分别访问域和方法的函数。实例的域可以使用对应域的GetXXXField的变体函数访问。GetStaticXXXField函数用于静态类型。设置域的值,用SetXXXField 和SetStaticXXXField函数。表E包含了所有访问域的函数列表。
表E
Java 类型 | Method方法 |
boolean | GetBooleanField, GetStaticBooleanField, SetBooleanField,SetStaticBooleanField |
byte | GetByteField, GetStaticByteField, SetByteField, SetStaticByteField |
char | GetCharField, GetStaticCharField, SetCharField, SetStaticCharField |
short | GetShortField, GetStaticShortField, SetShortField, SetStaticShortField |
int | GetIntField, GetStaticIntField, SetIntField, SetStaticIntField |
long | GetLongField, GetStaticLongField, SetLongField, SetStaticLongField |
float | GetFloatField, GetStaticFloatField, SetFloatField, SetStaticFloatField |
double | GetDoubleField, GetStaticDoubleField, SetDoubleField, SetStaticDoubleField |
object | GetObjectField, GetStaticObjectField, SetObjectField, SetStaticObjectField |
※访问域的函数
另外,方法的访问是由CallXXXMethod 函数和CallStaticXXXMethod函数完成的,XXX表明了方法的返回值类型。这些函数的变体允许传递数组参数 (CallXXXMethodA and CallStaticXXXMethodA)或者传递一个可变大小的列表(CallXXXMethodV and CallStaticXXXMethodV)。
一个完整的列表
表F:一个完整的列表
返回类型 | 函数 |
boolean | CallBooleanMethod, CallBooleanMethodA, CallBooleanMethodV, CallStaticBooleanMethod, CallStaticBooleanMethodA, CallStaticBooleanMethodV |
byte | CallByteMethod, CallByteMethodA, CallByteMethodV, CallStaticByteMethod, CallStaticByteMethodA, CallStaticByteMethodV |
char | CallCharMethod, CallCharMethodA, CallCharMethodV, CallStaticCharMethod, CallStaticCharMethodA, CallStaticCharMethodV |
short | CallShortMethod, CallShortMethodA, CallShortMethodV, CallStaticShortMethod, CallStaticShortMethodA, CallStaticShortMethodV |
int | CallIntMethod, CallIntMethodA, CallIntMethodV, CallStaticIntMethod, CallStaticIntMethodA, CallStaticIntMethodV |
long | CallLongMethod, CallLongMethodA, CallLongMethodV, CallStaticLongMethod, CallStaticLongMethodA, CallStaticLongMethodV |
float | CallFloatMethod, CallFloatMethodA, CallFloatMethodV, CallStaticFloatMethod, CallStaticFloatMethodA, CallStaticFloatMethodV |
double | CallDoubleMethod, CallDoubleMethodA, CallDoubleMethodV, CallStaticDoubleMethod, CallStaticDoubleMethodA, CallStaticDoubleMethodV |
void | CallVoidMethod, CallVoidMethodA, CallVoidMethodV, CallStaticVoidMethod, CallStaticVoidMethodA, CallStaticVoidMethodV |
object | CallObjectMethod, CallObjectMethodA, CallObjectMethodV, CallStaticObjectMethod, CallStaticObjectMethodA, CallStaticObjectMethodV |
※方法访问函数
清单E演示了如何在本地代码中调用方法。本地方法printRandom得到了静态方法Math.random的ID,并且调用它几次,打印出结果。实例方法也一样处理。
当你关注java的扩展时,JNI是一个强大的工具,它不会严重降低可移植性。我这里只是接触它的表面,仅仅向你演示了JNI的能力和潜力
标签:
原文地址:http://www.cnblogs.com/samyboy/p/5604551.html