1. 程式人生 > >Android平臺Native開發與JNI機制

Android平臺Native開發與JNI機制

JNI的出現使得開發者既可以利用Java語言跨平臺、類庫豐 富、開發便捷等特點,又可以利用Native語言的高效。


JNI是JVM實現中的一部分,因此Native語言和Java程式碼都執行在JVM的宿主環境。

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補充的增強功能也基本都能支援。

缺點:由於Native模組的使用,Java程式碼會喪失其原有的跨平臺性和型別安全等特性。此外,在JNI應用中,Java程式碼與Native代 碼運行於同一個程序空間內;對於跨程序甚至跨宿主環境的Java與Native間通訊的需求,可以考慮採用socket、Web Service等IPC通訊機制來實現。

互的型別可以分為在Java程式碼中呼叫Native模組和在Native程式碼中呼叫Java模組兩種。

Java呼叫Native模組

HelloJni.java

  1. /* A native method that is implemented by the   
  2.    * 'hello-jni' native library, which is packaged   
  3.    * with this application.   
  4.    */    
  5.   public native String  stringFromJNI();  

這個stringFromJNI()函式就是要在Java程式碼中呼叫的Native函式。

hello-jni.c

  1. #include <string.h>
  2. #include <jni.h>
  3. jstring    
  4. Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,    
  5.                                                  jobject thiz ) {    
  6.         return (*env)->NewStringUTF(env, "Hello from JNI !");    
  7. }    

這個Native函式對應的正是我們在com.example.hellojni.HelloJni這個中宣告的Native函式String stringFromJNI()的具體實現。

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)。這個動作是通過如下語句完成的:

  1. static {  
  2.     System.loadLibrary("hello-jni");  
  3. }  
JNI函式的使用方法和普通Java函式一樣。在本例中,呼叫程式碼如下:
  1. TextView tv = new TextView(this);  
  2. tv.setText( stringFromJNI() );  
  3. setContentView(tv);  
Native呼叫Java模組

從OPhone的系統架構來看,JVM和Native系統庫位於核心之上,構成OPhone Runtime;更多的系統功能則是通過在其上的Application Framework以Java API的形式提供的。因此,如果希望在Native庫中呼叫某些系統功能,就需要通過JNI來訪問Application Framework提供的API。

  1. package com.example.hellojni;  
  2. public class SayHello {  
  3.         public String sayHelloFromJava(String nativeMsg) {  
  4.                String str = nativeMsg + " But shown in Java!";  
  5.                return str;  
  6.         }  
  7. }  
一般來說,要在Native程式碼中訪問Java物件,有如下幾個步驟: 1.         得到該Java物件的類定義。JNI定義了jclass 這個型別來表示Java的類的定義,並提供了FindClass介面,根據類的完整的包路徑即可得到其jclass 。 2.         根據jclass 建立相應的物件實體,即jobject 。在Java中,建立一個新物件只需要使用new 關鍵字即可,但在Native程式碼中建立一個物件則需要兩步:首先通過JNI介面GetMethodID得到該類的建構函式,然後利用NewObject介面構造出該類的一個例項物件。 3.         訪問jobject 中的成員變數或方法。訪問物件的方法是先得到方法的Method ID,然後使用Call<Type>Method 介面呼叫,這裡Type對應相應方法的返回值——返回值為基本型別的都有相對應的介面,如CallIntMethod;其他的返回值(包括String) 則為CallObjectMethod。可以看出,建立物件實質上是呼叫物件的一個特殊方法,即建構函式。訪問成員變數的步驟一樣:首先 GetFieldID得到成員變數的ID,然後Get/Set<Type>Field讀/寫變數值。
  1. jstring helloFromJava( JNIEnv* env ) {  
  2.        jstring str = NULL;  
  3.        jclass clz = (*env)->FindClass(env, "com/example/hellojni/SayHello");  
  4.        jmethodID ctor = (*env)->GetMethodID(env, clz, "<init>""()V");  
  5.        jobject obj = (*env)->NewObject(env, clz, ctor);  
  6.        jmethodID mid = (*env)->GetMethodID(env, clz, "sayHelloFromJava""(Ljava/lang/String;)Ljava/lang/String;");  
  7.        if (mid) {  
  8.               jstring jmsg = (*env)->NewStringUTF(env, "I'm born in native.");  
  9.               str = (*env)->CallObjectMethod(env, obj, mid, jmsg);  
  10.        }  
  11.        return str;  
  12. }  
提一下程式設計時要注意的要點:1、FindClass要寫明Java類的完整包路徑,並將 “.”以“/”替換;2、GetMethodID的第三個引數是方法名(對於建構函式一律用“<init>”表示),第四個引數是方法的“籤 名”,需要用一個字串序列表示方法的引數(依宣告順序)和返回值資訊。

如上這種使用NewObject建立的物件例項被稱為“Local Reference”,它僅在建立它的Native程式碼作用域內有效,因此應避免在作用域外使用該例項及任何指向它的指標。如果希望建立的物件例項在作用 域外也能使用,則需要使用NewGlobalRef介面將其提升為“Global Reference”——需要注意的是,當Global Reference不再使用後,需要顯式的釋放,以便通知JVM進行垃圾收集。

Native模組的編譯與釋出

NDK的全稱是Native Development Toolkit,即原生應用開發包。,為廣大開發者提供了編譯用於Android應用的Native模組的能力,以及將Native模組隨Java應用打包為APK檔案。