JNI 中有兩種數組操做,基礎數據類型數組和對象數組,JNI 對待基礎數據類型數組和對象數組是不同的。java
對於基本數據類型數組,JNI 都有和 Java 相對應的結構,在使用起來和基本數據類型的使用相似。git
在 Android JNI 基礎知識篇提到了 Java 數組類型對應的 JNI 數組類型。好比,Java int 數組對應了 jintArray,boolean 數組對應了 jbooleanArray。github
如同 String 的操做同樣,JNI 提供了對應的轉換函數:GetArrayElements、ReleaseArrayElements。數組
intArray = env->GetIntArrayElements(intArray_, NULL); env->ReleaseIntArrayElements(intArray_, intArray, 0);
另外,JNI 還提供了以下的函數:微信
將數組內容複製到 C 緩衝區內,或將緩衝區內的內容複製到數組上。函數
獲得數組中的元素個數,也就是長度。spa
返回一個指定數據類型的數組,而且經過 SetTypeArrayRegion 來給指定類型數組賦值。指針
如同 String 中的操做同樣,返回一個指定基礎數據類型數組的直接指針,在這兩個操做之間不能作任何阻塞的操做。code
實際操做以下:對象
// Java 傳遞 數組 到 Native 進行數組求和 private native int intArraySum(int[] intArray, int size);
對應的 C++ 代碼以下:
JNIEXPORT jint JNICALL Java_com_glumes_cppso_jnioperations_ArrayTypeOps_intArraySum(JNIEnv *env, jobject instance, jintArray intArray_, jint num) { jint *intArray; int sum = 0; // 操做方法一: // 如同 getUTFString 同樣,會申請 native 內存 intArray = env->GetIntArrayElements(intArray_, NULL); if (intArray == NULL) { return 0; } // 獲得數組的長度 int length = env->GetArrayLength(intArray_); LOGD("array length is %d", length); for (int i = 0; i < length; ++i) { sum += intArray[i]; } LOGD("sum is %d", sum); // 操做方法二: jint buf[num]; // 經過 GetIntArrayRegion 方法來獲取數組內容 env->GetIntArrayRegion(intArray_, 0, num, buf); sum = 0; for (int i = 0; i < num; ++i) { sum += buf[i]; } LOGD("sum is %d", sum); // 使用完了別忘了釋放內存 env->ReleaseIntArrayElements(intArray_, intArray, 0); return sum; }
假如須要從 JNI 中返回一個基礎數據類型的數組,對應的代碼以下:
// 從 Native 返回基本數據類型數組 private native int[] getIntArray(int num);
對應的 C++ 代碼以下:
/** * 從 Native 返回 int 數組,主要調用 set<Type>ArrayRegion 來填充數據,其餘數據類型相似操做 */ extern "C" JNIEXPORT jintArray JNICALL Java_com_glumes_cppso_jnioperations_ArrayTypeOps_getIntArray(JNIEnv *env, jobject instance, jint num) { jintArray intArray; intArray = env->NewIntArray(num); jint buf[num]; for (int i = 0; i < num; ++i) { buf[i] = i * 2; } // 使用 setIntArrayRegion 來賦值 env->SetIntArrayRegion(intArray, 0, num, buf); return intArray; }
以上例子,基本把相關的操做都使用上了,能夠發現和 String 的操做大都是類似的。
對於對象數組,也就是引用類型數組,數組中的每一個類型都是引用類型,JNI 只提供了以下函數來操做。
和基本數據類型不一樣的是,不能一次獲得數據中的全部對象元素或者一次複製多個對象元素到緩衝區。只能經過上面的函數來訪問或者修改指定位置的元素內容。
字符串和數組都是引用類型,所以也只能經過上面的方法來訪問。
例如在 JNI 中建立一個二維的整型數組並返回:
// 從 Native 返回二維整型數組,至關因而一個一維整型數組,數組中的每一項內容又是數組 private native int[][] getTwoDimensionalArray(int size);
二維數組具備特殊性在於,能夠將它當作一維數組,其中數組的每項內容又是一維數組。
具體 C++ 代碼以下:
/** * 從 Native 返回一個二維的整型數組 */ extern "C" JNIEXPORT jobjectArray JNICALL Java_com_glumes_cppso_jnioperations_ArrayTypeOps_getTwoDimensionalArray(JNIEnv *env, jobject instance, jint size) { // 聲明一個對象數組 jobjectArray result; // 找到對象數組中具體的對象類型,[I 指的就是數組類型 jclass intArrayCls = env->FindClass("[I"); if (intArrayCls == NULL) { return NULL; } // 至關於初始化一個對象數組,用指定的對象類型 result = env->NewObjectArray(size, intArrayCls, NULL); if (result == NULL) { return NULL; } for (int i = 0; i < size; ++i) { // 用來給整型數組填充數據的緩衝區 jint tmp[256]; // 聲明一個整型數組 jintArray iarr = env->NewIntArray(size); if (iarr == NULL) { return NULL; } for (int j = 0; j < size; ++j) { tmp[j] = i + j; } // 給整型數組填充數據 env->SetIntArrayRegion(iarr, 0, size, tmp); // 給對象數組指定位置填充數據,這個數據就是一個一維整型數組 env->SetObjectArrayElement(result, i, iarr); // 釋放局部引用 env->DeleteLocalRef(iarr); } return result; }
首先須要使用 NewObjectArray 方法來建立對象數組。
而後使用 SetObjectArrayElement 函數填充數據時,須要構建好每一個位置對應的對象。這裏就使用了 NewIntArray 來創造了一個對象,並給對象填充數據後,在賦值給對象數組。
經過一個 for 循環就完成給對象數組賦值的操做。
在建立對象數組時,有一個操做是找到對應的對象類型,經過 findClass 方法。findClass 的參數 [I 這裏就涉及到 Java 與 JNI 對應簽名的轉換。
在前一篇文章中,用表格列出了 Java 與 JNI 對應的數據類型格式的轉換關係,如今要列舉的是 Java 與 JNI 對應簽名的轉換關係。
這裏的簽名指的是在 JNI 中去查找 Java 中對應的數據類型、對應的方法時,須要將 Java 中的簽名轉換成 JNI 所能識別的。
對於 Java 中類或者接口的轉換,須要用到 Java 中類或者接口的全限定名,把 Java 中描述類或者接口的 . 換成 / 就行了,好比 String 類型對應的 JNI 描述爲:
java/lang/String // . 換成 /
對於數組類型,則是用 [ 來表示數組,而後跟一個字段的簽名轉換。
[I // 表明一維整型數組,I 表示整型 [[I // 表明二維整型數組 [Ljava/lang/String; // 表明一維字符串數組,
對應基礎類型字段的轉換:
Java 類型 | JNI 對應的描述轉 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
對於引用類型的字段簽名轉換,是大寫字母 L 開頭,而後是類的簽名轉換,最後以 ; 結尾。
Java 類型 | JNI 對應的描述轉換 |
---|---|
String | Ljava/lang/String; |
Class | Ljava/lang/Class; |
Throwable | Ljava/lang/Throwable |
int[] | "[I" |
Object[] | "[Ljava/lang/Object;" |
對於方法簽名描述的轉換,首先是將方法內全部參數轉換成對應的字段描述,並所有寫在小括號內,而後在小括號外再緊跟方法的返回值類型描述。
Java 類型 | JNI 對應的描述轉換 |
---|---|
String f(); | ()Ljava/lang/String; |
long f(int i, Class c); | (ILjava/lang/Class;)J |
String(byte[] bytes); | ([B)V |
這裏要注意的是在 JNI 對應的描述轉換中不要出現空格。
瞭解並掌握這些轉換後,就能夠進行更多的操做了,實現 Java 與 C++ 的相互調用。
好比,有一個自定義的 Java 類,而後再 Native 中打印類的對象數組的某一個字段值。
private native void printAnimalsName(Animal[] animal);
具體 C++ 代碼以下:
/** * 打印對象數組中的信息 */ extern "C" JNIEXPORT void JNICALL Java_com_glumes_cppso_jnioperations_ArrayTypeOps_printAnimalsName(JNIEnv *env, jobject instance, jobjectArray animals) { jobject animal; // 數組長度 int size = env->GetArrayLength(animals); // 數組中對應的類 jclass cls = env->FindClass("com/glumes/cppso/model/Animal"); // 類對應的字段描述 jfieldID fid = env->GetFieldID(cls, "name", "Ljava/lang/String;"); // 類的字段具體的值 jstring jstr; // 類字段具體值轉換成 C/C++ 字符串 const char *str; for (int i = 0; i < size; ++i) { // 獲得數組中的每個元素 animal = env->GetObjectArrayElement(animals, i); // 每個元素具體字段的值 jstr = (jstring) (env->GetObjectField(animal, fid)); str = env->GetStringUTFChars(jstr, NULL); if (str == NULL) { continue; } LOGD("str is %s", str); env->ReleaseStringUTFChars(jstr, str); } }
具體示例代碼可參考個人 Github 項目,歡迎 Star。
https://github.com/glumes/AndroidDevWithCpp
歡迎關注微信公衆號:【紙上淺談】,得到最新文章推送~~