OPhone平臺Native開發(fā)與JNI機制詳解
JNI簡介
Java Native Interface(JNI)是Java提供的一個很重要的特性。它使得用諸如C/C++等語言編寫的代碼可以與運行于Java虛擬機(JVM)中的Java代碼集成。有些時候,Java并不能滿足你的全部開發(fā)需求,比如你希望提高某些關(guān)鍵模塊的效率,或者你必須使用某個以C/C++等Native語言編寫的程序庫;此時,JNI就能滿足你在Java代碼中訪問這些Native模塊的需求。JNI的出現(xiàn)使得開發(fā)者既可以利用Java語言跨平臺、類庫豐富、開發(fā)便捷等特點,又可以利用Native語言的高效。實際上,JNI是JVM實現(xiàn)中的一部分,因此Native語言和Java代碼都運行在JVM的宿主環(huán)境(Host Environment),正如圖1所示。此外,JNI是一個雙向的接口:開發(fā)者不僅可以通過JNI在Java代碼中訪問Native模塊,還可以在Native代碼中嵌入一個JVM,并通過JNI訪問運行于其中的Java模塊。可見,JNI擔(dān)任了一個橋梁的角色,它將JVM與Native模塊聯(lián)系起來,從而實現(xiàn)了Java代碼與Native代碼的互訪。在OPhone上使用Java虛擬機是為嵌入式設(shè)備特別優(yōu)化的Dalvik虛擬機。每啟動一個應(yīng)用,系統(tǒng)會建立一個新的進(jìn)程運行一個Dalvik虛擬機,因此各應(yīng)用實際上是運行在各自的VM中的。Dalvik VM對JNI的規(guī)范支持的較全面,對于從JDK 1.2到JDK 1.6補充的增強功能也基本都能支持。
開發(fā)者在使用JNI之前需要充分了解其優(yōu)缺點,以便合理選擇技術(shù)方案實現(xiàn)目標(biāo)。JNI的優(yōu)點前面已經(jīng)講過,這里不再重復(fù),其缺點也是顯而易見的:由于Native模塊的使用,Java代碼會喪失其原有的跨平臺性和類型安全等特性。此外,在JNI應(yīng)用中,Java代碼與Native代碼運行于同一個進(jìn)程空間內(nèi);對于跨進(jìn)程甚至跨宿主環(huán)境的Java與Native間通信的需求,可以考慮采用socket、Web Service等IPC通信機制來實現(xiàn)。 在OPhone開發(fā)中使用JNI 正如我們在上一節(jié)所述,JNI是一個雙向的接口,所以交互的類型可以分為在Java代碼中調(diào)用Native模塊和在Native代碼中調(diào)用Java模塊兩種。下面,我們就使用一個Hello-JNI的示例來分別對這兩種交互方式的開發(fā)要點加以說明。 Java調(diào)用Native模塊 Hello-JNI這個示例的結(jié)構(gòu)很簡單:首先我們使用Eclipse新建一個OPhone應(yīng)用的Java工程,并添加一個com.example.hellojni.HelloJni的類。這個類實際上是一個Activity,稍后我們會創(chuàng)建一個TextView,并顯示一些文字在上面。 要在Java代碼中使用Native模塊,必須先對Native函數(shù)進(jìn)行聲明。在我們的例子中,打開HelloJni.java文件,可以看到如下的聲明:#p#從上述聲明中我們可以知道,這個stringFromJNI()函數(shù)就是要在Java代碼中調(diào)用的Native函數(shù)。接下來我們要創(chuàng)建一個hello-jni.c的C文件,內(nèi)容很簡單,只有如下一個函數(shù):
- /* A native method that is implemented by the
- * 'hello-jni' native library, which is packaged
- * with this application.
- */
- public native String stringFromJNI();
從函數(shù)名可以看出,這個Native函數(shù)對應(yīng)的正是我們在com.example.hellojni.HelloJni這個中聲明的Native函數(shù)String stringFromJNI()的具體實現(xiàn)。 從上面Native函數(shù)的命名上我們可以了解到JNI函數(shù)的命名規(guī)則: Java代碼中的函數(shù)聲明需要添加native關(guān)鍵字;Native的對應(yīng)函數(shù)名要以“Java_”開頭,后面依次跟上Java的“package名”、“class名”、“函數(shù)名”,中間以下劃線“_”分割,在package名中的“.”也要改為“_”。此外,關(guān)于函數(shù)的參數(shù)和返回值也有相應(yīng)的規(guī)則。對于Java中的基本類型如int、double、char等,在Native端都有相對應(yīng)的類型來表示,如jint、jdouble、jchar等;其他的對象類型則統(tǒng)統(tǒng)由jobject來表示(String是個例外,由于其使用廣泛,故在Native代碼中有jstring這個類型來表示,正如在上例中返回值String對應(yīng)到Native代碼中的返回值jstring)。而對于Java中的數(shù)組,在Native中由jarray對應(yīng),具體到基本類型和一般對象類型的數(shù)組則有jintArray等和jobjectArray分別對應(yīng)(String數(shù)組在這里沒有例外,同樣用jobjectArray表示)。還有一點需要注意的是,在JNI的Native函數(shù)中,其前兩個參數(shù)JNIEnv*和jobject是必需的——前者是一個JNIEnv結(jié)構(gòu)體的指針,這個結(jié)構(gòu)體中定義了很多JNI的接口函數(shù)指針,使開發(fā)者可以使用JNI所定義的接口功能;后者指代的是調(diào)用這個JNI函數(shù)的Java對象,有點類似于C++中的this指針。在上述兩個參數(shù)之后,還需要根據(jù)Java端的函數(shù)聲明依次對應(yīng)添加參數(shù)。在上例中,Java中聲明的JNI函數(shù)沒有參數(shù),則Native的對應(yīng)函數(shù)只有類型為JNIEnv*和jobject的兩個參數(shù)。 當(dāng)然,要使用JNI函數(shù),還需要先加載Native代碼編譯出來的動態(tài)庫文件(在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 !");
- }
注意這里調(diào)用的共享庫名遵循Linux對庫文件的命名慣例,因為OPhone的核心實際上是Linux系統(tǒng)——上例中,實際加載的庫文件應(yīng)為“l(fā)ibhello-jni.so”,在引用時遵循命名慣例,不帶“l(fā)ib”前綴和“.so”的擴展名。對于沒有按照上述慣例命名的Native庫,在加載時仍需要寫成完整的文件名。 JNI函數(shù)的使用方法和普通Java函數(shù)一樣。在本例中,調(diào)用代碼如下:
- static {
- System.loadLibrary("hello-jni");
- }
就可以在TextView中顯示出來自于Native函數(shù)的字符串。怎么樣,是不是很簡單呢? Native調(diào)用Java模塊 從OPhone的系統(tǒng)架構(gòu)來看,JVM和Native系統(tǒng)庫位于內(nèi)核之上,構(gòu)成OPhone Runtime;更多的系統(tǒng)功能則是通過在其上的Application Framework以Java API的形式提供的。因此,如果希望在Native庫中調(diào)用某些系統(tǒng)功能,就需要通過JNI來訪問Application Framework提供的API。#p# JNI規(guī)范定義了一系列在Native代碼中訪問Java對象及其成員與方法的API。下面我們還是通過示例來具體講解。首先,新建一個SayHello的類,代碼如下:
- TextView tv = new TextView(this);
- tv.setText( stringFromJNI() );
- setContentView(tv);
接下來要實現(xiàn)的就是在Native代碼中調(diào)用這個SayHello類中的sayHelloFromJava方法。 一般來說,要在Native代碼中訪問Java對象,有如下幾個步驟: 1. 得到該Java對象的類定義。JNI定義了jclass這個類型來表示Java的類的定義,并提供了FindClass接口,根據(jù)類的完整的包路徑即可得到其jclass。 2. 根據(jù)jclass創(chuàng)建相應(yīng)的對象實體,即jobject。在Java中,創(chuàng)建一個新對象只需要使用new關(guān)鍵字即可,但在Native代碼中創(chuàng)建一個對象則需要兩步:首先通過JNI接口GetMethodID得到該類的構(gòu)造函數(shù),然后利用NewObject接口構(gòu)造出該類的一個實例對象。 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的第三個參數(shù)是方法名(對于構(gòu)造函數(shù)一律用“
- 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進(jìn)行編譯也很簡單:首先從命令行進(jìn)入
【編輯推薦】