Java Native Interface
Android 作为一种嵌入式操作系统,有大量的驱动,硬件相关的功能必须在native层实现,另外,一些注重性能,功耗的功能用C/C++来实现也优于Java来实现。因此无论是应用级的开发还是系统级的开发都离不开JNI。Android 在编译,应用打包和安装,程序装载等各方面都对JNI进行了有力的支持。
Android 中,Java 层主要负责UI功能的实现,C/C++完成一些复杂的算法及和底层的交互功能。Java语言的执行,离不开虚拟机,因此当要在Java 代码中调用C/C++层的函数时,需要告诉虚拟机哪个方法代表本地的函数,以及在哪能找到这个函数,反之,也类似。但是也有区别:
Java调用 C/C++ 建立的是函数间的关联;
1.装载JNI 动态库
为了使用JNI,在调用本地方法前,必须把C/C++代码所在的动态库装载到进程的内存空间中,装载库文件调用的是System类的LoadLibrary()方法。
public static void loadLibrary(String libName)
loadLibrary() 方法的参数是动态库文件名称的一部分。Android JNI 动态库的名称必须以"lib"开头,这里传入的参数必须是去掉前缀lib以及后缀.so的中间部分。这是因为Java 希望代码可以跨平台使用,如:linux 下是.so, 而windows 下是.dll,所以传入的参数去掉了和系统相关的部分。
调用loadLibrary() 方法不需要指定库文件所在的路径,Android 会在几个系统目录下查找动态库。
为了保证调用native方法前,所需要的动态库已经加载,loadLibrary()的调用位置一般是放在类的static块中,这样进程初始化时就能执行装载语句了。如:
public class AudioEffect{
static{
System.loadLibrary("audioeffect_jni");
native_init();
}
}
2.定义native 方法
在Java类中定义native 方法很简单,在方法前面加上native 关键字就可以了,如:
private static native final void init();
3.编写JNI动态库
JNI动态库和非JNI动态库的区别是:JNI动态库中定义了一个名为“JNI_OnLoader”的函数,这个函数在动态库加载后会被系统调用,用于完成JNI函数的的注册。JNI_OnLoader()函数的原型如下:
jint JNI_OnLoad(JavaVM* vm, void*)
在JNI_OnLoader()函数中,最重要的一件事就是调用呢registerNativeMethods()函数完成动态库中JNI函数的注册,所谓注册,就是通过一张表把Java类中的native方法和本地的C函数联系起来,这样Dalvik虚拟机在解析Java类中的native方法时就能查找到对应的C函数。
例:
static JNINativeMethod gMethods[]= {
{"enableOperator", "(IZ) Z", (void*) NATIVE_enable_operator},
{"disableOperator", "(IZ) Z", (void*) NATIVE_disable_operator},
};
jnit JNI_OnLoad(JavaVM* vm, void* // reserved){
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_Ok){
return -1;
}
if(AndroidRuntime: :registerNativeMethods(env,"com/android/TestService", gMethods, NELEM(gMethods)) >= 0){
result = JNI_VERSION_1_4;
}
return result;
}
registerNativeMethods的函数原型是:
registerNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods)
其中第二个参数className 是指java的全限定类名,但是名称中的"."要换成“/”, 这是JNI的规定。
第三个参数gMethods是JNINativeMethod类型的数组,类型定义如下:
typedef struct {
const char* name;// Java类中native方法的名称
const char* signature; // native 方法的参数签名,签名由参数➕返回值组成,参数必须用小括号括起来,没有参数时也要使用一对空括号。
void* fnPtr; // native方法对应的本地函数指针,这里只能是void类型的函数指针,调用时再根据参数做强制类型转换
}JNINativeMethod;
4.复杂类型的参数签名格式是L +全限定类名+;如:String类的参数签名是“L/java/lang/String;”,对应JNI的类型为jstring,其余复杂的java类型对应的JNI类型都是jobject。
5.如何使用JNI类型
JNI类型不都是无符号的。
在JNIEnv中也定义了与数组相关的操作函数。如果需要知道数组的个数,可以调用GetArrayLength()函数。
原型是 jsize GetArrayLength(jarray array)
将JNI的数组类型转换为基本数组类型,每一种基本类型都有自己的转换,如int类型转换函数是:
jint* GetIntArrayElements(jintArray array, jboolean* isCopy)
返回的数组内存是在函数内分配的,用完后,必须是放掉,否则就会造成内存泄漏,int类型的释放函数为:
void ReleaseIntArraryElements(jintArray array, jint* elems, jint mode)
jstring 本质也是jobject类,使用前也要进行转换,使用完后同样要记得释放内存。
如果需要转换成utf-8的字符串,相关函数原型是:
jsize GetStringUTFLength(jstring string);
const char* GetStringUTFChars(jstring string, jboolean* isCopy);
void ReleaseStringUTFChars(jstring string, const char* utf);
需要转换成unicode的字符串
jsize GetStringLength(jstring string);
const char* GetStringChars(jstring string, jboolean* isCopy);
void ReleaseStringChars(jstring string, const char* utf);
如果一次只取一个数组的元素,可以使用下面的函数,也不用释放内存,更加方便jobject GetObjectArrayElement(jobjectArray array, jsize index);
C/C++调用 Java 必须先得到Java对象的引用,才能调用该对象的方法。
无论哪种调用,两者在一个线程中运行,但是在各自打印出的log中,线程ID是不同的,因为JAVA线程的ID是从1开始的整数,C/C++使用的是一个与线程相关的数据结构的指针。