Android中的JNI數(shù)組操作教程
前言
JNI 中有兩種數(shù)組操作,基礎(chǔ)數(shù)據(jù)類(lèi)型數(shù)組和對(duì)象數(shù)組,JNI 對(duì)待基礎(chǔ)數(shù)據(jù)類(lèi)型數(shù)組和對(duì)象數(shù)組是不一樣的。
基本數(shù)據(jù)類(lèi)型數(shù)組
對(duì)于基本數(shù)據(jù)類(lèi)型數(shù)組,JNI 都有和 Java 相對(duì)應(yīng)的結(jié)構(gòu),在使用起來(lái)和基本數(shù)據(jù)類(lèi)型的使用類(lèi)似。
在 Android JNI 基礎(chǔ)知識(shí)篇提到了 Java 數(shù)組類(lèi)型對(duì)應(yīng)的 JNI 數(shù)組類(lèi)型。比如,Java int 數(shù)組對(duì)應(yīng)了 jintArray,boolean 數(shù)組對(duì)應(yīng)了 jbooleanArray。
如同 String 的操作一樣,JNI 提供了對(duì)應(yīng)的轉(zhuǎn)換函數(shù):GetArrayElements、ReleaseArrayElements。
intArray = env->GetIntArrayElements(intArray_, NULL); env->ReleaseIntArrayElements(intArray_, intArray, 0);
另外,JNI 還提供了如下的函數(shù):
GetTypeArrayRegion / SetTypeArrayRegion
將數(shù)組內(nèi)容復(fù)制到 C 緩沖區(qū)內(nèi),或?qū)⒕彌_區(qū)內(nèi)的內(nèi)容復(fù)制到數(shù)組上。
GetArrayLength
得到數(shù)組中的元素個(gè)數(shù),也就是長(zhǎng)度。
NewTypeArray
返回一個(gè)指定數(shù)據(jù)類(lèi)型的數(shù)組,并且通過(guò) SetTypeArrayRegion 來(lái)給指定類(lèi)型數(shù)組賦值。
GetPrimitiveArrayCritical / ReleasePrimitiveArrayCritical
如同 String 中的操作一樣,返回一個(gè)指定基礎(chǔ)數(shù)據(jù)類(lèi)型數(shù)組的直接指針,在這兩個(gè)操作之間不能做任何阻塞的操作。
實(shí)際操作如下:
// Java 傳遞 數(shù)組 到 Native 進(jìn)行數(shù)組求和 private native int intArraySum(int[] intArray, int size);
對(duì)應(yīng)的 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 一樣,會(huì)申請(qǐng) native 內(nèi)存
intArray = env->GetIntArrayElements(intArray_, NULL);
if (intArray == NULL) {
return 0;
}
// 得到數(shù)組的長(zhǎng)度
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];
// 通過(guò) GetIntArrayRegion 方法來(lái)獲取數(shù)組內(nèi)容
env->GetIntArrayRegion(intArray_, 0, num, buf);
sum = 0;
for (int i = 0; i < num; ++i) {
sum += buf[i];
}
LOGD("sum is %d", sum);
// 使用完了別忘了釋放內(nèi)存
env->ReleaseIntArrayElements(intArray_, intArray, 0);
return sum;
}
假如需要從 JNI 中返回一個(gè)基礎(chǔ)數(shù)據(jù)類(lèi)型的數(shù)組,對(duì)應(yīng)的代碼如下:
// 從 Native 返回基本數(shù)據(jù)類(lèi)型數(shù)組 private native int[] getIntArray(int num);
對(duì)應(yīng)的 C++ 代碼如下:
/**
* 從 Native 返回 int 數(shù)組,主要調(diào)用 set<Type>ArrayRegion 來(lái)填充數(shù)據(jù),其他數(shù)據(jù)類(lèi)型類(lèi)似操作
*/
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 來(lái)賦值
env->SetIntArrayRegion(intArray, 0, num, buf);
return intArray;
}
以上例子,基本把相關(guān)的操作都使用上了,可以發(fā)現(xiàn)和 String 的操作大都是相似的。
對(duì)象數(shù)組
對(duì)于對(duì)象數(shù)組,也就是引用類(lèi)型數(shù)組,數(shù)組中的每個(gè)類(lèi)型都是引用類(lèi)型,JNI 只提供了如下函數(shù)來(lái)操作。
GetObjectArrayElement / SetObjectArrayElement
和基本數(shù)據(jù)類(lèi)型不同的是,不能一次得到數(shù)據(jù)中的所有對(duì)象元素或者一次復(fù)制多個(gè)對(duì)象元素到緩沖區(qū)。只能通過(guò)上面的函數(shù)來(lái)訪問(wèn)或者修改指定位置的元素內(nèi)容。
字符串和數(shù)組都是引用類(lèi)型,因此也只能通過(guò)上面的方法來(lái)訪問(wèn)。
例如在 JNI 中創(chuàng)建一個(gè)二維的整型數(shù)組并返回:
// 從 Native 返回二維整型數(shù)組,相當(dāng)于是一個(gè)一維整型數(shù)組,數(shù)組中的每一項(xiàng)內(nèi)容又是數(shù)組 private native int[][] getTwoDimensionalArray(int size);
二維數(shù)組具有特殊性在于,可以將它看成一維數(shù)組,其中數(shù)組的每項(xiàng)內(nèi)容又是一維數(shù)組。
具體 C++ 代碼如下:
/**
* 從 Native 返回一個(gè)二維的整型數(shù)組
*/
extern "C"
JNIEXPORT jobjectArray JNICALL
Java_com_glumes_cppso_jnioperations_ArrayTypeOps_getTwoDimensionalArray(JNIEnv *env,
jobject instance,
jint size) {
// 聲明一個(gè)對(duì)象數(shù)組
jobjectArray result;
// 找到對(duì)象數(shù)組中具體的對(duì)象類(lèi)型,[I 指的就是數(shù)組類(lèi)型
jclass intArrayCls = env->FindClass("[I");
if (intArrayCls == NULL) {
return NULL;
}
// 相當(dāng)于初始化一個(gè)對(duì)象數(shù)組,用指定的對(duì)象類(lèi)型
result = env->NewObjectArray(size, intArrayCls, NULL);
if (result == NULL) {
return NULL;
}
for (int i = 0; i < size; ++i) {
// 用來(lái)給整型數(shù)組填充數(shù)據(jù)的緩沖區(qū)
jint tmp[256];
// 聲明一個(gè)整型數(shù)組
jintArray iarr = env->NewIntArray(size);
if (iarr == NULL) {
return NULL;
}
for (int j = 0; j < size; ++j) {
tmp[j] = i + j;
}
// 給整型數(shù)組填充數(shù)據(jù)
env->SetIntArrayRegion(iarr, 0, size, tmp);
// 給對(duì)象數(shù)組指定位置填充數(shù)據(jù),這個(gè)數(shù)據(jù)就是一個(gè)一維整型數(shù)組
env->SetObjectArrayElement(result, i, iarr);
// 釋放局部引用
env->DeleteLocalRef(iarr);
}
return result;
}
首先需要使用 NewObjectArray 方法來(lái)創(chuàng)建對(duì)象數(shù)組。
然后使用 SetObjectArrayElement 函數(shù)填充數(shù)據(jù)時(shí),需要構(gòu)建好每個(gè)位置對(duì)應(yīng)的對(duì)象。這里就使用了 NewIntArray 來(lái)創(chuàng)造了一個(gè)對(duì)象,并給對(duì)象填充數(shù)據(jù)后,在賦值給對(duì)象數(shù)組。
通過(guò)一個(gè) for 循環(huán)就完成給對(duì)象數(shù)組賦值的操作。
在創(chuàng)建對(duì)象數(shù)組時(shí),有一個(gè)操作是找到對(duì)應(yīng)的對(duì)象類(lèi)型,通過(guò) findClass 方法。findClass 的參數(shù) [I 這里就涉及到 Java 與 JNI 對(duì)應(yīng)簽名的轉(zhuǎn)換。
Java 與 JNI 簽名的轉(zhuǎn)換
在前一篇文章中,用表格列出了 Java 與 JNI 對(duì)應(yīng)的數(shù)據(jù)類(lèi)型格式的轉(zhuǎn)換關(guān)系,現(xiàn)在要列舉的是 Java 與 JNI 對(duì)應(yīng)簽名的轉(zhuǎn)換關(guān)系。
這里的簽名指的是在 JNI 中去查找 Java 中對(duì)應(yīng)的數(shù)據(jù)類(lèi)型、對(duì)應(yīng)的方法時(shí),需要將 Java 中的簽名轉(zhuǎn)換成 JNI 所能識(shí)別的。
對(duì)于類(lèi)的簽名轉(zhuǎn)換
對(duì)于 Java 中類(lèi)或者接口的轉(zhuǎn)換,需要用到 Java 中類(lèi)或者接口的全限定名,把 Java 中描述類(lèi)或者接口的 . 換成 / 就好了,比如 String 類(lèi)型對(duì)應(yīng)的 JNI 描述為:
java/lang/String // . 換成 /
對(duì)于數(shù)組類(lèi)型,則是用 [ 來(lái)表示數(shù)組,然后跟一個(gè)字段的簽名轉(zhuǎn)換。
[I // 代表一維整型數(shù)組,I 表示整型 [[I // 代表二維整型數(shù)組 [Ljava/lang/String; // 代表一維字符串?dāng)?shù)組,
對(duì)于字段的簽名轉(zhuǎn)換
對(duì)應(yīng)基礎(chǔ)類(lèi)型字段的轉(zhuǎn)換:
| Java 類(lèi)型 | JNI 對(duì)應(yīng)的描述轉(zhuǎn) |
|---|---|
| boolean | Z |
| byte | B |
| char | C |
| short | S |
| int | I |
| long | J |
| float | F |
| double | D |
對(duì)于引用類(lèi)型的字段簽名轉(zhuǎn)換,是大寫(xiě)字母 L 開(kāi)頭,然后是類(lèi)的簽名轉(zhuǎn)換,最后以 ; 結(jié)尾。
| Java 類(lèi)型 | JNI 對(duì)應(yīng)的描述轉(zhuǎn)換 |
|---|---|
| String | Ljava/lang/String; |
| Class | Ljava/lang/Class; |
| Throwable | Ljava/lang/Throwable |
| int[] | "[I" |
| Object[] | "[Ljava/lang/Object;" |
對(duì)于方法的簽名轉(zhuǎn)換
對(duì)于方法簽名描述的轉(zhuǎn)換,首先是將方法內(nèi)所有參數(shù)轉(zhuǎn)換成對(duì)應(yīng)的字段描述,并全部寫(xiě)在小括號(hào)內(nèi),然后在小括號(hào)外再緊跟方法的返回值類(lèi)型描述。
| Java 類(lèi)型 | JNI 對(duì)應(yīng)的描述轉(zhuǎn)換 |
|---|---|
| String f(); | ()Ljava/lang/String; |
| long f(int i, Class c); | (ILjava/lang/Class;)J |
| String(byte[] bytes); | ([B)V |
這里要注意的是在 JNI 對(duì)應(yīng)的描述轉(zhuǎn)換中不要出現(xiàn)空格。
了解并掌握這些轉(zhuǎn)換后,就可以進(jìn)行更多的操作了,實(shí)現(xiàn) Java 與 C++ 的相互調(diào)用。
比如,有一個(gè)自定義的 Java 類(lèi),然后再 Native 中打印類(lèi)的對(duì)象數(shù)組的某一個(gè)字段值。
private native void printAnimalsName(Animal[] animal);
具體 C++ 代碼如下:
/**
* 打印對(duì)象數(shù)組中的信息
*/
extern "C"
JNIEXPORT void JNICALL
Java_com_glumes_cppso_jnioperations_ArrayTypeOps_printAnimalsName(JNIEnv *env, jobject instance,
jobjectArray animals) {
jobject animal;
// 數(shù)組長(zhǎng)度
int size = env->GetArrayLength(animals);
// 數(shù)組中對(duì)應(yīng)的類(lèi)
jclass cls = env->FindClass("com/glumes/cppso/model/Animal");
// 類(lèi)對(duì)應(yīng)的字段描述
jfieldID fid = env->GetFieldID(cls, "name", "Ljava/lang/String;");
// 類(lèi)的字段具體的值
jstring jstr;
// 類(lèi)字段具體值轉(zhuǎn)換成 C/C++ 字符串
const char *str;
for (int i = 0; i < size; ++i) {
// 得到數(shù)組中的每一個(gè)元素
animal = env->GetObjectArrayElement(animals, i);
// 每一個(gè)元素具體字段的值
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 項(xiàng)目,歡迎 Star。
https://github.com/glumes/AndroidDevWithCpp (本地下載)
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Android RecyclerView實(shí)現(xiàn)下拉列表功能
這篇文章主要介紹了Android RecyclerView實(shí)現(xiàn)下拉列表功能,下拉展開(kāi)更多選項(xiàng),具有一定的實(shí)用性,感興趣的小伙伴們可以參考一下2016-11-11
解決VSCode調(diào)試react-native android項(xiàng)目錯(cuò)誤問(wèn)題
這篇文章主要介紹了VSCode調(diào)試react-native android項(xiàng)目錯(cuò)誤解決辦法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
詳解Android通過(guò)修改配置文件設(shè)置wifi密碼
這篇文章主要介紹了詳解Android通過(guò)修改配置文件設(shè)置wifi密碼的相關(guān)資料,需要的朋友可以參考下2017-07-07
Android ListView滑動(dòng)改變標(biāo)題欄背景漸變效果
這篇文章主要為大家詳細(xì)介紹了Android ListView滑動(dòng)改變標(biāo)題欄背景漸變效果,透明轉(zhuǎn)變成不透明,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07
實(shí)例詳解Android 獲取短信會(huì)話列表
本文通過(guò)實(shí)例詳解android獲取短信會(huì)話列表的全部?jī)?nèi)容,涉及到android獲取短信列表的相關(guān)知識(shí),對(duì)android會(huì)話列表相關(guān)知識(shí)感興趣的朋友一起學(xué)習(xí)吧2015-12-12
android 跳轉(zhuǎn)到應(yīng)用通知設(shè)置界面的示例
本篇文章主要介紹了android 跳轉(zhuǎn)到應(yīng)用通知設(shè)置界面的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10
Dcloud的native.js直接撥打電話Android實(shí)例代碼
本文為大家分享了3種利用Dcloud的native.js直接撥打電話實(shí)例代碼,由于iOS系統(tǒng)的限制所以只有Android版實(shí)例2018-09-09
Android ProgressDialog進(jìn)度條使用詳解
這篇文章主要對(duì)Android開(kāi)發(fā)之ProgressDialog讀取文件進(jìn)度進(jìn)行解析,感興趣的朋友可以參考一下2016-02-02

