NDK之Java与C/C++之间实现数组传递数据
Java与Android原生代码(C/C++)之间的数组调用
一、基本类型数组传递
1 Eclipse新建Android Application Project,文中取项目名 ArrayNdk
项目根目录建立jni文件夹:右击HelloNdk工程/Android Tools/Add Native Support…,.os文件默认命名为工程名HelloNdk,自带生成Android.mk和HelloNdk.cpp文件(有用)
2 配置原生代码编译环境,
见 http://www.cnblogs.com/hugeNumber/articles/7059308.html 配置Builder
3 src \ com.example.arrayndk 新建JniClient.java
package com.example.arrayndk; public class JniClient { public static native int[] useArray(int arr[], int length); }
1) 用cmd命令定位到JniClient.class 所在目录,输入“javac JniClient.java“后回车,截图如下:

2) cmd命令定位到ArrayNdk\bin\classes目录,输入”javah com.example.hellondk.JniClient“后回车。

3) 在 HelloNdk\bin\classes目录内会【自动】生成 com_example_arrayndk_JniClient.h将其拷贝到Eclipse的HelloNdk工程 \jni文件夹目录下。
com_example_arrayndk_JniClient.h 代码如下(提示:C \ C++ 代码由于未编译,有一堆错误,正常!正确配置Builder后,错误提示自动取消):
1 /* DO NOT EDIT THIS FILE - it is machine generated */ 2 #include <jni.h> 3 /* Header for class com_example_arrayndk_JniClient */ 4 5 #ifndef _Included_com_example_arrayndk_JniClient 6 #define _Included_com_example_arrayndk_JniClient 7 #ifdef __cplusplus 8 extern "C" { 9 #endif 10 /* 11 * Class: com_example_arrayndk_JniClient 12 * Method: useArray 13 * Signature: ([II)[I 14 */ 15 JNIEXPORT jintArray JNICALL Java_com_example_arrayndk_JniClient_useArray 16 (JNIEnv *, jclass, jintArray, jint); 17 18 #ifdef __cplusplus 19 } 20 #endif 21 #endif
4) 编写ArrayNdk.cpp ,书写规范同 C / C++
1 #include <jni.h> 2 #include <com_example_arrayndk_JniClient.h> 3 4 JNIEXPORT jintArray JNICALL Java_com_example_arrayndk_JniClient_useArray 5 (JNIEnv *env, jclass, jintArray arr, jint length){ 6 //整形数组操作示例,每个数组元素加上length值后,返回数组 7 int nLength = env->GetArrayLength(arr); 8 int *pArr = env->GetIntArrayElements(arr, 0); 9 10 for(int i=0; i < nLength; i++){ 11 *(pArr + i) += length; 12 } 13 env->ReleaseIntArrayElements(arr,pArr,0); 14 return arr; 15 }
5)ArrayNdk.cpp 处理数组的函数的几点说明:
a)jni定义的基本类型数组有:
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
文中以 jintArray 为例,其他类型的数组处理方式相似。
b)JNIEnv 作用 :
-- 调用 Java 函数 : JNIEnv 代表 Java 运行环境, 可以使用 JNIEnv 调用 Java 中的代码;
-- 操作 Java 对象 : Java 对象传入 JNI 层就是 Jobject 对象, 需要使用 JNIEnv 来操作这个 Java 对象;
c)C++代码接收Java数组方式一(都是套路)
int *pArr = GetIntArrayElements(Array arr , jboolean* isCopide);
这类函数可以把Java基本类型的数组转换到C / C++中的数组,C++中直接用指针接收。
int length = (*env)->GetArrayLength(env, arr); // 获得Java传递进来数组的长度
env->ReleaseIntArrayElements(arr, pArr, 0); // 处理完本地化的数组(C++代码中)后,通过ReleaseArrayElements来释放数组
6)activity_main.xml文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginLeft="94dp" android:layout_marginTop="45dp" android:text="我是个打酱油的" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/textView1" android:layout_centerHorizontal="true" android:layout_marginTop="22dp" android:text="" /> </RelativeLayout>
7) 返回MainActivity.java我们来书写Java代码
package com.example.arrayndk; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { TextView tv = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) findViewById(R.id.textView1); Button btn = (Button) findViewById(R.id.button1); btn.setOnClickListener(new OnClickListener(){ int[] arr = {3,2,1,5,4,9,6,8,7}; public void onClick(View v) { tv.setText("导入的Java数组在原生代码中被修改"); arr = JniClient.useArray(arr, 10); Toast.makeText(getApplicationContext(), "排序后:"+ Integer.toString(arr[0]) + " " + Integer.toString(arr[1]) + " " + Integer.toString(arr[2]) + " " + Integer.toString(arr[3]) + " " + Integer.toString(arr[4]) + " " + Integer.toString(arr[5]) + " " + Integer.toString(arr[6]) + " " + Integer.toString(arr[7]) + " " + Integer.toString(arr[8]) // + " " + Integer.toString(arr[9]) , Toast.LENGTH_LONG).show(); } }); } static { System.loadLibrary("ArrayNdk"); // 载入动态链接库lib…….so } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } }
至此,代码全部书写完毕。
8)编译 C / C++代码 和 查看运行结果
Eclipse中,Project / clean…

9)(重要提示:如果Console提示couldn't find ndk_build ,把C / C++ Build默认的ndk-build设置成ndk-build.cmd,原因未明 )

10)我们将ArrayNdk.cpp 的函数稍加调整,实现数组排序:
1 void swap(int *a, int *b){ 2 *a += *b; 3 *b = *a - *b; 4 *a = *a - *b; 5 } 6 7 JNIEXPORT jintArray JNICALL Java_com_example_ztestndk_JniClient_sort 8 (JNIEnv *env, jclass, jintArray arr, jint length){ 9 10 11 // 选择排序,成功 12 int nLength = env->GetArrayLength(arr); 13 int *pArr = env->GetIntArrayElements(arr, 0); 14 15 for(jint i=0; i<nLength; i++){ 16 for(jint j=i+1; j<nLength; j++){ 17 if(*(pArr + i) > *(pArr + j)){ 18 swap(pArr + i, pArr + j); 19 } 20 } 21 }//End outer for 22 env->ReleaseIntArrayElements(arr,pArr,0); 23 return arr; 24 }
我们发现,在Android原生代码中,可以调用本地函数swap(),书写规则同C / C++ 一样一样的。
11)在Android原生代码中,实现递归(以8皇后为例)
//1)将Java数组区复制到C数组中
(*env)->GetIntArrayRegion(env,javaArray,0,length,nativeArr);
//2)使用SetArrayRegion函数将C数组复制回Java数组中
(*env)->SetIntArrayRegion(env,javaArray,0,length,nativeArr);
3)原生代码可以用GetArrayElements函数获取指向数组元素的直接指针。
第三个参数和我们在上篇博文中提到的一样,它是一个可选参数,该可选参数的名称为isCopy,
让调用者确定返回的C字符串地址指向副本还是指向堆中的固定对象。
JNI要求原生代码用完这些指针必须立刻释放,否则会出现内存溢出问题,
原生代码可以使用JNI提供的ReleaseArrayElements函数来释放GetArrayElements函数返回的C数组。
该函数有四个参数,第四个是释放模式,有以下三种。
将内容复制回来并释放原生数组。JNI_COMMIT:将内容复制回来但是不释放原生数组,一般用于周期性的更新一个Java数组。
JNI_ABORT释放原生数组但是不将内容复制回来。

浙公网安备 33010602011771号