JNI简介
Java Native Interface(JNI)是Java提供的一个很重要的特性。它使得用诸如C/C++等语言编写的代码可以与运行于Java虚拟机(JVM)中的Java代码集成。有些时候,Java并不能满足你的全部开发需求,比如你希望提高某些关键模块的效率,或者你必须使用某个以C/C++等Native语言编写的程序库;此时,JNI就能满足你在Java代码中访问这些Native模块的需求。JNI的出现使得开发者既可以利用Java语言跨平台、类库丰富、开发便捷等特点,又可以利用Native语言的高效。实际上,JNI是JVM实现中的一部分,因此Native语言和Java代码都运行在JVM的宿主环境(Host Environment),正如图1所示。此外,JNI是一个双向的接口:开发者不仅可以通过JNI在Java代码中访问Native模块,还可以在Native代码中嵌入一个JVM,并通过JNI访问运行于其中的Java模块。可见,JNI担任了一个桥梁的角色,它将JVM与Native模块联系起来,从而实现了Java代码与Native代码的互访。在OPhone上使用Java虚拟机是为嵌入式设备特别优化的Dalvik虚拟机。每启动一个应用,系统会建立一个新的进程运行一个Dalvik虚拟机,因此各应用实际上是运行在各自的VM中的。Dalvik VM对JNI的规范支持的较全面,对于从JDK 1.2到JDK 1.6补充的增强功能也基本都能支持。
开发者在使用JNI之前需要充分了解其优缺点,以便合理选择技术方案实现目标。JNI的优点前面已经讲过,这里不再重复,其缺点也是显而易见的:由于Native模块的使用,Java代码会丧失其原有的跨平台性和类型安全等特性。此外,在JNI应用中,Java代码与Native代码运行于同一个进程空间内;对于跨进程甚至跨宿主环境的Java与Native间通信的需求,可以考虑采用socket、Web Service等IPC通信机制来实现。 在OPhone开发中使用JNI 正如我们在上一节所述,JNI是一个双向的接口,所以交互的类型可以分为在Java代码中调用Native模块和在Native代码中调用Java模块两种。下面,我们就使用一个Hello-JNI的示例来分别对这两种交互方式的开发要点加以说明。 Java调用Native模块 Hello-JNI这个示例的结构很简单:首先我们使用Eclipse新建一个OPhone应用的Java工程,并添加一个com.example.hellojni.HelloJni的类。这个类实际上是一个Activity,稍后我们会创建一个TextView,并显示一些文字在上面。 要在Java代码中使用Native模块,必须先对Native函数进行声明。在我们的例子中,打开HelloJni.java文件,可以看到如下的声明:#p#从上述声明中我们可以知道,这个stringFromJNI()函数就是要在Java代码中调用的Native函数。接下来我们要创建一个hello-jni.c的C文件,内容很简单,只有如下一个函数:
- /* A native method that is implemented by the
- * 'hello-jni' native library, which is packaged
- * with this application.
- */
- public native String stringFromJNI();
从函数名可以看出,这个Native函数对应的正是我们在com.example.hellojni.HelloJni这个中声明的Native函数String stringFromJNI()的具体实现。 从上面Native函数的命名上我们可以了解到JNI函数的命名规则: Java代码中的函数声明需要添加native关键字;Native的对应函数名要以“Java_”开头,后面依次跟上Java的“package名”、“class名”、“函数名”,中间以下划线“_”分割,在package名中的“.”也要改为“_”。此外,关于函数的参数和返回值也有相应的规则。对于Java中的基本类型如int、double、char等,在Native端都有相对应的类型来表示,如jint、jdouble、jchar等;其他的对象类型则统统由jobject来表示(String是个例外,由于其使用广泛,故在Native代码中有jstring这个类型来表示,正如在上例中返回值String对应到Native代码中的返回值jstring)。而对于Java中的数组,在Native中由jarray对应,具体到基本类型和一般对象类型的数组则有jintArray等和jobjectArray分别对应(String数组在这里没有例外,同样用jobjectArray表示)。还有一点需要注意的是,在JNI的Native函数中,其前两个参数JNIEnv*和jobject是必需的——前者是一个JNIEnv结构体的指针,这个结构体中定义了很多JNI的接口函数指针,使开发者可以使用JNI所定义的接口功能;后者指代的是调用这个JNI函数的Java对象,有点类似于C++中的this指针。在上述两个参数之后,还需要根据Java端的函数声明依次对应添加参数。在上例中,Java中声明的JNI函数没有参数,则Native的对应函数只有类型为JNIEnv*和jobject的两个参数。 当然,要使用JNI函数,还需要先加载Native代码编译出来的动态库文件(在Windows上是.dll,在Linux上则为.so)。这个动作是通过如下语句完成的:
- #include <string.h>
- #include <jni.h>
- jstring
- Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
- jobject thiz ) {
- return (*env)->NewStringUTF(env, "Hello from JNI !");
- }
注意这里调用的共享库名遵循Linux对库文件的命名惯例,因为OPhone的核心实际上是Linux系统——上例中,实际加载的库文件应为“libhello-jni.so”,在引用时遵循命名惯例,不带“lib”前缀和“.so”的扩展名。对于没有按照上述惯例命名的Native库,在加载时仍需要写成完整的文件名。 JNI函数的使用方法和普通Java函数一样。在本例中,调用代码如下:
- static {
- System.loadLibrary("hello-jni");
- }
就可以在TextView中显示出来自于Native函数的字符串。怎么样,是不是很简单呢? Native调用Java模块 从OPhone的系统架构来看,JVM和Native系统库位于内核之上,构成OPhone Runtime;更多的系统功能则是通过在其上的Application Framework以Java API的形式提供的。因此,如果希望在Native库中调用某些系统功能,就需要通过JNI来访问Application Framework提供的API。#p# JNI规范定义了一系列在Native代码中访问Java对象及其成员与方法的API。下面我们还是通过示例来具体讲解。首先,新建一个SayHello的类,代码如下:
- TextView tv = new TextView(this);
- tv.setText( stringFromJNI() );
- setContentView(tv);
接下来要实现的就是在Native代码中调用这个SayHello类中的sayHelloFromJava方法。 一般来说,要在Native代码中访问Java对象,有如下几个步骤: 1. 得到该Java对象的类定义。JNI定义了jclass这个类型来表示Java的类的定义,并提供了FindClass接口,根据类的完整的包路径即可得到其jclass。 2. 根据jclass创建相应的对象实体,即jobject。在Java中,创建一个新对象只需要使用new关键字即可,但在Native代码中创建一个对象则需要两步:首先通过JNI接口GetMethodID得到该类的构造函数,然后利用NewObject接口构造出该类的一个实例对象。 3. 访问jobject中的成员变量或方法。访问对象的方法是先得到方法的Method ID,然后使用Call
- package com.example.hellojni;
- public class SayHello {
- public String sayHelloFromJava(String nativeMsg) {
- String str = nativeMsg + " But shown in Java!";
- return str;
- }
- }
可以看到,上述代码和前面讲到的步骤完全相符。这里提一下编程时要注意的要点:1、FindClass要写明Java类的完整包路径,并将“.”以“/”替换;2、GetMethodID的第三个参数是方法名(对于构造函数一律用“
- jstring helloFromJava( JNIEnv* env ) {
- jstring str = NULL;
- jclass clz = (*env)->FindClass(env, "com/example/hellojni/SayHello");
- jmethodID ctor = (*env)->GetMethodID(env, clz, "<init>", "()V");
- jobject obj = (*env)->NewObject(env, clz, ctor);
- jmethodID mid = (*env)->GetMethodID(env, clz, "sayHelloFromJava", "(Ljava/lang/String;)Ljava/lang/String;");
- if (mid) {
- jstring jmsg = (*env)->NewStringUTF(env, "I'm born in native.");
- str = (*env)->CallObjectMethod(env, obj, mid, jmsg);
- }
- return str;
- }
- LOCAL_PATH := $(call my-dir)
- include $(CLEAR_VARS)
- LOCAL_MODULE := hello-jni
- LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
- LOCAL_SRC_FILES := src/call_java.c \
- src/hello-jni.c
- include $(BUILD_SHARED_LIBRARY)
写好了代码和Makefile,接下来就是编译了。使用NDK进行编译也很简单:首先从命令行进入
【编辑推荐】