Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發實例 >> [Android的系統移植與平台開發]JNI介紹

[Android的系統移植與平台開發]JNI介紹

編輯:Android開發實例

JNI是在學習Android HAL時必須要面臨一個知識點,如果你不了解它的機制,不了解它的使用方式,你會被本地代碼繞的暈頭轉向,JNI作為一個中間語言的翻譯官在運行Java代碼的Android中有著重要的意義,這兒的內容比較多,也是最基本的,如果想徹底了解JNI的機制,請查看:

 

http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/design.html

本文結合了網友ljeagle寫的JNI學習筆記和自己通過JNI的手冊及Android中常用的部分寫得本文。

JNI學習筆記:

http://www.fengfly.com/plus/view-209787-1.html

讓我們開始吧!!


----------------------------------------------------------------------------------------

JNI概念

JNI是本地語言編程接口。它允許運行在JVM中的Java代碼和用C、C++或匯編寫的本地代碼相互操作。

在以下幾種情況下需要使用到JNI:

l  應用程序依賴於特定平台,平台獨立的Java類庫代碼不能滿足需要

l  你已經有一個其它語言寫的一個庫,並且這個庫需要通過JNI來訪問Java代碼

l  需要執行速度要求的代碼實現功能,比如低級的匯編代碼

通過JNI編程,你可以使用本地方法來:

l  創建、訪問、更新Java對象

l  調用Java方法

l  捕獲及拋出異常

l  加載並獲得類信息

l  執行運行時類型檢查


JNI的原理


 

JVM將JNI接口指針傳遞給本地方法,本地方法只能在當前線程中訪問該接口指針,不能將接口指針傳遞給其它線程使用。在VM中 JNI接口指針指向的區域用來分配和存儲線程本地數據。

當Java代碼調用本地方法時,VM將JNI接口指針作為參數傳遞給本地方法,當同一個Java線程調用本地方法時VM保證傳遞給本地方法的參數是相同的。不過,不同的Java線程調用本地方法時,本地方法接收到的JNI接口指針是不同的。
 

加載和鏈接本地方法
 

 

在Java裡通過System.loadLibrary()來加載動態庫,但是,動態庫只能被加載一次,因此,通常動態庫的加載放在靜態初始化語句塊中。

 

  1. package pkg;    
  2. class Cls {   
  3.      native double f(int i, String s);      // 聲明為本地方法  
  4.      static {   
  5.          System.loadLibrary(“pkg_Cls”);     // 通過靜態初始化語句塊來加載動態庫  
  6.      }   
  7. }   
  8.  

通常在動態庫中聲明大量的函數,這些函數被Java調用,這些本地函數由VM維護在一張函數指針數組中,在本地方法裡通過調用JNI方法RegisterNatives()來注冊本地方法和Java方法的映射關系。


 


 

本地方法可以由C或C++來實現,C語言版本:

  1. jdouble native_fun (   
  2.      JNIEnv *env,           /* interface pointer */   
  3.      jobject obj,           /* "this" pointer */   
  4.      jint i,                /* argument #1 */   
  5.      jstring s)             /* argument #2 */   
  6. {   
  7.      /* Obtain a C-copy of the Java string */   
  8.      const char *str = (*env)->GetStringUTFChars(env, s, 0);   
  9.  
  10.      /* process the string */   
  11.      ...   
  12.  
  13.      /* Now we are done with str */   
  14.      (*env)->ReleaseStringUTFChars(env, s, str);   
  15.      return ...   
  16. }   
  17.  

C++語言版本:

  1. extern "C"               /* specify the C calling convention */    
  2. jdouble native_fun (   
  3.      JNIEnv *env,           /* interface pointer */   
  4.      jobject obj,           /* "this" pointer */   
  5.      jint i,                /* argument #1 */   
  6.      jstring s)             /* argument #2 */   
  7. {   
  8.      const char *str = env->GetStringUTFChars(s, 0);   
  9.      ...   
  10.      env->ReleaseStringUTFChars(s, str);   
  11.      return ...   
  12. }   
  13.  

由上面兩段代碼對比可知,本地代碼使用C++來實現更簡潔。

兩段本地代碼第一個參數都是JNIEnv*env,它代表了VM裡的環境,本地代碼可以通過這個env指針對Java代碼進行操作,例如:創建Java類對象,調用Java對象方法,獲取Java對象屬性等。jobject obj相當於Java中的Object類型,它代表調用這個本地方法的對象,例如:如果有new NativeTest.CallNative(),CallNative()是本地方法,本地方法第二個參數是jobject表示的是NativeTest類的對象的本地引用。

如果本地方法聲明為static類型

static jint native_get_count(JNIEnv* env, jobject thiz);

 

 

 

數據傳遞

 

l  基本類型

用Java代碼調用C\C++代碼時候,肯定會有參數數據的傳遞。兩者屬於不同的編程語言,在數據類型上有很多差別,應該要知道他們彼此之間的對應類型。例如,盡管C擁有int和long的數據類型,但是他們的實現卻是取決於具體的平台。在一些平台上,int類型是16位的,而在另外一些平台上市32位的整數。基於這個原因,Java本地接口定義了jint,jlong等等。

Java Language Type

JNI Type

boolean

jboolean

byte

jbyte

char

jchar

short

jshort

int

jint

long

jlong

float

jfloat

double

jdouble

All Reference type

jobject

 


 


 




 


 


 


 



 


 


 


 

 

由Java類型和C/C++數據類型的對應關系,可以看到,這些新定義的類型名稱和Java類型名稱具有一致性,只是在前面加了個j,如int對應jint,long對應jlong。

我們看看jni.h和jni_md.h頭文件,可以更直觀的了解:
 

  1. typedef unsigned char       jboolean;  
  2. typedef unsigned short  jchar;  
  3. typedef short           jshort;  
  4. typedef float           jfloat;  
  5. typedef double          jdouble;  
  6. typedef long            jint;  
  7. typedef __int64             jlong;  
  8. typedef signed char         jbyte;  
  9.  

 

由jni頭文件可以看出,jint對應的是C/C++中的long類型,即32位整數,而不是C/C++中的int類型(C/C++中的int類型長度依賴於平台),它和Java 中int類型一樣。

所以如果要在本地方法中要定義一個jint類型的數據,規范的寫法應該是 jint i=123L;

再比如jchar代表的是Java類型的char類型,實際上在C/C++中卻是unsigned short類型,因為Java中的char類型為兩個字節。而在C/C++中有這樣的定義:typedef unsigned short wchar_t。所以jchar就是相當於C/C++中的寬字符。所以如果要在本地方法中要定義一個jchar類型的數據,規范的寫法應該是jchar c=L'C';

實際上,所有帶j的類型,都是代表Java中的類型,並且jni中的類型接口與本地代碼在類型大小是完全匹配的,而在語言層次卻不一定相同。在本地方法中與JNI接口調用時,要在內部都要轉換,我們在使用的時候也需要小心。

Java對象類型

Java對象在C\C++代碼中的形式如下:

  1. class _jclass : public _jobject {};  
  2. class _jthrowable : public _jobject {};  
  3. class _jstring : public _jobject {};  
  4. class _jarray : public _jobject {};  
  5. class _jbooleanArray : public _jarray {};  
  6. class _jbyteArray : public _jarray {};  
  7. class _jcharArray : public _jarray {};  
  8. class _jshortArray : public _jarray {};  
  9. class _jintArray : public _jarray {};  
  10. class _jlongArray : public _jarray {};  
  11. class _jfloatArray : public _jarray {};  
  12. class _jdoubleArray : public _jarray {};  
  13. class _jobjectArray : public _jarray {};  
  14.  

 

所有的_j開頭的類,都是繼承於_jobject,這也是Java語言的特別,所有的類都是Object的子類,這些類就是和Java中的類一一對應,只不過名字稍有不同而已。

1)        jclass類和如何取得jclass對象

在Java中,Class類型代表一個Java類編譯的字節碼,即:這個Java類,裡面包含了這個類的所有信息。在JNI中,同樣定義了這樣一個類:jclass。了解反射的人都知道Class類是如何重要,可以通過反射獲得java類的信息和訪問裡面的方法和成員變量。

JNIEnv有幾個方法可以取得jclass對象:

 

 
  1. jclass FindClass(const char *name) {  
  2.         return functions->FindClass(this, name);  
  3.  }  
  4.  

 

 

FindClass會在系統classpath環境變量下尋找name類,注意包的間隔使用   “/ “,而不是”. “,如:

jclass cls_string=env->FindClass("java/lang/String");

獲得對象對應的jclass類型:

 
  1. jclass GetObjectClass(jobject obj) {  
  2.         return functions->GetObjectClass(this,obj);  
  3. }  
  4.  
獲得一個類的父類jclass類型:

 

jclass GetSuperclass(jclass sub) {
        return functions->GetSuperclass(this,sub);
}

 JNI本地方法訪問Java屬性和方法

 

在JNI調用中,不僅僅Java可以調用本地方法,本地代碼也可以調用Java中的方法和成員變量。在Java1.0中“原始的”Java到C的綁定中,程序員可以直接訪問對象數據域。然而,直接方法要求虛擬機暴露他們的內部數據布局,基於這個原因,JNI要求程序員通過特殊的JNI函數來獲取和設置數據以及調用java方法。

1)        取得代表屬性和方法的jfieldID和jmethodID

為了在C/C++中表示屬性和方法,JNI在jni.h頭文件中定義了jfieldID和jmethodID類型來分別代表Java對象的屬性和方法。我們在訪問或是設置Java屬性的時候,首先就要先在本地代碼取得代表該Java屬性的jfieldID,然後才能在本地代碼進行Java屬性操作。同樣的,我們需要調用Java對象方法時,也是需要取得代表該方法的jmethodID才能進行Java方法調用。

使用JNIEnv提供的JNI方法,我們就可以獲得屬性和方法相對應的jfieldID和jmethodID:

l  GetFieldID   :取得成員變量的id

l  GetStaticFieldID  :取得靜態成員變量的id

l  GetMethodID  :取得方法的id

l  GetStaticMethodID :取得靜態方法的id

jfieldID GetFieldID(jclass clazz, const char *name,const char *sig)

jfieldID GetStaticFieldID(jclass clazz, const char*name, const char *sig)

jmethodID GetStaticMethodID(jclass clazz, const char*name, const char *sig)

jmethodID GetMethodID(jclass clazz, const char *name,constchar *sig)

可以看到這四個方法的參數列表都是一模一樣的,下面來分析下每個參數的含義:

第一個參數jclassclazz :

上一節講到的jclass類型,相當於Java中的Class類,代表一個Java類,而這裡面的代表的就是我們操作的Class類,我們要從這個類裡面取的屬性和方法的ID。

第二個參數constchar *name:

這是一個常量字符數組,代表我們要取得的方法名或者變量名。

第三個參數constchar *sig:
這也是一個常量字符數組,代表我們要取得的方法或變量的簽名。

什麼是方法或者變量的簽名呢?

我們來看下面的例子,如何來獲得屬性和方法ID:

public class NativeTest {

         publicvoid show(int i){

                   System.out.println(i);

         }

public void show(double d){

                   System.out.println(d);

         }

}

本地代碼部分:

//首先取得要調用的方法所在的類的Class對象,在C/C++中即jclass對象

jclass clazz_NativeTest=env->FindClass("cn/itcast/NativeTest");

//取得jmethodID

jmethodID id_show=env->GetMethodID(clazz_NativeTest,“show”,"???");

上述代碼中的id_show取得的jmethodID到底是哪個show方法呢?由於Java語言有方法重載的面向對象特性,所以只通過函數名不能明確的讓JNI找到Java裡對應的方法。所以這就是第三個參數sig的作用,它用於指定要取得的屬性或方法的類型簽名。

2)        JNI簽名:

類型簽名

Java 類型

類型簽名

Java 類型

Z

boolean

[

[]

B

byte

[I

int[]

C

char

[F

float[]

S

short

[B

byte[]

I

int

[C

char[]

J

long

[S

short[]

F

float

[D

double[]

D

double

[J

long[]

L

fully-qualified-class(全限定的類)

[Z

boolean[]


 


 


 


 


 


 


 


 


 


 


 


 


 

 

l  基本類型

以特定的大寫字母表示

l  引用類型

Java對象以L開頭,然後以“/”分隔包的完整類型,例如String的簽名為:Ljava/lang/String;

在Java裡數組類型也是引用類型,數組以[ 開頭,後面跟數組元素類型的簽名,例如:int[]   簽名就是[I ,對於二維數組,如int[][] 簽名就是[[I,object數組簽名就是[Ljava/lang/Object;

l  方法簽名

(參數1類型簽名參數2類型簽名參數3類型簽名.......)返回值類型簽名

注意:

函數名,在簽名中沒有體現出來

參數列表相挨著,中間沒有逗號,沒有空格

返回值出現在()後面


 

如果參數是引用類型,那麼參數應該為:L類型;

如果函數沒有返回值,也要加上V類型

例如:

Java方法

對應簽名

boolean isLedOn(void) ;

()Z

void setLedOn(int ledNo);

(I)

String substr(String str, int idx, int count);

(Ljava/lang/String;II)Ljava/lang/String

char fun (int n, String s, int[] value);

(ILjava/lang/String;[I)C

boolean showMsg(View v, String msg);

(Landroid/View;Ljava/lang/String;)Z

 

 

 

 

 

 

 

 

 

3)        根據獲取的ID,來取得和設置屬性,以及調用方法。

l  獲得、設置屬性和靜態屬性

取得了代表屬性和靜態屬性的jfieldID,就可以使用JNIEnv中提供的方法來獲取和設置屬性/靜態屬性。

獲取屬性/靜態屬性的形式:

Get<Type>Field    GetStatic<Type>Field。

設置屬性/靜態屬性的形式:

Set<Type>Field    SetStatic<Type>Field。

取得成員屬性:

jobject GetObjectField(jobjectobj, jfieldID fieldID);

jboolean GetBooleanField(jobjectobj, jfieldID fieldID);

jbyte GetByteField(jobjectobj, jfieldID fieldID);

         取得靜態屬性:

jobject GetStaticObjectField(jclassclazz, jfieldID fieldID);

jboolean GetStaticBooleanField(jclassclazz, jfieldID fieldID);

jbyte GetStaticByteField(jclassclazz, jfieldID fieldID);

Get方法的第一個參數代表要獲取的屬性所屬對象或jclass對象,第二個參數即屬性ID。

設置成員屬性:

void SetObjectField(jobjectobj, jfieldID fieldID, jobject val);

void SetBooleanField(jobjectobj, jfieldID fieldID,jboolean val);

void SetByteField(jobjectobj, jfieldID fieldID, jbyte val);

設置靜態屬性:

void SetStaticObjectField(jobjectobj, jfieldID fieldID, jobject val);

void SetStaticBooleanField(jobjectobj, jfieldID fieldID,jboolean val);

void SetStaticByteField(jobjectobj, jfieldID fieldID, jbyte val);

Set方法的第一個參數代表要設置的屬性所屬的對象或jclass對象,第二個參數即屬性ID,第三個參數代表要設置的值。

 

l  調用方法

取得了代表方法和靜態方法的jmethodID,就可以用在JNIEnv中提供的方法來調用方法和靜態方法。

調用普通方法:

Call<Type>Method(jobject obj, jmethodID methodID,...);

Call<Type>MethodV(jobject obj, jmethodID methodID,va_listargs);

Call<Type>tMethodA(jobject obj, jmethodID methodID,constjvalue *args);

調用靜態方法:

CallStatic<Type>Method(jclass clazz, jmethodID methodID,...);

CallStatic<Type>MethodV(jclass clazz, jmethodID methodID,va_listargs);

CallStatic<Type>tMethodA(jclass clazz, jmethodID methodID,constjvalue *args);

上面的Type這個方法的返回值類型,如Int,Char,Byte等等。

第一個參數代表調用的這個方法所屬於的對象,或者這個靜態方法所屬的類。

第二個參數代表jmethodID。

後面的參數,就代表這個方法的參數列表了。

上述方法的調用有三種形式:

a)        Call<Type>Method(jobject obj, jmethodIDmethodID,...);

// Java方法

public int show(int i,double d,char c){

}

 

// 本地調用Java方法

jint i=10L;

jdouble d=2.4;

jchar c=L'd';

env->CallIntMethod(obj, id_show, i, d, c);

 

b)        Call<Type>MethodV(jobject obj, jmethodIDmethodID,va_list args)

這種方式使用較少。

c)        Call<Type>MethodA(jobject obj, jmethodIDmethodID,jvalue* v)

這種調用方式其第三個參數是一個jvalue的指針。jvalue類型是在 jni.h頭文件中定義的聯合體union,看它的定義:

typedef union jvalue {

    jboolean     z;

    jbyte        b;

    jchar        c;

    jshort        s;

    jint          i;

    jlong        j;

    jfloat        f;

    jdouble      d;

    jobject       l;

} jvalue;

例如:

     jvalue * args=new jvalue[3];

     args[0].i=12L;

         args[1].d=1.2;

         args[2].c=L'c';

         jmethodIDid_goo=env->GetMethodID(env->GetObjectClass(obj),"goo","(IDC)V");

         env->CallVoidMethodA(obj,id_goo,args);

     delete []args;  //釋放內存

靜態方法的調用方式和成員方法調用一樣。

3.5 本地創建Java對象

1)        本地代碼創建Java對象

JNIEnv提供了下面幾個方法來創建一個Java對象:

jobject NewObject(jclass clazz, jmethodID methodID,...);

jobject NewObjectV(jclass clazz, jmethodIDmethodID,va_list args);

jobject NewObjectA(jclass clazz, jmethodID methodID,const jvalue *args) ;

本地創建Java對象的函數和前面本地調用Java方法很類似:

第一個參數jclass class  代表的你要創建哪個類的對象

第二個參數jmethodID methodID 代表你要使用哪個構造方法ID來創建這個對象。

只要有jclass和jmethodID ,我們就可以在本地方法創建這個Java類的對象。

指的一提的是:由於Java的構造方法的特點,方法名與類名一樣,並且沒有返回值,所以對於獲得構造方法的ID的方法env->GetMethodID(clazz,method_name ,sig)中的第二個參數是固定為類名,第三個參數和要調用的構造方法有關,默認的Java構造方法沒有返回值,沒有參數。例如:

jclassclazz=env->FindClass("java/util/Date");                                     //取得java.util.Date類的jclass對象

jmethodID id_date=env->GetMethodID(clazz,"Date","()V");    //取得某一個構造方法的jmethodID

jobject date=env->NewObject(clazz,id_date);                             //調用NewObject方法創建java.util.Date對象

2)        本地方法對Java字符串的操作

在Java中,字符串String對象是Unicoode(UTF-16)編碼,每個字符不論是中文還是英文還是符號,一個字符總是占用兩個字節。在C/C++中一個字符是一個字節, C/C++中的寬字符是兩個字節的。所以Java通過JNI接口可以將Java的字符串轉換到C/C++的寬字符串(wchar_t*),或是傳回一個UTF-8的字符串(char*)到C/C++,反過來,C/C++可以通過一個寬字符串,或是一個UTF-8編碼的字符串創建一個Java端的String對象。

可以看下面的一個例子:

在Java端有一個字符串 String str="abcde";,在本地方法中取得它並且輸出:

void native_string_operation (JNIEnv * env,  jobject obj)

{

         //取得該字符串的jfieldID

jfieldIDid_string=env->GetFieldID(env->GetObjectClass(obj), "str", "Ljava/lang/String;");

         jstringstring=(jstring)(env->GetObjectField(obj, id_string));    //取得該字符串,強轉為jstring類型。

    printf("%s",string);

}

由上面的代碼可知,從java端取得的String屬性或者是方法返回值的String對象,對應在JNI中都是jstring類型,它並不是C/C++中的字符串。所以,我們需要對取得的 jstring類型的字符串進行一系列的轉換,才能使用。

JNIEnv提供了一系列的方法來操作字符串:

l  const jchar *GetStringChars(jstring str, jboolean*isCopy) 

將一個jstring對象,轉換為(UTF-16)編碼的寬字符串(jchar*)。

l  const char *GetStringUTFChars(jstring str,jboolean *isCopy)

將一個jstring對象,轉換為(UTF-8)編碼的字符串(char*)。

這兩個函數的參數中,第一個參數傳入一個指向Java 中String對象的jstring引用。第二個參數傳入的是一個jboolean的指針,其值可以為NULL、JNI_TRUE、JNI_FLASE。

如果為JNI_TRUE則表示開辟內存,然後把Java中的String拷貝到這個內存中,然後返回指向這個內存地址的指針。

如果為JNI_FALSE,則直接返回指向Java中String的內存指針。這時不要改變這個內存中的內容,這將破壞String在Java中始終是常量的規則。

如果是NULL,則表示不關心是否拷貝字符串。

 

使用這兩個函數取得的字符,在不適用的時候,要分別對應的使用下面兩個函數來釋放內存。

RealeaseStringChars(jstring jstr, const jchar*str)

RealeaseStringUTFChars(jstring jstr, constchar* str)

第一個參數指定一個jstring變量,即要釋放的本地字符串的資源

第二個參數就是要釋放的本地字符串

3)        創建Java String對象

jstring NewString(const jchar *unicode, jsizelen)            // 根據傳入的寬字符串創建一個Java String對象

jstring NewStringUTF(const char *utf)                    // 根據傳入的UTF-8字符串創建一個Java String對象

4)        返回Java String對象的字符串長度

jsize GetStringLength(jstring jstr)                   //返回一個java String對象的字符串長度

jsize GetStringUTFLength(jstring jstr)             //返回一個java String對象經過UTF-8編碼後的字符串長度

3.6 Java數組在本地代碼中的處理

我們可以使用GetFieldID獲取一個Java數組變量的ID,然後用GetObjectFiled取得該數組變量到本地方法,返回值為jobject,然後我們可以強制轉換為j<Type>Array類型。

typedef jarray jbooleanArray;

typedef jarray jbyteArray;

typedef jarray jcharArray;

typedef jarray jshortArray;

typedef jarray jintArray;

typedef jarray jlongArray;

typedef jarray jfloatArray;

typedef jarray jdoubleArray;

typedef jarray jobjectArray;

j<Type>Array類型是JNI定義的一個對象類型,它並不是C/C++的數組,如int[]數組,double[]數組等等。所以我們要把j<Type>Array類型轉換為C/C++中的數組來操作。

JNIEnv定義了一系列的方法來把一個j<Type>Array類型轉換為C/C++數組或把C/C++數組轉換為j<Type>Array。

jsize GetArrayLength(jarray array)                                                        // 獲得數組的長度

jobjectArray NewObjectArray(jsize len, jclass clazz, jobjectinit)                  // 創建對象數組,指定其大小

jobject GetObjectArrayElement(jobjectArray array, jsizeindex)                   // 獲得數組的指定元素

void SetObjectArrayElement(jobjectArray array, jsizeindex,jobject val)      // 設置數組元素

 

 jbooleanArrayNewBooleanArray(jsize len)            // 創建Boolean數組,指定其大小

 jbyteArrayNewByteArray(jsize len)                        //下面的都類似,創建對應類型的數組,並指定大小

 jcharArrayNewCharArray(jsize len)

 jshortArrayNewShortArray(jsize len)

 jintArrayNewIntArray(jsize len)

 jlongArrayNewLongArray(jsize len)

 jfloatArrayNewFloatArray(jsize len)

 jdoubleArrayNewDoubleArray(jsize len)

// 獲得指定類型數組的元素

jboolean * GetBooleanArrayElements(jbooleanArray array,jboolean *isCopy)

jbyte * GetByteArrayElements(jbyteArray array, jboolean*isCopy)

jchar * GetCharArrayElements(jcharArray array, jboolean*isCopy)

jshort * GetShortArrayElements(jshortArray array, jboolean*isCopy)

jint * GetIntArrayElements(jintArray array, jboolean*isCopy)

jlong * GetLongArrayElements(jlongArray array, jboolean*isCopy)

jfloat * GetFloatArrayElements(jfloatArray array,jboolean *isCopy)

jdouble * GetDoubleArrayElements(jdoubleArray array,jboolean *isCopy)

// 釋放指定數組

void ReleaseBooleanArrayElements(jbooleanArrayarray,jboolean *elems,jint mode)

void ReleaseByteArrayElements(jbyteArray array,jbyte*elems,jint mode)

void ReleaseCharArrayElements(jcharArray array,jchar*elems,jint mode)

void ReleaseShortArrayElements(jshortArray array,jshort*elems,jint mode)

void ReleaseIntArrayElements(jintArray array,jint*elems,jint mode)

void ReleaseLongArrayElements(jlongArray array,jlong*elems,jint mode)

void ReleaseFloatArrayElements(jfloatArray array,jfloat*elems,jint mode)

void ReleaseDoubleArrayElements(jdoubleArrayarray,jdouble *elems,jint mode)

 

void * GetPrimitiveArrayCritical(jarray array, jboolean*isCopy)

void ReleasePrimitiveArrayCritical(jarray array, void*carray, jint mode)

 

void GetBooleanArrayRegion(jbooleanArray array,jsizestart, jsize len, jboolean *buf)

void GetByteArrayRegion(jbyteArray array,jsize start,jsize len, jbyte *buf)

void GetCharArrayRegion(jcharArray array,jsize start,jsize len, jchar *buf)

void GetShortArrayRegion(jshortArray array,jsize start,jsize len, jshort *buf)

void GetIntArrayRegion(jintArray array,jsize start,jsize len, jint *buf)

void GetLongArrayRegion(jlongArray array,jsize start,jsize len, jlong *buf)

void GetFloatArrayRegion(jfloatArray array,jsize start,jsize len, jfloat *buf)

void GetDoubleArrayRegion(jdoubleArray array,jsizestart, jsize len, jdouble *buf)

void SetBooleanArrayRegion(jbooleanArray array, jsizestart, jsize len,const jboolean *buf)

void SetByteArrayRegion(jbyteArray array, jsize start,jsize len,const jbyte *buf)

void SetCharArrayRegion(jcharArray array, jsize start,jsize len,const jchar *buf)

void SetShortArrayRegion(jshortArray array, jsizestart, jsize len,const jshort *buf)

void SetIntArrayRegion(jintArray array, jsize start,jsize len,const jint *buf)

void SetLongArrayRegion(jlongArray array, jsize start,jsize len,const jlong *buf)

void SetFloatArrayRegion(jfloatArray array, jsizestart, jsize len,const jfloat *buf)

void SetDoubleArrayRegion(jdoubleArray array, jsizestart, jsize len,const jdouble *buf)

上面是JNIEnv提供給本地代碼調用的數組操作函數,大致可以分為下面幾類:

1)        獲取數組的長度

jsize GetArrayLength(jarray array);

2)        對象類型數組的操作

jobjectArray NewObjectArray(jsize len, jclassclazz,jobject init)                   // 創建

jobject GetObjectArrayElement(jobjectArray array, jsizeindex)                            // 獲得元素

void SetObjectArrayElement(jobjectArray array, jsizeindex,jobject val)       // 設置元素

JNI沒有提供直接把Java的對象類型數組(Object[ ])直接轉到C++中的jobject[ ]數組的函數。而是直接通過Get/SetObjectArrayElement這樣的函數來對Java的Object[ ]數組進行操作

3)        對基本數據類型數組的操作

基本數據類型數組的操作方法比較多,大致可以分為如下幾類:

Get<Type>ArrayElements/Realease<Type>ArrayElements;

Get<Type>ArrayElements(<Type>Array arr, jboolean*isCopied);

這類函數可以把Java基本類型的數組轉換到C/C++中的數組。有兩種處理方式,一是拷貝一份傳回本地代碼,另一種是把指向Java數組的指針直接傳回到本地代碼,處理完本地化的數組後,通過Realease<Type>ArrayElements來釋放數組。處理方式由Get方法的第二個參數isCopied來決定。

Realease<Type>ArrayElements(<Type>Arrayarr,<Type>* array, jint mode)用這個函數可以選擇將如何處理Java和C/C++本地數組:

其第三個參數mode可以取下面的值:

l  0:對Java的數組進行更新並釋放C/C++的數組

l  JNI_COMMIT:對Java的數組進行更新但是不釋放C/C++的數組

l  JNI_ABORT:對Java的數組不進行更新,釋放C/C++的數組

例如:

Test.java

public class Test {

         privateint [] arrays=new int[]{1,2,3,4,5};

         publicnative void show();

         static{

                   System.loadLibrary("NativeTest");

         }

         publicstatic void main(String[] args) {

                   newTest().show();

         }

}

本地方法:

void native_test_show(JNIEnv * env,  jobject obj)

{

         jfieldIDid_arrsys=env->GetFieldID(env->GetObjectClass(obj),"arrays","[I");

         jintArrayarr=(jintArray)(env->GetObjectField(obj, id_arrsys));

         jint*int_arr=env->GetIntArrayElements(arr,NULL);

         jsizelen=env->GetArrayLength(arr);

         for(inti=0; i<len; i++)

         {

                   cout<<int_arr[i]<<endl;

         }

         env->ReleaseIntArrayElements(arr,int_arr,JNI_ABORT);

}

 

1.7局部引用與全局引用

1)        JNI中的引用變量

Java代碼與本地代碼裡在進行參數傳遞與返回值復制的時候,要注意數據類型的匹配。對於int, char等基本類型直接進行拷貝即可,對於Java中的對象類型,通過傳遞引用實現。VM保證所有的Java對象正確的傳遞給了本地代碼,並且維持這些引用,因此這些對象不會被Java的gc(垃圾收集器)回收。因此,本地代碼必須有一種方式來通知VM本地代碼不再使用這些Java對象,讓gc來回收這些對象。

         JNI將傳遞給本地代碼的對象分為兩種:局部引用和全局引用。

l  局部引用:只在上層Java調用本地代碼的函數內有效,當本地方法返回時,局部引用自動回收。

l  全局引用:只有顯示通知VM時,全局引用才會被回收,否則一直有效,Java的gc不會釋放該引用的對象。

默認的話,傳遞給本地代碼的引用是局部引用。所有的JNI函數的返回值都是局部引用。

jstring

MyNewString(JNIEnv *env, jchar *chars, jint len)

{

    static jclassstringClass = NULL;               //static 不能保存一個局部引用

    jmethodID cid;

    jcharArrayelemArr;

    jstringresult;

    if(stringClass == NULL) {

       stringClass = (*env)->FindClass(env, "java/lang/String");    // 局部引用

        if(stringClass == NULL) {

           return NULL; /* exception thrown */

        }

    }

    /* It iswrong to use the cached stringClass here,

       because itmay be invalid. */

    cid =(*env)->GetMethodID(env, stringClass, "<init>","([C)V");

    ...

    elemArr =(*env)->NewCharArray(env, len);

    ...

    result =(*env)->NewObject(env, stringClass, cid, elemArr);

   (*env)->DeleteLocalRef(env, elemArr);

    returnresult;

}

 

2)        手動釋放局部引用情況

雖然局部引用會在本地代碼執行之後自動釋放,但是有下列情況時,要手動釋放:

l  本地代碼訪問一個很大的Java對象時,在使用完該對象後,本地代碼要去執行比較復雜耗時的運算時,由於本地代碼還沒有返回,Java收集器無法釋放該本地引用的對象,這時,應該手動釋放掉該引用對象。

/* A native method implementation */

JNIEXPORT void JNICALL

func(JNIEnv *env, jobject this)

{

    lref =...              /* a large Java object*/

    ...                     /* last use of lref */

   (*env)->DeleteLocalRef(env, lref);

   lengthyComputation();   /* maytake some time */

    return;                 /* all local refs are freed */

}

這個情形的實質,就是允許程序在native方法執行期間,java的垃圾回收機制有機會回收native代碼不在訪問的對象。

l  本地代碼創建了大量局部引用,這可能會導致JNI局部引用表溢出,此時有必要及時地刪除那些不再被使用的局部引用。比如:在本地代碼裡創建一個很大的對象數組。

for (i = 0; i < len; i++) {

    jstring jstr= (*env)->GetObjectArrayElement(env, arr, i);

    ... /*process jstr */

   (*env)->DeleteLocalRef(env, jstr);

}

在上述循環中,每次都有可能創建一個巨大的字符串數組。在每個迭代之後,native代碼需要顯示地釋放指向字符串元素的局部引用。

l  創建的工具函數,它會被未知的代碼調用,在工具函數裡使用完的引用要及時釋放。

l  不返回的本地函數。例如,一個可能進入無限事件分發的循環中的方法。此時在循環中釋放局部引用,是至關重要的,這樣才能不會無限期地累積,進而導致內存洩露。

局部引用只在創建它們的線程裡有效,本地代碼不能將局部引用在多線程間傳遞。一個線程想要調用另一個線程創建的局部引用是不被允許的。將一個局部引用保存到全局變量中,然後在其它線程中使用它,這是一種錯誤的編程。

3)        全局引用

在一個本地方法被多次調用時,可以使用一個全局引用跨越它們。一個全局引用可以跨越多個線程,並且在被程序員手動釋放之前,一直有效。和局部引用一樣,全局引用保證了所引用的對象不會被垃圾回收。

JNI允許程序員通過局部引用來創建全局引用, 全局引用只能由NewGlobalRef函數創建。下面是一個使用全局引用例子:

jstring

MyNewString(JNIEnv *env, jchar *chars, jint len)

{

    static jclassstringClass = NULL;

    ...

    if(stringClass == NULL) {

        jclasslocalRefCls =

           (*env)->FindClass(env, "java/lang/String");

        if(localRefCls == NULL) {

           return NULL;

        }

        /* Createa global reference */

       stringClass = (*env)->NewGlobalRef(env, localRefCls);

        /* Thelocal reference is no longer useful */

       (*env)->DeleteLocalRef(env, localRefCls);

        /* Is theglobal reference created successfully? */

        if(stringClass == NULL) {

           return NULL; /* out of memory exception thrown */

        }

    }

    ...

}

 

4)        釋放全局引用

在native代碼不再需要訪問一個全局引用的時候,應該調用DeleteGlobalRef來釋放它。如果調用這個函數失敗,Java VM將不會回收對應的對象。

 

1.8 本地C代碼中創建Java對象及本地JNI對象的保存

1)        Android中Bitmap對象的創建

通常在JVM裡創建Java的對象就是創建Java類的實例,再調用Java類的構造方法。而有時Java的對象需要在本地代碼裡創建。以Android中的Bitmap的構建為例,Bitmap中並沒有Java對象創建的代碼及外部能訪問的構造方法,所以它的實例化是在JNI的c中實現的。

BitmapFactory.java中提供了得到Bitmap的方法,時序簡化為:

BitmapFactory.java->BitmapFactory.cpp -> GraphicsJNI::createBitmap()  [graphics.cpp]

GraphicsJNI::createBitmap()[graphics.cpp]的實現:

jobjectGraphicsJNI::createBitmap(JNIEnv* env, SkBitmap*bitmap, bool isMutable,

                                  jbyteArrayninepatch, intdensity)

{

   SkASSERT(bitmap != NULL);

    SkASSERT(NULL!= bitmap->pixelRef());

 

    jobject obj=env->AllocObject(gBitmap_class);

    if (obj) {

       env->CallVoidMethod(obj,gBitmap_constructorMethodID,

                           (jint)bitmap,isMutable, ninepatch, density);

        if(hasException(env)) {

            obj =NULL;

        }

    }

    return obj;

}

而gBitmap_class的得到是通過:

jclass c=env->FindClass("android/graphics/Bitmap");

gBitmap_class =(jclass)env->NewGlobalRef(c);

//gBitmap_constructorMethodID是Bitmap的構造方法(方法名用”<init>”)的jmethodID:

gBitmap_constructorMethodID=env->GetMethodID(gBitmap_class, "<init>",  "(IZ[BI)V");

總結一下,c中如何訪問Java對象的屬性:

1)        通過JNIEnv::FindClass()找到對應的jclass;

2)        通過JNIEnv::GetMethodID()找到類的構造方法的jfieldID;

3)        通過JNIEnv::AllocObject創建該類的對象;

4)        通過JNIEnv::CallVoidMethod()調用Java對象的構造方法。

 

2)        本地JNI對象保存在Java環境中

C代碼中某次被調用時生成的對象,在其他函數調用時是不可見的,雖然可以設置全局變量但那不是好的解決方式,Android中通常是在Java域中定義一個int型的變量,在本地代碼生成對象的地方,與這個Java域的變量關聯,在別的使用到的地方,再從這個變量中取值。

以JNICameraContext為例來說明:

JNICameraContext是android_hardware_camera.cpp中定義的類型,並會在本地代碼中生成對象並與Java中android.hardware.Camera的mNativeContext關聯。

在注冊native函數之前,C中就已經把Java域中的屬性的jfieldID得到了。通過下列方法:

jclass clazz =env->FindClass("android/hardware/Camera ");

jfieldID field = env->GetFieldID(clazz, "mNativeContext","I");

如果執行成功,把field保存到fileds.context成員變量中。

生成cpp對象時,通過JNIEnv::SetIntField()設置為Java對象的屬性

static void android_hardware_Camera_native_setup(JNIEnv*env, jobject thiz,

    jobjectweak_this, jintcameraId)

{

    // …

   sp<JNICameraContext>context = new JNICameraContext(env, weak_this,clazz, camera);

    // …

    // 該處通過context.get()得到context對象的地址,保存到了Java中的mNativeContext屬性裡

env->SetIntField(thiz,fields.context, (int)context.get());

}

而要使用時,又通過JNIEnv::GetIntField()獲取Java對象的屬性,並轉化為JNICameraContext類型:

   JNICameraContext* context=reinterpret_cast<JNICameraContext*>(env->GetIntField(thiz,fields.context));

    if (context!= NULL) {

        // …

    }

總結一下,c++中生成的對象如何保存和使用:

1)   通過JNIEnv::FindClass()找到對應的jclass;

2)   通過JNIEnv::GetFieldID()找到類中屬性的jfieldID;

3)   某個調用過程中,生成cpp對象時,通過JNIEnv::SetIntField()設置為Java對象的屬性;

4)   另外的調用過程中,通過JNIEnv::GetIntField()獲取Java對象的屬性,再轉化為真實的對象類型。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved