從JNI函數動態註冊進階一文可知,JNI把Java類型分爲兩類來處理,一類是基本類型,另外一類是引用類型,並且引用類型都是用jobject
類型表示的,以下圖所示java
從這個圖中能夠看出,引用類型中有一種比較特殊的類型--數組,在JNI層中有好多個類型相對應,例如int[]
對應於jintArray
,String[]
對應於jobjectArray
。c++
從上圖中其實還能夠看到的一點是,JNI中對數組的處理也分爲基本類型和引用類型。因爲篇幅關係,本文只講述JNI中基本類型數組的操做,下一篇文章講述引用類型數組的操做。數組
如今假設有一個Java類的native
方法緩存
public class ArrayTest {
static {
System.loadLibrary("array_jni");
}
public native void handleArray(int[] a);
public static void main(String[] args) {
int[] a = {1, 2, 3};
new ArrayTest().handleArray(a);
}
}
複製代碼
而且再次假設在JNI層的實現函數以下bash
static void com_uni_ndkdemo_ArrayTest_handleArray(JNIEnv *env, jobject *thiz, jintArray array) {
}
複製代碼
若是不瞭解JNI函數是如何註冊的,能夠參考不使用IDE作一次JNI開發, JNI函數動態註冊 和 JNI函數動態註冊進階。函數
在com_uni_ndkdemo_ArrayTest_handleArray
中將會實現對基本類型數組的操做。post
Get<PrimitiveType>ArrayElements
函數返回一個基本類型的數組,其中<PrimitiveType>
表示基本類型,例爲GetIntArrayElements
是獲取int
類型數組。ui
NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env,
ArrayType array, jboolean *isCopy);
複製代碼
首先解釋下參數spa
ArrayType array
: Java的數組對象jboolean *iscopy
: 若是isCopy
不爲NULL
,當返回指針指向的是原數組的拷貝時,*isCopy
值爲JNI_TRUE
,若是返回的指針指向原數組,那麼*isCopy
的值爲JNI_FALSE
。這個通用函數有兩個類型須要替換,一個是返回類型NativeType
表示一個JNI類型,一個是ArrayType
表示JNI的數組類型。怎麼替換呢?舉個例子吧,若是ArrayType array
是Java的int[]
類型對象,那麼ArrayType
就是Javaint[]
類型在JNI中的對應類型,也就是jintArray
。函數的返回值是一個指針,很顯然int[]
在JNI中確定是一個jint
的指針,所以NativeType
就是jint
。因此,對於Java的int[]
對象,函數原型以下線程
jint * GetIntArrayElements(JNIEnv *env, jintArray array, jboolean *isCopy);
複製代碼
GetIntArrayElements
返回一個jint
類型的指針(指針可能爲NULL
),可能指向Java的原始數組,也可能指向一個拷貝的數組,這個取決於虛擬機,可是咱們能夠經過最後一個參數的值來判斷是否有拷貝發生。
若是函數返回的指針指向原數組,那麼全部的修改都會在原數組上進行,這與Java的方式是一致的。而若是函數返回的指針指向原始數組的拷貝,那麼全部的修改都僅僅是在拷貝上進行的,原始數組不受影響。
然而是否發生拷貝並非咱們能決定的,咱們能決定的是,當拷貝發生了,咱們可以確保全部在拷貝上的修改都能寫回到原始數組上,這就是Release<PrimitiveType>ArrayElements
函數的做用。
void Release<PrimitiveType>ArrayElements(JNIEnv *env,
ArrayType array, NativeType *elems, jint mode);
複製代碼
參數
ArrayType array
: Java數組對象NativeType * elems
: 指向JNI類型的指針,是經過Get<PrimitiveType>ArrayElements
函數獲取的jint mode
的值有如下幾種
NativeType *elems
緩存。JNI_COMMIT
: 把拷貝數組的修改寫回原數組,可是並不釋放NativeType *elems
緩存。JNI_ABORT
: 釋放NativeType *elems
緩存,但並不把拷貝數組的修改寫回原數組。關於
mode
參數,若是Get<PrimitiveType>ArrayElements
函數不發生拷貝,mode
參數就沒有任何影響。
Release<PrimitiveType>ArrayElements
函數實際上是通知虛擬機NativeType *elems
指向的數組不會再被訪問,虛擬機會根據參數mode
的值決定是否釋放本地數組,以及是否把修改寫回到原數組。
Release<PrimitiveType>ArrayElements
是一個通用的寫法,舉個具體的例子吧,當處理的是Java的int[]
對象時,函數原型以下
void ReleaseIntArrayElements(JNIEnv *env,
jintArray array, jint *elems, jint mode);
複製代碼
讀者可根據前面所講的類型替換原理來理解這裏的類型替換,後面遇到這樣的類型替換也只會給出某個具體的例子,並不會講解如何替換。
static void com_uni_ndkdemo_ArrayTest_handleArray(JNIEnv *env, jobject *thiz, jintArray array)
{
// 1. 獲取數組的長度
jsize length = env->GetArrayLength(intArr);
// 2. 獲取本地數組
jint *native_int_array = env->GetIntArrayElements(intArr, NULL);
// 3. 操做本地數組
for (int i = 0; i < length; i++)
{
native_int_array[i] += 100;
}
// 4. 釋放本地數組
env->ReleaseIntArrayElements(intArr, native_int_array, 0);
}
複製代碼
調用GetIntArrayElements
函數的時候,第二個參數傳入了NULL
,所以後面沒法判斷是否生成了數組的拷貝,然而在這裏並不關心這個問題,由於在調用ReleaseIntArrayElements
函數的時候,第三個參數的值爲0
,修改必定會應用到原始數組上。
調用GetIntArrayElements
函數返回的本地數組,在函數返回前也沒有進行手動釋放,這是由於調用ReleaseIntArrayElements
傳入的第三個參數爲0
,本地數組會被自動釋放。
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,
jsize start, jsize len, NativeType *buf);
複製代碼
參數
ArrayType array
: Java數組jsize start
: 起始數組的索引jsize len
: 須要拷貝的長度NativeType * buf
: 本地緩衝區Get<PrimitiveType>ArrayRegion
函數的效果是拷貝數組ArrayType array
的一部分到緩存NativeType *buf
中,這一部分的起始位置是start
,長度爲len
。
若是處理的是int[]
類型,對應函數原型以下
void GetIntArrayRegion(JNIEnv *env, jintArray array, jsize start,
jsize len, jint *buf);
複製代碼
咱們應該注意到了,Get<PrimitiveType>ArrayRegion
函數的效果是拷貝,那麼天然而然有一個問題擺在眼前,那就是若是你關心拷貝數組上的修改須要寫會到原始數組上,能夠調用Set<PrimitiveType>ArrayRegion
,而若是你並不關心這個問題,可能什麼也不用作(若是本地緩存是動態分配的,須要手動釋放)。
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array,
jsize start, jsize len, const NativeType *buf);
複製代碼
參數
ArrayType array
: Java數組對象jsize start
: 拷貝的起始索引jsize len
: 拷貝的長度NativeType *buf
: 拷貝的數據源Set<PrimitiveType>ArrayRegion
把緩存buf
寫回到原數組array
中,起始位置爲start
,長度爲len
。
參數
start
和len
都是對於原是數組array
來講的。
static void com_uni_ndkdemo_ArrayTest_handleArray(JNIEnv *env, jobject *thiz, jintArray array)
{
// 獲取數組長度
const jsize length = env->GetArrayLength(array);
// 在棧上建立緩衝區
jint buff[length - 1];
// 獲取原始數組最後length -1個數組的拷貝
env->GetIntArrayRegion(array, 1, length - 1, buff);
// 修改拷貝數組
for (int i = 0; i < length - 1; ++i)
{
buff[i] += 100;
}
// 把拷貝數組的修改寫會到原始數組中
env->SetIntArrayRegion(array, 1, length - 1, buff);
}
複製代碼
在這裏例子中,緩衝區jint buff[length - 1]
是在棧上建立,所以在函數返回後會自動釋放內存,而若是在堆上建立緩衝區,例如使用malloc
函數,那麼在函數返回前須要手動釋放內存,不然會形成內存泄露(C語言基本認知)。
當使用SetIntArrayRegion
把修改寫回到原是數組後,在Java層是能夠看到數組的改變的,你們能夠本身打印看看,我這裏就不演示了。
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);
複製代碼
這兩個函數與Get/Release<primitivetype>ArrayElements
函數的使用方式是同樣的,虛擬機可能返回一個指向原數組的指針,也可能返回一個指向拷貝數組的指針。
可是這兩個函數在使用的時候有很是大的限制。這兩個函數是要成對出現的,並且在調用這兩個函數之間,不能調用可能致使當前線程阻塞而且等待其它線程的JNI函數或系統調用。可是這種限制也帶來一些好處,它使本地代碼更容易獲取一個非拷貝版本的數組的指針,虛擬機也可能暫時禁用垃圾回收功能。禁用垃圾回收功能可讓本地代碼執行更快,畢竟不會暫停本地線程,可是呢,短暫禁用垃圾回收功能對系統垃圾回收功能形成影響,能夠說利與弊相輔相成。
在實際中使用這兩個函數,須要斟酌斟酌,權衡利弊。
static jlong com_uni_ndkdemo_ArrayTest_handleArray(JNIEnv *env, jobject thiz, jintArray array) {
// 1. 獲取數組的大小
jsize length = env->GetArrayLength(array);
// 2. 獲取數組的指針
jint * a = (jint *) env->GetPrimitiveArrayCritical(array, NULL);
if (a == NULL) {
return 0;
}
// 3. 操做數組(不要進行耗時的JNI函數調用或者系統調用)
for (int i = 0; i < length; i++) {
a[i] += 100;
}
// 4. 釋放本地數組,修改寫回原始數組
env->ReleasePrimitiveArrayCritical(array, a, 0);
}
複製代碼
使用Get<Primitive>ArrayCritical
和Release<Primitive>ArrayCritical
這兩個函數的之間,注意不要調用耗時的JNI函數或者系統調用,由於畢竟虛擬機有可能暫時禁用垃圾回收功能,若是進行耗時操做,就可能影響垃圾回收功能。
前面的例子都是在JNI層處理Java層傳過來的基本類型數組,也能夠在JNI層建立基本類型數組並返回給Java層,使用的就是New<PrimitiveType>Array
函數
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
複製代碼
例如,若是要返回一個int[]
對象給Java層,那麼New<PrimitiveType>Array
函數原型以下
jintArray NewIntArray(JNIEnv *env, jsize length);
複製代碼
那麼,在建立完Java基本類型數組後,如何給每一個元素賦值呢?固然就是用前面講過的函數。
假設Java類有一個返回int[]
的方法
public class ArrayTest {
static {
System.loadLibrary("array_jni");
}
public native int[] getNativeArray();
}
複製代碼
通過動態註冊後,在JNI層的實現函數以下
static jintArray com_uni_ndkdemo_ArrayTest_getNativeArray(JNIEnv * env, jobject thiz) {
// 1. 建立一個Java的int[]
jintArray array = env->NewIntArray(2);
// 2. 獲取數組指針
jint *c_array = env->GetIntArrayElements(array, NULL);
// 3. 操做數組元素
c_array[0] = 110;
c_array[1] = 120;
// 4. 把修改寫回原數組而且釋放本地數組
env->ReleaseIntArrayElements(array, c_array, 0);
return array;
}
複製代碼
最關鍵的一步就是第一步,建立一個Java層的基本類型數組,剩下的事情就是在JNI層處理這個數組,很顯然這個事情前面都講過。
在操做基本類型數組方面呢,有三種方式,Get/Release<PrimitiveType>ArrayElements
,Get/Set<PrimitiveType>ArrayRegion
以及 Get/Release<Primitive>ArrayCritical
。它們各有千秋,這裏就總結下。
若是操做整個數組,而且要把修改引用到原數組,那麼Get/Release<PrimitiveType>ArrayElements
是首選,若是更須要執行速度,那麼能夠選擇Get/Release<Primitive>ArrayCritical
。
若是不須要把修改應用到原數組上,那麼Get/Set<PrimitiveType>ArrayRegion
是首選,畢竟Get/Release<PrimitiveType>ArrayElements
和Get/Release<Primitive>ArrayCritical
對因而否生成數組的拷貝具備不肯定性,須要加入代碼進行判斷。
後面的文章就會講解JNI的關於引用類型數組的操做,這個稍微就有點複雜,若是引用類型是字符串類型,那就又是另一種說法了。不過在此以前,但願你們好好理解我前面寫的文章,由於這是一環套一環的,基本功丟了,就無從談起上層建築了。