我们经常遇到在native代码中处理数组的需求,JNI中数组的使用可以说是基本功。下面的例子演示了如何在JNI中获取数组的长度,JNI获取Java层的数组并使用等。
一、涉及API
1.1 GetArrayLength
jsize GetArrayLength(JNIEnv *env, jarray array);
返回数组中元素的数量。
LINKAGE:
JNIEnv接口函数表中的索引171。
PARAMETERS:
env:JNI接口指针。
array:一个Java数组对象。
RETURNS:
返回数组的长度。
1.2 NewArray
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
用于构造新原始数组对象的一系列操作。下表描述了特定的基本数组构造函数。您应该将NewArray替换为此表中的一个实际原始数组构造函数例程名称,并将ArrayType替换为该例程的相应数组类型。
NewArray 例程 | 数组类型 |
---|---|
NewBooleanArray() | jbooleanArray |
NewByteArray() | jbyteArray |
NewCharArray() | jcharArray |
NewShortArray() | jshortArray |
NewIntArray() | jintArray |
NewLongArray() | jlongArray |
NewFloatArray() | jfloatArray |
NewDoubleArray() | jdoubleArray |
LINKAGE:
JNIEnv接口函数表中的索引。
NewArray例程 | Index |
---|---|
NewBooleanArray() | 175 |
NewByteArray() | 176 |
NewCharArray() | 177 |
NewShortArray() | 178 |
NewIntArray() | 179 |
NewLongArray() | 180 |
NewFloatArray() | 181 |
NewDoubleArray() | 182 |
PARAMETERS:
env:JNI接口指针。
length:数组长度。
RETURNS:
返回Java数组,如果无法构造数组,则返回NULL。
1.3 GetArrayElements
NativeType *Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
返回基本数组主体的一系列函数。结果有效,直到调用相应的ReleaseArrayElements()函数。由于返回的数组可能是Java数组的副本,因此在调用ReleaseArrayElements())之前,对返回数组所做的更改不一定会反映在原始数组中。
如果isCopy不为NULL,如果进行了复制,则*isCopy设置为JNI_TRUE; 如果没有复制,则设置为JNI_FALSE。
下表描述了特定的原始数组元素访问器。您应该进行以下替换:
- 将GetArrayElements替换为下表中的一个实际原始元素访问器例程名称。
- 将ArrayType替换为相应的数组类型。
- 将NativeType替换为该例程的相应本地类型。
无论如何在JVM中表示布尔数组,GetBooleanArrayElements()始终返回指向jbooleans的指针,每个字节表示一个元素(解包表示)。其他类型的所有数组都保证在内存中是连续的。
GetArrayElements例程 | 数组类型 | 本地类型 |
---|---|---|
GetBooleanArrayElements() | jbooleanArray | jboolean |
GetByteArrayElements() | jbyteArray | jbyte |
GetCharArrayElements() | jcharArray | jchar |
GetShortArrayElements() | jshortArray | jshort |
GetIntArrayElements() | jintArray | jint |
GetLongArrayElements() | jlongArray | jlong |
GetFloatArrayElements() | jfloatArray | jfloat |
GetDoubleArrayElements() | jdoubleArray | jdouble |
LINKAGE:
JNIEnv接口函数表中的索引。
GetArrayElements例程 | Index |
---|---|
GetBooleanArrayElements() | 183 |
GetByteArrayElements() | 184 |
GetCharArrayElements() | 185 |
GetShortArrayElements() | 186 |
GetIntArrayElements() | 187 |
GetLongArrayElements() | 188 |
GetFloatArrayElements() | 189 |
GetDoubleArrayElements() | 190 |
PARAMETERS:
env:JNI接口指针。
array:一个Java字符串对象。
isCopy:指向布尔值的指针。
RETURNS:
返回指向数组元素的指针,如果操作失败则返回NULL。
1.4 ReleaseArrayElements
void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode);
一系列函数,通知VM本地代码不再需要访问elems。elems参数是使用相应的GetArrayElements()函数从数组派生的指针。如有必要,此函数会将对elems所做的所有更改复制回原始数组。
mode参数提供有关如何释放数组缓冲区的信息。如果elems不是数组中元素的副本,则mode无效。否则,模式会产生以下影响,如下表所示:
模式 | 行为 |
---|---|
0 | 复制回内容并释放elems缓冲区 |
JNI_COMMIT | 复制回内容,但不释放elems缓冲区 |
JNI_ABORT | 释放缓冲区而不复制回可能的更改 |
在大多数情况下,程序员将“0”传递给mode参数,以确保固定和复制数组的一致行为。其他选项使程序员可以更好地控制内存管理,并且应该非常谨慎地使用。
下表描述了构成原始数组处理者系列的特定例程。您应该进行以下替换:
- 将ReleaseArrayElements替换为下表中的一个实际原始数组处理程序例程名称。
- 将ArrayType替换为相应的数组类型。
- 将NativeType替换为该例程的相应本地类型。
ReleaseArrayElements | 数组类型 | 本地类型 |
---|---|---|
ReleaseBooleanArrayElements() | jbooleanArray | jboolean |
ReleaseByteArrayElements() | jbyteArray | jbyte |
ReleaseCharArrayElements() | jcharArray | jchar |
ReleaseShortArrayElements() | jshortArray | jshort |
ReleaseIntArrayElements() | jintArray | jint |
ReleaseLongArrayElements() | jlongArray | jlong |
ReleaseFloatArrayElements() | jfloatArray | jfloat |
ReleaseDoubleArrayElements() | jdoubleArray | jdouble |
LINKAGE:
JNIEnv接口函数表中的索引。
ReleaseArrayElements | 索引 |
---|---|
ReleaseBooleanArrayElements() | 191 |
ReleaseByteArrayElements() | 192 |
ReleaseCharArrayElements() | 193 |
ReleaseShortArrayElements() | 194 |
ReleaseIntArrayElements() | 195 |
ReleaseLongArrayElements() | 196 |
ReleaseFloatArrayElements() | 197 |
ReleaseDoubleArrayElements() | 198 |
PARAMETERS:
env:JNI接口指针。
array:一个Java数组对象。
elems:指向数组元素的指针。
mode:释放模式。
1.5 GetArrayRegion
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, NativeType *buf);
一组函数,将原始数组的某个区域复制到缓冲区中。
下表描述了特定的原始数组元素访问器,你应该做以下替换:
- 将GetArrayRegion替换为下表中的一个实际原始元素访问器例程名称。
- 将ArrayType替换为相应的数组类型。
- 将NativeType替换为该例程的相应本地类型。
GetArrayRegion | 数组类型 | 本地类型 |
---|---|---|
GetBooleanArrayRegion() | jbooleanArray | jboolean |
GetByteArrayRegion() | jbyteArray | jbyte |
GetCharArrayRegion() | jcharArray | jchar |
GetShortArrayRegion() | jshortArray | jshort |
GetIntArrayRegion() | jintArray | jint |
GetLongArrayRegion() | jlongArray | jlong |
GetFloatArrayRegion() | jfloatArray | jloat |
GetDoubleArrayRegion() | jdoubleArray | jdouble |
LINKAGE:
JNIEnv接口函数表中的索引。
GetArrayRegion | 索引 |
---|---|
GetBooleanArrayRegion() | 199 |
GetByteArrayRegion() | 200 |
GetCharArrayRegion() | 201 |
GetShortArrayRegion() | 202 |
GetIntArrayRegion() | 203 |
GetLongArrayRegion() | 204 |
GetFloatArrayRegion() | 205 |
GetDoubleArrayRegion() | 206 |
PARAMETERS:
env:JNI接口指针。
array:一个Java数组。
start:起始索引。
len:要复制的元素数。
buf:目标缓冲区。
THROWS:
ArrayIndexOutOfBoundsException: 如果区域中的某个索引无效。
1.6 SetArrayRegion
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize len, const NativeType *buf);
一组函数,用于从缓冲区中复制回原始数组的某个区域。
下表描述了特定的原始数组元素访问器。您应该进行以下替换:
将SetArrayRegion替换为下表中的一个实际原始元素访问器例程名称。
将ArrayType替换为相应的数组类型。
将NativeType替换为该例程的相应本地类型。
SetArrayRegion | 数组类型 | 本地类型 |
---|---|---|
SetBooleanArrayRegion() | jbooleanArray | jboolean |
SetByteArrayRegion() | jbyteArray | jbyte |
SetCharArrayRegion() | jcharArray | jchar |
SetShortArrayRegion() | jshortArray | jshort |
SetIntArrayRegion() | jintArray | jint |
SetLongArrayRegion() | jlongArray | jlong |
SetFloatArrayRegion() | jfloatArray | jfloat |
SetDoubleArrayRegion() | jdoubleArray | jdouble |
LINKAGE:
JNIEnv接口函数表中的索引。
SetArrayRegion | 索引 |
---|---|
SetBooleanArrayRegion() | 207 |
SetByteArrayRegion() | 208 |
SetCharArrayRegion() | 209 |
SetShortArrayRegion() | 210 |
SetIntArrayRegion() | 211 |
SetLongArrayRegion() | 212 |
SetFloatArrayRegion() | 213 |
SetDoubleArrayRegion() | 214 |
PARAMETERS:
env:JNI接口指针。
array:一个Java数组。
start:起始索引。
len:要复制的元素数。
buf:源缓冲区。
THROWS:
ArrayIndexOutOfBoundsException: 如果区域中的某个索引无效。
注意:
从JDK/JRE 1.1开始,程序员可以使用Get/ReleaseArrayElements函数来获取指向原始数组元素的指针。如果VM支持固定,则返回指向原始数据的指针; 否则,制作副本。从JDK/JRE 1.3开始引入的新功能允许本地代码获取指向数组元素的直接指针,即使VM不支持固定。
二、实际使用
本实例通过在native代码中修改传入的数组,观察不同的JNI函数的作用。我们不难发现GetIntArrayElements和ReleaseIntArrayElements要成对使用,如果乱用,会出现意想不到的问题。同时在ReleaseIntArrayElements函数中试验了参数JNI_ABORT,发现native修改并未生效。最后验证了GetIntArrayRegion、SetIntArrayRegion和NewIntArray。这些函数都是我们在JNI操作数组中经常遇到的。
package ndk.example.com.ndkexample;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "Main";
static {
System.loadLibrary("native-lib");
}
private int[] mTestIntArr = new int[]{0, 1, 2, 3, 4, 5};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
tv.setText("数组使用...");
Log.d(TAG, "before nativeModifyIntArr test[0]=" + mTestIntArr[0]);
nativeModifyIntArr(mTestIntArr);
Log.d(TAG, "after nativeModifyIntArr test[0]=" + mTestIntArr[0]);
Log.d(TAG, "before nativeReadIntArr test[0]=" + mTestIntArr[0]);
nativeReadIntArr(mTestIntArr);
Log.d(TAG, "after nativeReadIntArr test[0]=" + mTestIntArr[0]);
Log.d(TAG, "before nativeModifyIntArr1 test[0]=" + mTestIntArr[0] + ",test[1]=" + mTestIntArr[1]);
nativeModifyIntArr1(mTestIntArr);
Log.d(TAG, "after nativeModifyIntArr1 test[0]=" + mTestIntArr[0] + ",test[1]=" + mTestIntArr[1]);
Log.d(TAG, "before nativeReadIntArr1 test[0]=" + mTestIntArr[0] + ",test[1]=" + mTestIntArr[1]);
nativeReadIntArr1(mTestIntArr);
Log.d(TAG, "after nativeReadIntArr1 test[0]=" + mTestIntArr[0] + ",test[1]=" + mTestIntArr[1]);
Log.d(TAG, "before nativeModifyIntArr2 test[0]=" + mTestIntArr[0] + ",test[1]=" + mTestIntArr[1]);
nativeModifyIntArr2(mTestIntArr);
Log.d(TAG, "after nativeModifyIntArr2 test[0]=" + mTestIntArr[0] + ",test[1]=" + mTestIntArr[1]);
mTestIntArr = nativeArr();
for (int ele : mTestIntArr) {
Log.d(TAG, "after nativeArr ele=" + ele);
}
}
public native void nativeModifyIntArr(int[] arr);
public native void nativeModifyIntArr1(int[] arr);
public native void nativeModifyIntArr2(int[] arr);
public native void nativeReadIntArr1(int[] arr);
public native void nativeReadIntArr(int[] arr);
public native int[] nativeArr();
}
Native代码:
#include <jni.h>
#include <string.h>
#include <android/log.h>
#define TAG "Native"
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__))
extern "C" {
JNIEXPORT void JNICALL
Java_ndk_example_com_ndkexample_MainActivity_nativeModifyIntArr(JNIEnv *env, jobject instance, jintArray arr_) {
jint *arr = env->GetIntArrayElements(arr_, NULL);
if (NULL != arr) {
arr[0] = 1000;
env->ReleaseIntArrayElements(arr_, arr, 0);
}
}
JNIEXPORT void JNICALL
Java_ndk_example_com_ndkexample_MainActivity_nativeReadIntArr(JNIEnv *env, jobject instance, jintArray arr_) {
//此代码会报错,无法设置* isCopy = JNI_TRUE
//jint *arr = env->GetIntArrayElements(arr_, reinterpret_cast<jboolean *>(JNI_TRUE));
jint *arr = env->GetIntArrayElements(arr_, NULL);
if (NULL != arr) {
arr[0] = -1000;
env->ReleaseIntArrayElements(arr_, arr, JNI_ABORT);
}
}
JNIEXPORT void JNICALL
Java_ndk_example_com_ndkexample_MainActivity_nativeModifyIntArr1(JNIEnv *env, jobject instance, jintArray arr_) {
jint *arr = env->GetIntArrayElements(arr_, NULL);
if (NULL != arr) {
arr[0] = -1000;
env->ReleaseIntArrayElements(arr_, arr, JNI_COMMIT);
arr[1] = -1000;
env->ReleaseIntArrayElements(arr_, arr, 0);
}
}
JNIEXPORT void JNICALL
Java_ndk_example_com_ndkexample_MainActivity_nativeReadIntArr1(JNIEnv *env, jobject instance, jintArray arr_) {
jint len = env->GetArrayLength(arr_);
LOGI("array len=%d", len);
jint *buf = new jint[len];
for (int i = 0; i < len; i++) {
buf[i] = 0;
}
env->GetIntArrayRegion(arr_, 0, len, buf);
for (int i = 0; i < len; i++) {
LOGI("buf[%d]=%d", i, buf[i]);
}
}
JNIEXPORT void JNICALL
Java_ndk_example_com_ndkexample_MainActivity_nativeModifyIntArr2(JNIEnv *env, jobject instance, jintArray arr_) {
jint len = env->GetArrayLength(arr_);
jint *buf = new jint[len];
for (int i = 0; i < len; i++) {
buf[i] = 0;
}
env->SetIntArrayRegion(arr_, 0, len, buf);
}
JNIEXPORT jintArray JNICALL
Java_ndk_example_com_ndkexample_MainActivity_nativeArr(JNIEnv *env, jobject instance) {
int len = 3;
jintArray intArr = env->NewIntArray(len);
jint *arr = env->GetIntArrayElements(intArr, NULL);
if (NULL != arr) {
for (int i = 0; i < len; i++) {
arr[i] = 99;
}
env->ReleaseIntArrayElements(intArr, arr, 0);
}
return intArr;
}
}
运行结果
10-16 11:14:53.319 28150-28150/? D/Main: before nativeModifyIntArr test[0]=0
after nativeModifyIntArr test[0]=1000
before nativeReadIntArr test[0]=1000
after nativeReadIntArr test[0]=1000
before nativeModifyIntArr1 test[0]=1000,test[1]=1
after nativeModifyIntArr1 test[0]=-1000,test[1]=-1000
before nativeReadIntArr1 test[0]=-1000,test[1]=-1000
10-16 11:14:53.320 28150-28150/? I/Native: array len=6
buf[0]=-1000
buf[1]=-1000
buf[2]=2
buf[3]=3
buf[4]=4
buf[5]=5
10-16 11:14:53.320 28150-28150/? D/Main: after nativeReadIntArr1 test[0]=-1000,test[1]=-1000
before nativeModifyIntArr2 test[0]=-1000,test[1]=-1000
after nativeModifyIntArr2 test[0]=0,test[1]=0
after nativeArr ele=99
after nativeArr ele=99
after nativeArr ele=99