轉載地址 Android JNI 數組 操作
JNI 中有兩種數組操作,基礎數據類型數組和對象數組,JNI 對待基礎數據類型數組和對象數組是不一樣的。
基本數據類型數組
對於基本數據類型數組,JNI 都有和 Java 相對應的結構,在使用起來和基本數據類型的使用類似。
在 Android JNI 基礎知識篇提到了 Java 數組類型對應的 JNI 數組類型。比如,Java int 數組對應了 jintArray,boolean 數組對應了 jbooleanArray。
如同 String 的操作一樣,JNI 提供了對應的轉換函數:GetArrayElements、ReleaseArrayElements。
intArray = env->GetIntArrayElements(intArray_, NULL);
env->ReleaseIntArrayElements(intArray_, intArray, 0);
另外,JNI 還提供瞭如下的函數:
- GetTypeArrayRegion / SetTypeArrayRegion
將數組內容複製到 C 緩衝區內,或將緩衝區內的內容複製到數組上。
- GetArrayLength
得到數組中的元素個數,也就是長度。
- NewTypeArray
返回一個指定數據類型的數組,並且通過 SetTypeArrayRegion 來給指定類型數組賦值。
- GetPrimitiveArrayCritical / ReleasePrimitiveArrayCritical
如同 String 中的操作一樣,返回一個指定基礎數據類型數組的直接指針,在這兩個操作之間不能做任何阻塞的操作。
實際操作如下:
// 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 只提供瞭如下函數來操作。
- GetObjectArrayElement / SetObjectArrayElement
和基本數據類型不同的是,不能一次得到數據中的所有對象元素或者一次複製多個對象元素到緩衝區。只能通過上面的函數來訪問或者修改指定位置的元素內容。
字符串和數組都是引用類型,因此也只能通過上面的方法來訪問。
例如在 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 對應的數據類型格式的轉換關係,現在要列舉的是 Java 與 JNI 對應簽名的轉換關係。
這裏的簽名指的是在 JNI 中去查找 Java 中對應的數據類型、對應的方法時,需要將 Java 中的簽名轉換成 JNI 所能識別的。
對於類的簽名轉換
對於 Java 中類或者接口的轉換,需要用到 Java 中類或者接口的全限定名,把 Java 中描述類或者接口的 .
換成 /
就好了,比如 String 類型對應的 JNI 描述爲:
java/lang/String // . 換成 /
對於數組類型,則是用 [ 來表示數組,然後跟一個字段的簽名轉換。
[I // 代表一維整型數組,I 表示整型
[[I // 代表二維整型數組
[Ljava/lang/String; // 代表一維字符串數組,
對於字段的簽名轉換
對應基礎類型字段的轉換:
對於引用類型的字段簽名轉換,是大寫字母 L 開頭,然後是類的簽名轉換,最後以 ; 結尾。
對於方法的簽名轉換
對於方法簽名描述的轉換,首先是將方法內所有參數轉換成對應的字段描述,並全部寫在小括號內,然後在小括號外再緊跟方法的返回值類型描述。
這裏要注意的是在 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);
}
}