android framework 之JNI

Java Native Interface ( JN I)是Java本地接口,所谓的本地(native) —般是指C/C++ ( 以下统称C)
语言。当使用Java进行程序设计时,一般主要有三种情况需要C/C++语言的协助。
• 调用驱动。由于操作系统所提供的驱动一般都是C/C++ 接口,Java语言本身不具备操作这些驱动的
能力。
• 对于某些大量数据处理的模块,Java的效率可能远低于C/C++,因此,程序员希望使用C/C++ 去完成。
• 对于某些功能模块,可能Java和C/C++ 的效率差不多,但是这些模块已经存在已有的C/C++ 代码,程序
员不想再用Java重写,而只想重新利用已有的C/C++ 代码。
这就是Java提出JNI概念的原因。无论出于什么原因,从程序的角度来看,JNI接口主要包含两种
情况。第一种是从Java中访问C/C++,第二种是从C/C++ 中访问Java,只要解决了这两个问题,那么就可以任
意进行Java和C/C++ 的应用组合。Framework中大量使用JNI完成本地接口的实现,因此,理解JNI是阅读kernel的重中之重。

1. java 访问 c、c++

Java中可以定义某个函数为native类型,对于native函数,只需要声明即可,因为该函数的实现是
native的,即由相应的C 去实现。Java编译器遇到native函数时,不会关心该函数的具体实现,因此,
编译上不会出任何差错。
程序运行时,在调用native方法之前,程序员必须把C 所生成的动态库装载进来,否则程序会因
为找不到相应的native方法而出错。关于如何把C 程序编译为动态库,将在第18章中的Make系统中
介绍,这牵扯到包含jni.h头文件等及编译选项的设置。
当调用native函数时,Java会自动产生一个对应的C 中的函数名称,因为Java中声明的函数名称
和C 中实现的函数名称是不同的。其关系为,后者等于包名加前者的名称,并且中间以下画线分隔,
比如,Framework中AssetManager类中声明了以下方法:

AssetManager.java (base\core\java\android\content\res)

private native final String[] getArrayStringResource(int arrayRes);

android_util_AssetManager.cpp (base\core\jni)中找到JNI关系映射数组:

static JNINativeMethod gAssetManagerMethods[] = {
...
// Arrays.
{ "getArrayStringResource","(I)[Ljava/lang/String;",
(void*) android_content_AssetManager_getArrayStringResource },
{ "getArrayStringInfo","(I)[I",
(void*) android_content_AssetManager_getArrayStringInfo },
{ "getArrayIntResource","(I)[I",
(void*) android_content_AssetManager_getArrayIntResource },
...
}

同时,我们可以在android_util_AssetManager.cpp (base\core\jni)中找到void*) android_content_AssetManager_getArrayStringResource的定义。

static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* env, jobject clazz,
jint arrayResId)
{
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return NULL;
}

.......

res.unlockBag(startOfBag);
return array;
}

以上这种映射关系并不是Java编译器内含的,程序员完全可以改变,但这是一种编程规范。事实上,
当Java调用native时,编译器会向native引擎传递调用者的包名,以及函数名称,还有参数类型,仅
此而已,当然这也足矣,native引擎根据这些信息决定应该具体调用哪个本地函数。native引擎中
AndroidRuntime类提供了一个registerNativeMethods()函数,可以通过该函数来定义Java native函数和C
函数名称的映射关系。

如下:

int register_android_content_AssetManager(JNIEnv* env)
{
...;
return AndroidRuntime::registerNativeMethods(env,
"android/content/res/AssetManager", gAssetManagerMethods, NELEM(gAssetManagerMethods));
}


在产生的C/C++ 函数中,会包含至少两个参数。前者是JNIEnv对象,该对象是一个Java虚拟机(JVM)
所运行的环境,相当于JVM的“管家”,通过它可以访问JVM内部的各种对象;第二个参数jobject是
调用该函数的对象,本例中指的就是AssetManager对象,第三个参数是java函数中的参数。

在以上的转换关系中,大家可能会注意到native中所使用的类型和Java中有所不同。比如Java中
的int在native中为jint,返回值中String□变为jobjectArray,这些具体的定义实际上是在jni.h中,该文
件所在的路径为:

Jni.h (kernel\android\jb\development\ndk\platforms\android-3\include)

里面还有其他类型的定义。

 

同时,不同的platforms会有不同的定义,这个很好理解,因为Java的类型是跨平台的,而各自平台的
CPU数据宽度是不同的,所以必须有各自的类型定义。
以上介绍了 Java和C 函数的名称转换,那么,具体怎么操作呢?在程序设计时,如果你已经定义
好了 Java代码,如何实现相应的native C 代码呢?
你可能会想:“那就按照这种转换关系,手工编写相应的C 代码,然后编译成动态库,并在Java
代码执行时加载该库就可以了(需要使用System.loadLibrary( “ lib—name” )函数装载该库。)。

没错,是这个样子,为了辅助你这样做,Java还提供了一个javah工
具,该工具可以从一个Java文件自动生成相应的头文件,剩下的就是你根据这些头文件再实现具体的
内部代码即可。

 

2.c/c++ 访问 java 

这种情况似乎比较少,C 为什么还要访问Java呢?这个也容易理解,如果C 中需要使用Java的某
个变量而进行相应的处理,或者C 中也想调用Java中的某个函数完成某些操作,那么C 就要访问Java。
由于Java中的函数在native引擎中并没有直接的函数指针,Java函数只能由Java引擎去执行,而
不是C。所以,C 访问Java不能通过函数指针,而只能通过通用的参数接口,正如Java调用C 一样。
Java把类名、函数名称、参数类型传递给native引擎,然后由native引擎处理C 函数,同理,C 调用
Java时,也需要把想要访问的类名、函数名称、参数传递给Java引擎。其步骤如下:

(1)获取Java对象的类。
cls = env->GetObjectClass(jobject);

其中env为Java调用C 函数时的第一个参数,这意味着C 调用Java函数只能在Java调用C 函数
中进行,否则无法获取env变量。换句话说,对于C 来讲,就是“你不惹我,我不惹你”。jobject为第
二个参数。cls的类型是jelass。

2.获取Java函数的id 值。
jmethodld mid = env->GetMethodId(cls,"method_name","([Ljava/lang/String;)V");

该方法中第二个参数为Java中的函数名称,第三个参数值得注意,它代表了 Java函数的参数和返
回值,参数在括弧之中,返回值在括弧之外。本例中,参数[Ljava/lang/String代表了 String类型的参数,
由于String本身是一个类,而不是Java的原子类型,所以前面加了包的名称,并用斜线分隔,最前面
还要用一个中括弧进行标识,后面还要用分号隔离。返回值V 代表void.

3.找到了函数后,就可以调用该函数了。
env->CallXXXMethod(jobject,mid,ret);
其中XXX代表了函数的返回值类型,具体包括Void、Object、Boolean、Byte、Char、Short、Int、
Long, Float、Double。在我看来,JNI提供的这种按类型调用并不是必需的,只是为了某种灵活,因为
该函数的第三个参数是保存返回值的变量,所以JN I内部完全可以根据ret的类型来选择把Java的执行
结果进行格式转换。第二个参数mid即为第二步中所获得的函数id。

通过以上三步,实现了 C 中调用Java函数的目标。还有一个问题,C 中如何访问Java中的变量呢?
实现步骤如下:

(1)获取Java对象的类。
cls = env->GetObjectClass(jobject);

2.获取Java变量的id 值。
jmethodld fid = env->GetfiledId(cls,"filed_name","I");

参数filed_name 为Java变量的名称,第三个参数为变量的类型,其格式与上面相同。

3.获取变量值。

value=env->GetXXXField(env,jobject,fid);
该函数的参数与上面的显著不同,其中第」、第二个参数为原装Java访问C 函数的前两个参数,
该方法以返回值的方式获取变量值,而不是通过参数引用。

posted @ 2014-04-09 11:39  偶的神!!  阅读(894)  评论(0编辑  收藏  举报