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文件(有用)

配置原生代码编译环境,

  见 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释放原生数组但是不将内容复制回来。

 

 

二、引用类型数组传递

posted @ 2017-06-22 19:05  倪明  阅读(1117)  评论(0)    收藏  举报