JNI的作用
JNI是Java Native Interface(Java本地接口)的缩写。是从Java继承而来的,在Android中JNI的作用大大增强了。Android作为一种嵌入式操作系统,有大量和驱动、硬件相关的功能必须在native层实现,另外一些注重性能、功耗的功能使用C/C++来实现也优于Java来实现。因此,在Android开发中,无论是应用开发,还是系统开发都离不开JNI。
Android中,Java层主要负责UI功能的实现,而C/C++层则完成一些复杂的算法以及和底层交互的功能,因此,Java层和C/C++层交互比较频繁,这就用到了JNI。
Java语音的执行,离不开虚拟机。因此,当需要在Java中调用C/C++层的函数时,需要告诉虚拟机哪个方法代表本地的函数,以及在哪里能找到这个函数,反之类似。但是这两者之间还是有区别的,从Java到C/C++建立的是函数间的关联;而从C/C++到Java,必须先得到Java对象的引用,才能调用该对象的方法。这是因为Java是“纯”面向对象的语言,所以从C/C++到Java,必须和对象打交道,而不想C/C++中对象和函数可以混用。
注意,无论从Java到C/C++,还是从C/C++到Java,两者是在一个线程中运行的。
JNI用法
从Java到C/C++
1、装在JNI动态库
为了使用JNI,在调用本地方法前必须把C/C++代码所在的动态库装载到进程的内存空间中。装载库文件调用的是System类的loadLibrary()方法,如下:
public static void loadLibrary(String libName)
参数是动态库文件名称的一部分。Android JNI动态库的名称必须以“lib”开头,这里传入的参数是去掉前缀“lib”和后缀“.so”的中间部分。由于不同平台动态库的后缀时不一样的,Java希望代码能够跨平台使用,所以参数去掉了和系统相关的部分。
调用loadLibrary()方法不需要指定库文件的路径,Android会在几个系统目录下查找动态库。
为了保证调用native方法前所需要的动态库已经加载,loadLibrary()的调用位置一般放在类的static代码块中,这样进程初始化时就能执行装载语句了。例如,AudioEffect类装载JNI库的代码如下:
public class AudioEffect {
static {
System.loadLibrary("audioeffect_jni");
native_init();
}
......
}
2、定义native方法
在Java类中定义native方法很简单,在方法前加上“native”关键字就可以了,例如:
// ---------------------------------------------------------
// Native methods called from the Java side
// --------------------
private static native final void native_init();
在native方法中,可以使用任何类型作为参数,包括基本对象类型、数组类型、负责对象等。
3、编写JNI动态库
JNI动态库中定义了一个“JNI_OnLoad()”的函数,这个函数在动态库加载后会被系统调用,用于完成JNI函数的注册。函数原型如下:
jint JNI_OnLoad(JavaVM* vm, void*)
在该函数中最重要的就是调用registerNativeMethods()函数,完成动态库中JNI函数的注册。所谓注册,就是通过一张表把Java类中定义的native方法和本地C函数联系起来,这样Dalvik虚拟机在解析Java类中的native方法时就能查找到对应的C函数。
下面看一个简单的例子:(media\jni\audioeffect\android_media_AudioEffect.cpp)
jint JNI_OnLoad(JavaVM* vm, void* reserved __unused)
{
......
if (register_android_media_AudioEffect(env) < 0) {
ALOGE("ERROR: AudioEffect native registration failed\n");
goto bail;
}
......
}
int register_android_media_AudioEffect(JNIEnv *env)
{
return AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods));
}
// Dalvik VM type signatures
static const JNINativeMethod gMethods[] = {
{"native_init", "()V", (void *)android_media_AudioEffect_native_init},
{"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;II[I[Ljava/lang/Object;Ljava/lang/String;)I",
(void *)android_media_AudioEffect_native_setup},
......
{"native_setParameter", "(I[BI[B)I", (void *)android_media_AudioEffect_native_setParameter},
......
};
static const char* const kClassPathName = "android/media/audiofx/AudioEffect";
registerNativeMtehods()函数的原型是:
int registerNativeMethods(JNIEnv* env, const char* classname, const JNINativeMethod* gMethods, int numMethods);
第二个参数classname是指Java类的权限定类名,但是名称中的“.”要换成“/”。例如上面例子。第三个参数是JNINativeMethod类型的数组,类型定义如下:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
类型定义中的成员变量name是指Java类中native方法的名称;signature是native方法的参数签名;fnPtr是指native方法对应的本地函数指针。
本地函数的原型要求如下:
返回类型 函数名(JNIEnv *env, jobject obj, ......);
第一个参数是JNI环境,第二个参数是指调用类的Java对象,后面省略号和具体方法的参数一致。具体例子如下:
static jint
android_media_AudioEffect_native_setEnabled(JNIEnv *env, jobject thiz, jboolean enabled)
{
......
}
4、参数签名
native方法的参数签名使用了一些缩写符号来代表参数类型,签名有参数加返回值组成,参数必须用小括号括起来,没有参数时使用一对空括号。如“(I)V”,表示方法有一个整形参数,无返回值;“([IZ)I”,表示方法有两个参数,第一个参数是整形数组,第二个参数是布尔型,返回类型是整形。
参数签名中数组的表示方法是 在基本类型符号前加上“[”。
复杂类型的参数签名格式是“L”加上“全限定类名”再加上“;”,例如
Ljava/lang/String;
从C/C++到Java
1、生产Java对象
在JNIEnv中生成一个Java对象可以使用函数NewObject(),函数原型如下:
jobject NewObject(jclass clazz, jmethodID methodID, ...)
clazz是指Java类对象(不是类的实例对象),可以通过函数FindClass()得到;参数methodID是指Java类的构造函数。
FindClass()函数原型如下:
jclass FindClass(const char* name);
name是指Java类的名称,如: “/java/lang/String”。jclass用来表示Java类。
调用一个Java对象的方法或者存取一个Java对象的域变量前,要先知道对应的Id。取得方法Id和域变量Id的函数原型如下:
jmethodID GetMethodID(jclass clazz, const char* name, const char* sig);
jfieldID GetFieldID(jclass clazz, const char* name, const char* sig);
参数name是指Java类的方法或成员变量名称;sig是指参数类型签名。
如果要获得一个Java类的构造函数,使用函数GetMethodID时,传入的参数name必须是“<init>”才行。
2、调用Java类的方法
env->CallStaticVoidMethod(
callbackInfo->audioEffect_class,
fields.midPostNativeEvent,
callbackInfo->audioEffect_ref, event, arg1, arg2, obj);
3、存取Java类的域变量
env->SetLongField(thiz, fields.fidJniData, (jlong)lpJniStorage);
JNI环境
待续。。。
ART带来的JNI变化
待续。。。