NDK之旅必須要知道的一些基本知識

codeGoogle發表於2017-08-22

JNIEnv

  • 定義: 相當於一個jni上下文物件。

  • 作用: 通過JNIEnv的指標能夠對Java端的程式碼進行操作:

    • a.建立Java物件.

      jstring str = (env).NewStringUTF("終端研發部");
      jclass jclazz = (
      env).GetObjectClass(obj);

      • b.呼叫Java物件的方法。

        jclz = (*env)->FindClass(env, "java/lang/String");
        jdouble doub = (*env).GetStaticDoubleField()複製程式碼
      • c.獲取及設定Java物件的屬性。

        jintArray arrayResult = NULL;
        jclass  jclazz = (*env).GetObjectClass(obj);
        jint * elements =(*env).GetIntArrayElements(array,NULL);複製程式碼

        Get/Set[Static]Method

        JNI中通常用JType指代Java環境中的類。

typedef _jobject *jobject;  
typedef _jclass *jclass;  
typedef _jthrowable *jthrowable;  
typedef _jstring *jstring;  
typedef _jarray *jarray;  
typedef _jbooleanArray *jbooleanArray;  
typedef _jbyteArray *jbyteArray;  
typedef _jcharArray *jcharArray;  
typedef _jshortArray *jshortArray;  
typedef _jintArray *jintArray;  
typedef _jlongArray *jlongArray;  
typedef _jfloatArray *jfloatArray;  
typedef _jdoubleArray *jdoubleArray;  
typedef _jobjectArray *jobjectArray;複製程式碼

JType都繼承自JObject

[cpp] view plain copy
class _jobject {};  
class _jclass : public _jobject {};  
class _jthrowable : public _jobject {};  
class _jstring : public _jobject {};  
class _jarray : public _jobject {};  
class _jbooleanArray : public _jarray {};  
class _jbyteArray : public _jarray {};  
class _jcharArray : public _jarray {};  
class _jshortArray : public _jarray {};  
class _jintArray : public _jarray {};  
class _jlongArray : public _jarray {};  
class _jfloatArray : public _jarray {};  
class _jdoubleArray : public _jarray {};  
class _jobjectArray : public _jarray {};複製程式碼

jobject的理解

JNIEXPORT void JNICALL Java_com_jue_testnative_TestNative1_hello(JNIEnv *, jobject);

這裡是jobject指代的在Java中呼叫native方法的java類例項複製程式碼

獲取jclass的方法

a. jclass FindClass(const char *name)

b. jclass GetObjectClass(jobject obj)複製程式碼

FindClass注意事項:

  • 注意: 會在ClassPath下面尋找類。需要傳入完整類名,注意包與包之間用’/'。

    jclass class_str = env->FindClass("java/lang/String");複製程式碼

jfiledID/jmethodID的獲取

  • 在natvie方法中獲取/設定欄位的值,或者方法呼叫,需要先獲取相應的field/method的ID

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

    • jmethodID GetMethodID(jclass clazz, const char name, const char sig)

注意sig用來處理函式過載引起的不確定。

然後通過ID獲取

jobject GetObjectField(jobject obj, jfieldID fieldID)   

jobject CallObjectMethod(jobject obj, jmethodID methodID, ...)  複製程式碼

獲取方法的簽名

  • javap -s 命令工具可以檢視一個類的方法的簽名

    javap -s xxx.class

Sin簽名含義細節

型別 相應的簽名
boolean Z
byte B
char C
short S
int I
long L
float F
double D
void V
String Ljava/lang/String
Array [Ljava/lang/Object
Method (para s1,para s2) 返回值簽名

jni如何呼叫Java裡面的方法的

NIEnv提供了一下函函式(能夠實現子類物件呼叫父類方法的功能)

  • CallMethod,
  • CallStaticMethod,
  • CallNonvirtualMethod。

       CallVoidMethod(jobject obj, jmethodID methodID, ...)
    
      void CallVoidMethodV(jobject obj, jmethodID methodID, va_list args)
    
      void CallVoidMethodA(jobject obj, jmethodID methodID, jvalue* args)複製程式碼

    呼叫例項方法的三種方法

  • CallMethod(jobject obj, jmethodID id, ...);

      boolean funcation(int i, double d, char c) {  
      }  
    
      env->CallBooleanMethod( obj, id_function , 100L, 3.44, L'3');複製程式碼
  • CallMethodV(jobject obj, jmethodID id,va_list lst);

  • CallMethodA(jobject obj, jmethodID id, jvalue* v);
    ```
    jvalue是一個聯合體

    [cpp] view plain copy
    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 = 100L;  
args[1].d = 3.44;  
args[2].c = L'3';  
env->CallBooleanMethodA(obj, id_funcation, args);  
delete [] args;      
```複製程式碼

Java物件的建立

  • 方式-NewObject

使用NewObject來建立Java物件。

jobject NewObject(jclass clazz, jmethodID methodID, ...)複製程式碼

需要先活的相應構造器的name,方法名設定為,另外返回值的簽名是Void

jmethodID GetMethodID(jclass clazz, const char *name, const char *sig)複製程式碼

例子:

jclass class_date = env->FindClass("java/util/Date");  
jmethodID method_id = env->GetMethodID(class_date,"<init>","()V");  
jobject now = env->NewObject(class_date, method_id);複製程式碼
  • b.方式-AllocObject
    ```
    jclass cls = (env).GetObjectClass(obj);
    jobject oobj = (
    env).AllocObject(cls);

####  Java字串和C/C++字串之間的轉換

-  a.在java中字串String物件是Unicode(UTF-16)碼,每個字元無論中文還是英文都佔用兩個位元組。

- b.可以通過JNI介面把Java中的字串轉換成C/C++中的寬字串,Java也可以傳一個UTF-8的字串(char*) 到C/C++中。

- c.反過來C++可以通過把一個寬字串,或者一個UTF-8的字串來建立一個Java端的物件。

#### memset的注意事項
- C庫函式 void *memset(void *str, int c, size_t n) 複製字元c(unsigned char型別)引數str指向的字串的前n個字元。

- memset是以位元組為單位,初始化記憶體塊。
當初始化一個位元組單位的陣列時,可以用memset把每個陣列單元初始化成任何你想要的值

    `複製程式碼
char data[10];  
memset(data, 1, sizeof(data));    // right  
memset(data, 0, sizeof(data)); 

char str[50];
strcpy(str,"This is string.h library function");
puts(str);
memset(str,'$',7);
puts(str);

```複製程式碼
  • 在初始化其他基礎型別時,則需要注意,比如
int data[10];  
memset(data, 0, sizeof(data));    // right  
memset(data, -1, sizeof(data));    // right  
memset(data, 1, sizeof(data));    // wrong, data[x] would be 0x0101 instead of 1複製程式碼

獲取字串

  • a.取得與某個jstring物件相關的Java字串
方法 |     作用
---|---
GetStringChars |     取得UTF-16編碼的寬字串(char*)
GetStringUTFChars |     取得UTF-8編碼的字串(char*)複製程式碼

注意在不使用到的使用,要注意使用ReleaseStringChars,或者ReleaseStringUTFChars釋放拷貝的記憶體,或Java物件的引用。

還可以使用下面的方法,這個方法可以增加返回jvm中java物件的可能性,但是還是有可能返回相應java串的拷貝。

這個方法沒有相應的GetStringUTFCritical,由於Java字串使用的是UTF-16,要轉換成UTF-8編碼的串本來就需要一次拷貝動作。

陣列分為2種

  • a.基本型別的陣列。

  • b.物件型別(Object[])的陣列。

    有一個通用於兩種不同陣列的的函式:得到資料的長度函式:GetArrayLength

處理基本型別陣列

跟處理字串相似

GetArrayElements,可以把Java基本型別的陣列轉換成C/C++中的陣列,可以拷貝一份傳原生程式碼,也可以把指向Java中的指標直接傳回原生程式碼。

需要用

ReleaseArrayElements

void ReleaseIntArrayElements(jintArray array,  
                             jint *elems,  
                             jint mode) {  
    functions->ReleaseIntArrayElements(this,array,elems,mode);  
}複製程式碼
mode的型別:
  • 0:對Java陣列進行更新,並釋放C/C++的陣列。

  • JNI_COMMIT: 對Java陣列進行更新但不釋放C/C++陣列。

  • JNI_ABORT:對Java陣列不進行更新,並釋放C/C++陣列。

相似GetStringUTFCritical的函式:為了直接傳回指向Java陣列的指標而加入的函式。

void * GetPrimitiveArrayCritical(jarray array, jboolean *isCopy) {  
    return functions->GetPrimitiveArrayCritical(this,array,isCopy);  
}  
void ReleasePrimitiveArrayCritical(jarray array, void *carray, jint mode) {  
    functions->ReleasePrimitiveArrayCritical(this,array,carray,mode);  
}複製程式碼

GetArrayRegion 相似於GetStringRegion的函式:沒有Release方法,因為我們是拷貝

void GetIntArrayRegion(jintArray array,  
                       jsize start, jsize len, jint *buf) {  
    functions->GetIntArrayRegion(this,array,start,len,buf);  
}複製程式碼

SetArrayRegion 可以把指定範圍內的Java陣列元素用C++中的元素賦值。

void SetIntArrayRegion(jintArray array, jsize start, jsize len,  
                       const jint *buf) {  
    functions->SetIntArrayRegion(this,array,start,len,buf);  
}複製程式碼

建立一個基本型別的Java陣列

Array NewArray(jsize size),指定長度後返回相應Java基本型別的陣列。

處理Object型別陣列

JNI中沒有把Java物件型別的陣列轉換成C++中的jobject[]的函式。而是直接通過Get/SetObjectArrayElement來對Java的Object陣列進行操作。

jobject GetObjectArrayElement(jobjectArray array, jsize index) {  
    return functions->GetObjectArrayElement(this,array,index);  
}  
void SetObjectArrayElement(jobjectArray array, jsize index,  
                           jobject val) {  
    functions->SetObjectArrayElement(this,array,index,val);  
}複製程式碼

使用上述方式不需要釋放資源。
可以根據陣列長度和初始值來建立某個類的陣列

jobjectArray NewObjectArray(jsize len, jclass clazz,  
                            jobject init) {  
    return functions->NewObjectArray(this,len,clazz,init);  
}複製程式碼

java物件在JNI中的引用

在jvm中建立的物件被傳到本地的C/C++程式碼的時候,會產生引用。根據jvm的垃圾回收機制,只要引用存在,就不會觸發該引用指向物件的被回收。

這些引用分為三種

  • 區域性引用Local Reference:是最常見的引用,區域性引用只在native函式中有效。區域性引用的存在會阻止其指向物件的回收。

  • 全域性引用Global Reference:

    • 可以跨越多個執行緒

    • 在多個navtive方法中有效、

    • 全域性引用存在期間會阻止其引用java物件在jvm的回收。

    • 全域性引用的建立不是JNI自動建立的。

    • 全域性引用的建立要顯示的呼叫NewGlobalRef函式,而釋放需要呼叫ReleaseGlobalRef

  • 弱全域性引用:

    • 與全域性引用相似,建立和釋放都需要由程式設計人員來進行。

    • 多個執行緒,多個native方法中有效。

      區別:這個引用不會阻止jvm回收其指向的引用。

      NewWeakGlobalRef與ReleaseWeakGlobalRef來建立和釋放引用。

IsSameObject

IsSameObject在弱全域性引用中有一個特別的功能。

把NULL傳給要判斷的object,來判斷弱全域性引用指向的java物件是否被回收。

快取jfieldID,jmethodID.

  • a.通過方法名+簽名來查詢jfieldID,jmethod開銷是非常大的。

  • b.快取方式:

    • 1.在使用的時候快取。(Caching at the Point of Use)

      使用static型別的區域性變數來儲存已經查詢過的ID,這樣就不會在每次呼叫的時候查詢,而是隻查詢一次。

    • 2.在java類初始化的時候快取。(Caching at Class's inititalizer)

      這種載入方式在jvm類的載入和重新載入都會重新呼叫該native方法重新計算ID

public class TestNative  
{  
    static {  
        initNativeIDs();  
    }  
    static natvie void initNativeIDs();  
    int propInt = 0;  
}複製程式碼
JNIEXPORT void JNICALL    Java_TestNative_initNativeIDs(JNIEnv *env, jobject object)  
{  
    ......  
    g_propInt_id = GetFieldID(clazz,  "propInt", "I" );  

}複製程式碼

相信自己,沒有做不到的,只有想不到的

如果你覺得此文對您有所幫助,歡迎入群 QQ交流群 :644196190
微信公眾號:終端研發部

技術+職場
技術+職場

相關文章