JNI高階知識(shí)總結(jié)

JNI與NDK的關(guān)系

NDK可以為我們生成了C/C++的動(dòng)態(tài)鏈接庫(kù),JNI是java和C/C++溝通的接口,兩者與android沒有半毛錢關(guān)系,只因?yàn)榘沧渴莏ava程序語(yǔ)言開發(fā),然后通過JNI又能與C/C++溝通,所以我們可以使用NDK+JNI來實(shí)現(xiàn)“Java+C”的開發(fā)方式。

JNIEnv與JavaVM

JNIEnv 概念 : 是一個(gè)線程相關(guān)的結(jié)構(gòu)體, 該結(jié)構(gòu)體代表了 Java 在本線程的運(yùn)行環(huán)境 ;

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

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

JNIEnv 體系結(jié)構(gòu)
線程相關(guān) : JNIEnv 是線程相關(guān)的, 即 在 每個(gè)線程中 都有一個(gè) JNIEnv 指針, 每個(gè)JNIEnv 都是線程專有的, 其它線程不能使用本線程中的 JNIEnv, 線程 A 不能調(diào)用 線程 B 的 JNIEnv;

*.so的入口函數(shù)

JNI_OnLoad()與JNI_OnUnload()
當(dāng)Android的VM(Virtual Machine)執(zhí)行到System.loadLibrary()函數(shù)時(shí),首先會(huì)去執(zhí)行C組件里的JNI_OnLoad()函數(shù)。它的用途有二:
(1)告訴VM此C組件使用那一個(gè)JNI版本。如果你的.so檔沒有提供JNI_OnLoad()函數(shù),VM會(huì)默認(rèn)該.so檔是使用最老的JNI 1.1版本。由于新版的JNI做了許多擴(kuò)充,如果需要使用JNI的新版功能,例如JNI 1.4的java.nio.ByteBuffer,就必須藉由JNI_OnLoad()函數(shù)來告知VM。
(2)由于VM執(zhí)行到System.loadLibrary()函數(shù)時(shí),就會(huì)立即先呼叫JNI_OnLoad(),所以C組件的開發(fā)者可以藉由JNI_OnLoad()來進(jìn)行C組件內(nèi)的初期值之設(shè)定(Initialization) 。

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

JNI字符串函數(shù)

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

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

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

GetStringLength 返回Unicode格式字符串的長(zhǎng)度

GetStringUTFLength 返回UTF-8格式字符串的長(zhǎng)度

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

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

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

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

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

int sprintf( char *buffer, const char *format, [ argument] … );
類似于printf,根據(jù)格式化字符串format,將后續(xù)參數(shù)列表中的參數(shù)逐個(gè)輸出。不過輸出目標(biāo)不是標(biāo)準(zhǔn)輸出終端,而是字符串buffer。

字符串操作

C字符串——>java字符串

例如:下面的函數(shù)以一個(gè)C字符串為參數(shù),并返回一個(gè)Java字符串引用類型jstring值。

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

注意,在內(nèi)存溢出的情況下,NewString函數(shù)將返回NULL以通知原生代碼虛擬機(jī)中有異常拋出。

java字符串轉(zhuǎn)換成C字符串

為了在原生代碼中使用java字符串,需要先將java字符串轉(zhuǎn)換成C字符串,我們使用GetStringChars函數(shù)可以將Unicode格式的java字符串轉(zhuǎn)換成C字符串,使用GetStringUTFChars函數(shù)可以將UTF-8格式的Java字符串轉(zhuǎn)換成C字符串。這些函數(shù)的第三個(gè)參數(shù)均為可選參數(shù),該可選參數(shù)名是isCopy,它讓調(diào)用者確定返回的C字符串地址指向副本還是指向堆中的固定對(duì)象。例如:

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 函數(shù)GetStringUTFChars函數(shù)獲得的C字符串在原生代碼中使用完成之后需要正確的釋放,否則將會(huì)引起內(nèi)存泄漏。通常我們使用ReleaseStringChars函數(shù)釋放Unicode格式的字符串,使用ReleaseUTFStringChars函數(shù)釋放UTF-8格式的字符串.

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

數(shù)組操作

JNI把java數(shù)組當(dāng)成引用類型來處理,JNI提供必要的函數(shù)訪問和處理Java數(shù)組。

創(chuàng)建數(shù)組

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

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

訪問數(shù)組元素

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

對(duì)副本的操作

1.java數(shù)組轉(zhuǎn)C數(shù)組
GetArrayRegion函數(shù)將給定的基本Java數(shù)組賦值到給對(duì)你給的C數(shù)組中,例如:


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

2.C數(shù)組轉(zhuǎn)java數(shù)組
原生代碼可以像使用普通的C數(shù)組一樣使用和修改數(shù)組元素。當(dāng)原生代碼想將所做的修改提交給java數(shù)組時(shí),可以使用SetArrayRegion函數(shù)將C數(shù)組復(fù)制回java數(shù)組中。例如:

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

注意:當(dāng)數(shù)組很大時(shí),對(duì)數(shù)組做復(fù)制操作會(huì)引起性能問題。

對(duì)直接指針的操作

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

jint nativeDirectArray;
jboolean isCopy;

nativeDirectArray = (*env)->GetIntArrayElements(env,javaArray,&isCopy);
其中,第三個(gè)&isCopy參數(shù)為可選參數(shù),讓調(diào)用者確定返回的C字符串地址指向副本還是指向堆中的固定對(duì)象。 
因?yàn)榭梢韵衿胀ǖ腃數(shù)組一樣訪問和處理數(shù)組元素,因此JNI沒提供訪問和處理數(shù)組元素的方法,JNI要求原生代碼用完這些指針后立刻釋放,否則會(huì)出現(xiàn)內(nèi)存溢出??梢允褂肑NI提供的ReleaseArrayElements函數(shù)釋放GetArrayElements返回的C數(shù)組。例如:


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

NIO操作

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

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

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

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

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

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


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

JNI訪問java對(duì)象屬性

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

獲取域ID
JNI提供了用域ID訪問兩類域的方法,可以通過給定實(shí)例的class對(duì)象獲取域ID,用GetObjectClass函數(shù)可以獲得class對(duì)象,例如:


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

1.使用GetFieldID獲取實(shí)例域的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函數(shù)獲得實(shí)際的實(shí)例域,例如:

1.獲得實(shí)例域

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

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


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

兩個(gè)函數(shù)的最后一個(gè)參數(shù)是java中表示域類型的域描述符,其中”Ljava/lang/String;”表明域類型是Sting。

JNI調(diào)用Java方法

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

獲取方法ID
JNI提供了用方法ID訪問兩類方法的途徑,可以用給定實(shí)例的class對(duì)象獲得方法ID。用GetMethodID函數(shù)獲得實(shí)例方法的方法ID,例如:

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

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


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

兩個(gè)函數(shù)的最后一個(gè)參數(shù)均表示方法描述符,在Java中表示方法簽名。

調(diào)用方法
可以以方法ID為參數(shù)通過CallMethod類函數(shù)調(diào)用實(shí)際的實(shí)例方法,例如:
1.調(diào)用實(shí)例方法


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

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

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

JNI調(diào)用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異常處理

調(diào)用throwingMethod方法時(shí),accessMethod原生方法需要顯示地做異常處理。JNI提供了ExceptionOccurred函數(shù)查詢虛擬機(jī)中是否有掛起的現(xiàn)象。例如,原生代碼中的異常處理:

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也允許原生代碼拋出異常。因?yàn)楫惓J莏ava類,應(yīng)該先用FindClass函數(shù)找到異常類。用ThrowNew函數(shù)可以初始化且拋出新的異常,例如:

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

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

局部引用
大多數(shù)JNI函數(shù)返回局部引用。局部引用不能在后續(xù)的調(diào)用中被緩存及重用,主要是因?yàn)樗鼈兊氖褂闷谙迌H限于原生方法,一旦原生函數(shù)返回,局部引用即被釋放。例如,使用FindClass函數(shù)返回一個(gè)局部引用,當(dāng)原生方法返回時(shí),它被自動(dòng)釋放,也可以用DeleteLocalRef函數(shù)顯示釋放原生代碼:


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

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

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


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

2.刪除全局引用
當(dāng)原生代碼不再需要一個(gè)全局引用時(shí),可以隨時(shí)用DeleteLocalRef函數(shù)釋放它。

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

弱全局引用
弱全局引用和全局引用一樣,在原生方法的后續(xù)調(diào)用過程中依然有效。與全局引用不同,弱全局引用并不阻止?jié)撛诘膶?duì)象被垃圾回收。
1.創(chuàng)建弱全局引用
用NewWeakGlobalRef函數(shù)對(duì)弱全局引用進(jìn)行初始化,例如:


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

2.弱全局引用的有效性校驗(yàn)
可以使用IsSameObject函數(shù)檢驗(yàn)一個(gè)弱全局引用是否仍然指向活動(dòng)的類實(shí)例,例如:


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

刪除弱全局引用
可以隨時(shí)使用DeleteWeakGlobalRef函數(shù)釋放弱全局引用。

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

JNI常用函數(shù)大全

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容