OPhone平臺Native開發與JNI機制詳解
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進行編譯也很簡單:首先從命令行進入
【編輯推薦】