JNI高階知識總結

JNI與NDK的關系

NDK可以為我們生成了C/C++的動態(tài)鏈接庫骡技,JNI是java和C/C++溝通的接口,兩者與android沒有半毛錢關系囤萤,只因為安卓是java程序語言開發(fā),然后通過JNI又能與C/C++溝通涛舍,所以我們可以使用NDK+JNI來實現“Java+C”的開發(fā)方式唆途。

JNIEnv與JavaVM

JNIEnv 概念 : 是一個線程相關的結構體, 該結構體代表了 Java 在本線程的運行環(huán)境 ;

JNIEnv 與 JavaVM : 注意區(qū)分這兩個概念;
– JavaVM : JavaVM 是 Java虛擬機在 JNI 層的代表, JNI 全局只有一個;
– JNIEnv : JavaVM 在線程中的代表, 每個線程都有一個, JNI 中可能有很多個 JNIEnv;

JNIEnv 作用 :
– 調用 Java 函數 : JNIEnv 代表 Java 運行環(huán)境, 可以使用 JNIEnv 調用 Java 中的代碼;
– 操作 Java 對象 : Java 對象傳入 JNI 層就是 Jobject 對象, 需要使用 JNIEnv 來操作這個 Java 對象;

JNIEnv 體系結構
線程相關 : JNIEnv 是線程相關的, 即 在 每個線程中 都有一個 JNIEnv 指針, 每個JNIEnv 都是線程專有的, 其它線程不能使用本線程中的 JNIEnv, 線程 A 不能調用 線程 B 的 JNIEnv;

*.so的入口函數

JNI_OnLoad()與JNI_OnUnload()
當Android的VM(Virtual Machine)執(zhí)行到System.loadLibrary()函數時,首先會去執(zhí)行C組件里的JNI_OnLoad()函數肛搬。它的用途有二:
(1)告訴VM此C組件使用那一個JNI版本滚婉。如果你的.so檔沒有提供JNI_OnLoad()函數帅刀,VM會默認該.so檔是使用最老的JNI 1.1版本。由于新版的JNI做了許多擴充扣溺,如果需要使用JNI的新版功能,例如JNI 1.4的java.nio.ByteBuffer,就必須藉由JNI_OnLoad()函數來告知VM锥余。
(2)由于VM執(zhí)行到System.loadLibrary()函數時,就會立即先呼叫JNI_OnLoad()嘲恍,所以C組件的開發(fā)者可以藉由JNI_OnLoad()來進行C組件內的初期值之設定(Initialization) 雄驹。

這里寫圖片描述
這里寫圖片描述
這里寫圖片描述

JNI字符串函數

常用的JNI函數將在后續(xù)介紹医舆,這里給出其中的字符串操作函數的函數名以及相關描述俘侠。

GetStringChars
ReleaseStringChars 獲得/釋放一個Unicode格式的字符串指針爷速,可能返回一個字符串的副本

GetStringUTFChars
ReleaseStringUTFChars 獲得/釋放一個UTF-8格式的字符串指針霞怀,可能返回一個字符串的副本

GetStringLength 返回Unicode格式字符串的長度

GetStringUTFLength 返回UTF-8格式字符串的長度

NewString 根據Unicode格式的C字符串創(chuàng)建一個Java字符串

NewStringUTF 根據UTF-8格式的C字符串創(chuàng)建一個Java字符串

GetStringCritical
ReleaseStringCritical 獲得/釋放一個Unicode格式的字符串指針,可能返回一個字符串的副本【在該函數對區(qū)間內毙石,不能使用任何JNI函數】

GetStringRegion 將Unicode格式的String復制到預分配的緩沖區(qū)中

GetStringUTFRegion 將UTF-8格式的String復制到預分配的緩沖區(qū)中

int sprintf( char *buffer, const char *format, [ argument] … );
類似于printf禁谦,根據格式化字符串format废封,將后續(xù)參數列表中的參數逐個輸出。不過輸出目標不是標準輸出終端漂洋,而是字符串buffer。

字符串操作

C字符串——>java字符串

例如:下面的函數以一個C字符串為參數演训,并返回一個Java字符串引用類型jstring值贝咙。

jstring javastring
javastring = (*env)->NewStringUTF(env, "I LOVE YOU !");

注意,在內存溢出的情況下庭猩,NewString函數將返回NULL以通知原生代碼虛擬機中有異常拋出。

java字符串轉換成C字符串

為了在原生代碼中使用java字符串震糖,需要先將java字符串轉換成C字符串趴腋,我們使用GetStringChars函數可以將Unicode格式的java字符串轉換成C字符串,使用GetStringUTFChars函數可以將UTF-8格式的Java字符串轉換成C字符串优炬。這些函數的第三個參數均為可選參數,該可選參數名是isCopy雅宾,它讓調用者確定返回的C字符串地址指向副本還是指向堆中的固定對象糊余。例如:

const jbyte* str;
jboolean isCopy;

str = (*env)->GetStringUTFChars(env, javaString,&isCopy);
if(0 != str){
    printf("java String: %s",str);
    if(JNI_TRUE == isCopy){
        printf("C String is a copy of the java String");
    }else{
        printf("C String points to actual String");
    }
}

釋放字符串
通過JNI GetStringChars 函數GetStringUTFChars函數獲得的C字符串在原生代碼中使用完成之后需要正確的釋放,否則將會引起內存泄漏贬芥。通常我們使用ReleaseStringChars函數釋放Unicode格式的字符串,使用ReleaseUTFStringChars函數釋放UTF-8格式的字符串.

(*env)->ReleaseUTFStringChars(env,javaString,str);

數組操作

JNI把java數組當成引用類型來處理昏苏,JNI提供必要的函數訪問和處理Java數組。

創(chuàng)建數組

用NewArray函數在原生代碼中創(chuàng)建數組實例贤惯,其中可以是Int、Char和Boolean等屁商。例如:

jintArray javaArray;
javaArray = (*env)->NewIntArray(env,10);
if(0 != javaArray){
/*數組使用……*/
}
注意颈墅,在內存溢出的情況下,NewArray函數將返回NULL以通知原生代碼虛擬機中有異常拋出恤筛。 

訪問數組元素

JNI提供兩種訪問java數組元素的方法,可以將數組的代碼賦值成C數組或者讓JNI提供直接執(zhí)行數組元素的指針望伦。

對副本的操作

1.java數組轉C數組
GetArrayRegion函數將給定的基本Java數組賦值到給對你給的C數組中煎殷,例如:


jint nativeArray[10];
(*evn)->GetIntArrayRegion(env,javaArray,0,10,nativeArray);

2.C數組轉java數組
原生代碼可以像使用普通的C數組一樣使用和修改數組元素。當原生代碼想將所做的修改提交給java數組時蝌数,可以使用SetArrayRegion函數將C數組復制回java數組中度秘。例如:

(*env)->SetIntArrayRegion(env,javaArray,0,10,nativeArray);

注意:當數組很大時,對數組做復制操作會引起性能問題剑梳。

對直接指針的操作

3.java數組轉C數組
原生代碼可以使用GetArrayElements函數獲取執(zhí)行數組元素的直接指針。例如:

jint nativeDirectArray;
jboolean isCopy;

nativeDirectArray = (*env)->GetIntArrayElements(env,javaArray,&isCopy);
其中垢乙,第三個&isCopy參數為可選參數,讓調用者確定返回的C字符串地址指向副本還是指向堆中的固定對象追逮。 
因為可以像普通的C數組一樣訪問和處理數組元素,因此JNI沒提供訪問和處理數組元素的方法骂倘,JNI要求原生代碼用完這些指針后立刻釋放巴席,否則會出現內存溢出。可以使用JNI提供的ReleaseArrayElements函數釋放GetArrayElements返回的C數組堰塌。例如:


(*env)->ReleaseIntArrayRegion(env,javaArray,nativeDirectArray,0);
其中第四個參數是釋放模式分衫。 
釋放模式動作0將內容復制回來并釋放原生數組JNI_COMMIT將內容復制回來但是不釋放原生數組,一般用于周期性的更新一個java數組JNI_ABORT釋放原生數組但不用將內容復制回來

NIO操作

JNI提供了在原生代碼中使用NIO(I/O)的函數蚪战,與數組操作相比更適合原生代碼和java應用程序之間傳送大量數據。

創(chuàng)建直接字節(jié)緩沖區(qū)

原生代碼可以創(chuàng)建java應用程序使用的直接字節(jié)緩沖區(qū)施籍,該過程是以提供一個原生C字節(jié)數組為基礎概漱,例如:

unsigned char* buffer = (unsigned  char*) malloc(1024)
……
jobject directBuffer;
directBuffer = (*env)->NewDirectByteBuffer(env,buffer,1024);

直接字節(jié)緩沖區(qū)獲取

java應用程序中也可以創(chuàng)建直接字節(jié)緩沖區(qū),在原生代碼中調用GetDirectBufferAddress函數可以獲取原生自己數組的內存地址瓤摧。例如:


unsigned char* buffer
buffer = (unsigned char*) (*env)->GetDirectBufferAddress(env,directBuffer);

JNI訪問java對象屬性

// 實例域
private String instanceField = "Instance Field ";
// 靜態(tài)域
private static String staticField = "Static Field ";

獲取域ID
JNI提供了用域ID訪問兩類域的方法照弥,可以通過給定實例的class對象獲取域ID,用GetObjectClass函數可以獲得class對象这揣,例如:


jclass clazz
clazz = (*env)->GetObjectClass(env,instance);

1.使用GetFieldID獲取實例域的ID

jfieldID instanceFieldId;
instanceField = (*env)->GetFieldID(env,clazz,"instanceFieldId","Ljava/lang/String;");

2.使用GetStaticFieldID獲取靜態(tài)域的ID

jfieldID staticFieldId;
staticFieldId = (*env)->GetStaticFieldID(env,clazz,"staticFieldId","Ljava/lang/String;");

獲取域
在獲得域ID之后,可以用GetField函數獲得實際的實例域机打,例如:

1.獲得實例域

jstring instanceFieldId;
instanceField = (*env)->GetObjectField(env,clazz,"instanceFieldId");

2.獲得靜態(tài)域


jfieldID staticField;
staticFieldId = (*env)->GetStaticObjectField(env,clazz,"staticFieldId");

兩個函數的最后一個參數是java中表示域類型的域描述符片迅,其中”Ljava/lang/String;”表明域類型是Sting。

JNI調用Java方法

public class WJavaClass{
// 實例方法
private String instanceMethod(){
return "Instance Method";
}
// 靜態(tài)方法
private static String staticMethod(){
return "StaticMethod";
}
}

獲取方法ID
JNI提供了用方法ID訪問兩類方法的途徑芥挣,可以用給定實例的class對象獲得方法ID耻台。用GetMethodID函數獲得實例方法的方法ID,例如:

jmethodID instanceMethodId;
instanceMethodId = (*env)->GetMethodID(env,clazz,"instanceMethod","()Ljava/lang/String;");

用GetStaticMethodID函數獲得靜態(tài)域的方法ID粘我,例如:


jmethodID staticMethodId;
staticMethodId = (*env)->GetStaticMethodID(env,clazz,"staticMethod","()Ljava/lang/String;");

兩個函數的最后一個參數均表示方法描述符痹换,在Java中表示方法簽名都弹。

調用方法
可以以方法ID為參數通過CallMethod類函數調用實際的實例方法,例如:
1.調用實例方法


jstring instanceMethodResult;
instanceMethodResult = (*env)->CallStringMethod(env,instance,"instanceMethodId");

2.調用靜態(tài)方法

jstring staticMethodResult;
staticMethodResult = (*env)->CallStaticStringMethod(env,clazz,"staticMethodId");

JNI調用Java靜態(tài)方法案例

public class HelloJni extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        callJavaStaticMethod();
    }

    public native String callJavaStaticMethod();

    static {
        System.loadLibrary("hello-jni");
    }

    // 靜態(tài)方法
    private static String staticMethod() {
        return "StaticMethod Castiel";
    }
}
#include <string.h>
#include <jni.h>
#include <android/log.h>

JNIEXPORT void JNICALL
Java_com_example_hellojni_HelloJni_callJavaStaticMethod(JNIEnv *env, jclass type) {

    jclass jniClass = (*env)->FindClass(env, "com/example/hellojni/HelloJni");
    if (NULL == jniClass) {
        __android_log_print(ANDROID_LOG_INFO,"HelloJni","can't find jclass");
        return;
    }
    jmethodID getMId = (*env)->GetStaticMethodID(env, jniClass, "staticMethod",
                                                              "()Ljava/lang/String;");
    if (NULL == getMId) {
        __android_log_print(ANDROID_LOG_INFO,"HelloJni","can't find method getStringFromStatic from JniClass");
        return;
    }
    jstring result = (*env)->CallStaticObjectMethod(env, jniClass, getMId);
    const char *resultChar = (*env)->GetStringUTFChars(env, result, NULL);
    (*env)->DeleteLocalRef(env, jniClass);
    (*env)->DeleteLocalRef(env, result);
    __android_log_print(ANDROID_LOG_INFO,"HelloJni",resultChar);

JNI異常處理

調用throwingMethod方法時,accessMethod原生方法需要顯示地做異常處理框杜。JNI提供了ExceptionOccurred函數查詢虛擬機中是否有掛起的現象。例如振劳,原生代碼中的異常處理:

jthrowable ex;
……
(*env)->CallVoidMethod(env,instance,throwingMethodId);
ex = (*env)->ExceptionOccurred(env);
if(0 != ex){
(*env)->ExceptionClear(env);
/*Exception handler*/
}

拋出異常

public class WJavaClass{
// 拋出方法
private void throwingMethod() throws NullPointerException{
throw new NullPointerException("Null Pointer");
}
}
JNI也允許原生代碼拋出異常油狂。因為異常是java類,應該先用FindClass函數找到異常類专筷。用ThrowNew函數可以初始化且拋出新的異常,例如:

jclass clazz;
……
clazz = (*env)->FindClass(env,"java/lang/NullPointerException");
if(0 !=clazz){
(*env)->ThrowNew(env,clazz,"Exception message");
}

JNI的局部引用和全局引用和弱全局引用

局部引用
大多數JNI函數返回局部引用吮旅。局部引用不能在后續(xù)的調用中被緩存及重用味咳,主要是因為它們的使用期限僅限于原生方法,一旦原生函數返回莺葫,局部引用即被釋放。例如,使用FindClass函數返回一個局部引用贸铜,當原生方法返回時,它被自動釋放烤镐,也可以用DeleteLocalRef函數顯示釋放原生代碼:


jclass clazz
clazz = (*env)->FindClass(env,"java/lang/String");
……
(*env)->DeleteLocalRef(env,clazz);

根據JNI的規(guī)范棍鳖,虛擬機應該允許原生代碼創(chuàng)建最少16個局部引用

全局引用
全局引用在原生方法的后續(xù)調用過程中依然有效碗旅,除非它們被原生代碼顯示釋放镜悉。
1.創(chuàng)建全局引用
可以用NewGlobalRef函數將局部引用初始化為全局引用,例如:


jclass localclazz
jclass globalclazz
……
localclazz = (*env)->FindClass(env,"java/lang/String");
globalclazz = (*env)->NewGlobalRef(env,localclazz );
……
 (*env)->DeleteLocalRef(env,localclazz );

2.刪除全局引用
當原生代碼不再需要一個全局引用時旧困,可以隨時用DeleteLocalRef函數釋放它。

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

弱全局引用
弱全局引用和全局引用一樣吼具,在原生方法的后續(xù)調用過程中依然有效矩距。與全局引用不同,弱全局引用并不阻止?jié)撛诘膶ο蟊焕厥铡?br> 1.創(chuàng)建弱全局引用
用NewWeakGlobalRef函數對弱全局引用進行初始化锥债,例如:


jclass weakGlobalclazz
weakGlobalclazz = (*env)->NewWeakGlobalRef(env,localclazz);

2.弱全局引用的有效性校驗
可以使用IsSameObject函數檢驗一個弱全局引用是否仍然指向活動的類實例,例如:


if(JNI_FALSE == (*env)->IsSameObject(env,weakGlobalClazz,NULL)){
/*對象仍然處于活動狀態(tài)且可以使用*/
}else{
/*對象被垃圾回收期收回毅整,不能使用*/
}

刪除弱全局引用
可以隨時使用DeleteWeakGlobalRef函數釋放弱全局引用绽左。

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

JNI常用函數大全

http://blog.csdn.net/qinjuning/article/details/7595104

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市戏蔑,隨后出現的幾起案子鲁纠,更是在濱河造成了極大的恐慌,老刑警劉巖改含,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捍壤,死亡現場離奇詭異,居然都是意外死亡鹃觉,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門祷肯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人佑笋,你說我怎么就攤上這事¢夏耄” “怎么了颠锉?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長琼掠。 經常有香客問我,道長悼瓮,這世上最難降的妖魔是什么艰猬? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮命贴,結果婚禮上,老公的妹妹穿的比我還像新娘胸蛛。我一直安慰自己樱报,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布民珍。 她就那樣靜靜地躺著笤受,像睡著了一般穷缤。 火紅的嫁衣襯著肌膚如雪箩兽。 梳的紋絲不亂的頭發(fā)上章喉,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音落包,去河邊找鬼。 笑死咐蝇,一個胖子當著我的面吹牛,可吹牛的內容都是我干的抹腿。 我是一名探鬼主播旭寿,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盅称!你這毒婦竟也來了?” 一聲冷哼從身側響起混狠,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤疾层,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后俯逾,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡桌肴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年坠七,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彪置。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡蝇恶,死狀恐怖,靈堂內的尸體忽然破棺而出潘懊,到底是詐尸還是另有隱情姚糊,我是刑警寧澤授舟,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布释树,位于F島的核電站肠槽,受9級特大地震影響奢啥,放射性物質發(fā)生泄漏。R本人自食惡果不足惜筋栋,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一正驻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧姑曙,春花似錦、人聲如沸伤靠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卦洽。三九已至,卻和暖如春阀蒂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蚤霞。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工昧绣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓鉴嗤,卻偏偏與公主長得像序调,于是被迫代替她去往敵國和親兔簇。 傳聞我的和親對象是個殘疾皇子发绢,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344