NDK學習筆記(二)使用JNI同原生代碼通訊

一般要使用 JNI 技術來實現 Java 應用程序和原生代碼的通訊。java

任何使用JNI的操做都須要兩次或者三次函數調用,所以要實現大量的原生方法並讓它們同Java類保持同步很容易編程一件很是艱辛的工做。編程

而利用一些開源的方案則能夠幫助咱們基於現有的原生代碼接口自動生成 JNI 的代碼。數組

  • 學習這項技術,首先須要搞清楚下面這些關鍵概念:
  • 原生代碼如何被Java代碼調用到。
  • 原生方法的聲明。
  • 從共享庫加載原生模塊。
  • 使用 C/C++ 來實現原生方法。

原生方法的聲明

在Java代碼中使用native關鍵字能夠聲明原生方法,例如:多線程

public native String stringFromJNI();app

注意只是聲明而已,「()」這對括弧後面直接就是分號了。函數

加載共享庫

static { System.loadLibrary("hello-jni");//libhello-jni.so }學習

之因此要在靜態代碼塊中調用 System.loadLibrary ,就是爲了Java類首次加載和初始化時就能讓原生代碼的實現被載入。spa

在原生代碼中實現聲明的方法

原生代碼是C/C++的,所以要有一個頭文件明確要實現那些方法,JDK爲咱們提供了javah(頭文件生成器)來根據已經編譯好的class生成頭文件,例如:線程

javah –classpath bin/classes com.example.hellojni.HelloJni指針

javah 能夠配置到 Eclipse 的 External Tools 中,方便使用。

關於原生代碼中聲明的方法

Java代碼中對原生方法的聲明能夠不帶上參數,但對應的原生函數式帶有參數的,例如:

JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI (JNIEnv *, jobject);

JNIEnv 是一個指針,指向可供JNI使用的函數列表。JNIEnv所指向的類型根據語言不一樣(C/C++)而不一樣,若是是C,則其所指向的類型就是 JNINativeInterface 結構,若是是C++,則是一個擁有成員方法的類實例。

若是要返回一個 String ,C的寫法是:

return (*env)->NewStringUTF(env, "Hello from JNI !");//由於C中的JNI函數不清楚當前的JNI環境,因此要傳入env。

而C++的寫法則是:

return env->NewStringUTF("Hello from JNI !");

jobject 是一個引用了 HelloJni 類的實例的 Java 對象。

原生實例方法的定義示例以下:

JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI (JNIEnv * env, jobject thiz);

原生靜態方法的定義示例以下:

JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI (JNIEnv * env, jclass clazz);

關於數據類型

固然是分爲了原生類型和引用類型。

原生類型在Java , JNI 和 C/C++中有各自的對應的表示方法,引用類型也是。

字符串操做

新建字符串:

jstring javaString; javaString = (*env)->NewStringUTF(env, "Hello World!");

Java 字符串轉成 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."); }

數組操做

新建數組:

jintArray javaArray; javaArray = (env)->NewIntArray(env, 10); if (0 != javaArray) { / You can now use the array. */ }

經過操做副原本訪問數組元素:

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

將在 C 數組上的修改提交到 Java Array 中:

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

直接在指針上對數組進行操做:

jint* nativeDirectArray; jboolean isCopy; nativeDirectArray = (*env)->GetIntArrayElements(env, javaArray, &isCopy);

內存空間操做

新建:

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

獲取:

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

訪問 Java 類中的域

public class JavaClass { // Instance field private String instanceField = "Instance Field"; // Static field private static String staticField = "Static Field"; ... }

獲取域ID:

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

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

獲取靜態域ID:

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

調用 Java 類中的方法

public class JavaClass { //Instance method. private String instanceMethod() { return "Instance Method"; } //Static method. private static String staticMethod() { return "Static Method"; } ... }

獲取方法ID:

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

獲取靜態方法ID:

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

調用方法:

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

調用靜態方法:

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

關於方法和域的描述符

Java Type Signature Boolean Z Byte B Char C Short S Int I Long J Float F Double D fully-qualified-class Lfully-qualified-class; type[] [type method type (arg-type)ret-type

使用 javap 能夠從 class 文件中提取出域和方法的描述符:

javap –classpath bin/classes –p –s com.example.hellojni.HelloJni

捕獲異常

public class JavaClass { //Throwing method. private void throwingMethod() throws NullPointerException { throw new NullPointerException("Null pointer"); } //Access methods native method. private native void accessMethods(); }

原生代碼是這麼寫的:

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

拋出異常

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

本地和全局引用

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

新增全局引用:

jclass localClazz; jclass globalClazz; ... localClazz = (*env)->FindClass(env, "java/lang/String"); globalClazz = (*env)->NewGlobalRef(env, localClazz); ... (*env)->DeleteLocalRef(env, localClazz);

刪除全局引用:

(*env)->DeleteGlobalRef(env, globalClazz);

全局弱引用

新增全局弱引用:

jclass weakGlobalClazz; weakGlobalClazz = (*env)->NewWeakGlobalRef(env, localClazz);

驗證全局弱引用是否可用:

if (JNI_FALSE == (*env)->IsSameObject(env, weakGlobalClazz, NULL)) { //Object is still live and can be used. } else { //Object is garbage collected and cannot be used. }

刪除全局弱引用:

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

多線程

Java代碼是這樣寫的:

synchronized(obj) { //Synchronized thread-safe code block. }

原生代碼應該這樣寫:

if (JNI_OK == (*env)->MonitorEnter(env, obj)) { //Error handling. } //Synchronized thread-safe code block. if (JNI_OK == (*env)->MonitorExit(env, obj)) { // Error handling. }

原生多線程

將當前線程同虛擬機綁定和解綁: JavaVM* cachedJvm; ... JNIEnv* env; ... //Attach the current thread to virtual machine. (*cachedJvm)->AttachCurrentThread(cachedJvm, &env, NULL); //Thread can communicate with the Java application using the JNIEnv interface. //Detach the current thread from virtual machine. (*cachedJvm)->DetachCurrentThread(cachedJvm);

相關文章
相關標籤/搜索