• Android NDK 开发(四)java传递数据到C【转】


      转载请注明出处:http://blog.csdn.net/allen315410/article/details/41845701

            前面几篇文章介绍了Android NDK开发的简单概念、常见错误及处理和从第一个Hello World开始实际做一个简单的JNI开发示例,相信看完之后,大家对NDK开发有了一个概念上的认识了,那么接下来我们需要再深入一下NDK的开发,我们知道NDK开发就是使用JNI这层“协议”在Java和C之间起个“桥梁”的作用,将Java和Native C之间联立起来,让Java和C直接的数据进行互调。谈到Java和C之间的数据调用,那么Java是怎样传递数据到C中的呢,C拿到数据处理完后又怎样将处理后的数据回传给Java的呢?先别急,接下来我们就看看Java怎么传递数据给C的。

    1,建立一个Android工程,在工程下建立一个DataProvider类,在这个类里定义3个native方法,如下:

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. package com.example.ndktransferdata;  
    2.   
    3. public class DataProvider {  
    4.     /** 
    5.      * 把两个java中的int传递给C语言,c语言处理完毕后,把相加的结果返回给java 
    6.      *  
    7.      * @param x 
    8.      * @param y 
    9.      * @return 
    10.      */  
    11.     public native int add(int x, int y);  
    12.     /** 
    13.      * 把java中的String传递给c语言,c语言获取后,在string后面添加一个hello字符串,返回给java 
    14.      *  
    15.      * @param s 
    16.      * @return 
    17.      */  
    18.     public native String sayHelloInC(String s);  
    19.     /** 
    20.      * 把java中的一个int数组传递给C语言,C语言接收这个数组,把int数组中的每一个元素+10,然后返回给Java 
    21.      *  
    22.      * @param iNum 
    23.      * @return 
    24.      */  
    25.     public native int[] intMethod(int[] iNum);  
    26. }  

    2,用Javah编译头文件

    做到这一步发现了一个问题,从描述上看应该是编码错误,这里错误的使用了GBK来编译java文件了,改成UTF-8就没问题,只要在javah命令后面添加 -encoding utf-8,为编译器提供编码环境就行,下面是改正后的结果:

    说明native代码的函数签名已经生成了,我们将这个生成的函数签名头文件剪切到jni目录下,在c代码中引用这个头文件就好了。

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. /* DO NOT EDIT THIS FILE - it is machine generated */  
    2. #include <jni.h>  
    3. /* Header for class com_example_ndktransferdata_DataProvider */  
    4.   
    5. #ifndef _Included_com_example_ndktransferdata_DataProvider  
    6. #define _Included_com_example_ndktransferdata_DataProvider  
    7. #ifdef __cplusplus  
    8. extern "C" {  
    9. #endif  
    10. /* 
    11.  * Class:     com_example_ndktransferdata_DataProvider 
    12.  * Method:    add 
    13.  * Signature: (II)I 
    14.  */  
    15. JNIEXPORT jint JNICALL Java_com_example_ndktransferdata_DataProvider_add  
    16.   (JNIEnv *, jobject, jint, jint);  
    17.   
    18. /* 
    19.  * Class:     com_example_ndktransferdata_DataProvider 
    20.  * Method:    sayHelloInC 
    21.  * Signature: (Ljava/lang/String;)Ljava/lang/String; 
    22.  */  
    23. JNIEXPORT jstring JNICALL Java_com_example_ndktransferdata_DataProvider_sayHelloInC  
    24.   (JNIEnv *, jobject, jstring);  
    25.   
    26. /* 
    27.  * Class:     com_example_ndktransferdata_DataProvider 
    28.  * Method:    intMethod 
    29.  * Signature: ([I)[I 
    30.  */  
    31. JNIEXPORT jintArray JNICALL Java_com_example_ndktransferdata_DataProvider_intMethod  
    32.   (JNIEnv *, jobject, jintArray);  
    33.   
    34. #ifdef __cplusplus  
    35. }  
    36. #endif  
    37. #endif  

    3,编写C语言代码

            之前在第二步的时候我们编译好了函数签名的头文件,所以这里我们就需要用过头文件中的方法签名了,一共包含3个这样的native函数,函数里实现是这样的:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. #include<stdio.h>  
    2. #include<jni.h>  
    3. #include<malloc.h>  
    4. #include<string.h>  
    5. #include"com_example_ndktransferdata_DataProvider.h"  
    6. #include<android/log.h>  
    7. #define LOG_TAG "System.out.c"  
    8. #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)  
    9. #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)  
    10.   
    11. /** 
    12.  * 返回值 char* 这个代表char数组的首地址 
    13.  *  Jstring2CStr 把java中的jstring的类型转化成一个c语言中的char 字符串 
    14.  */  
    15. char* Jstring2CStr(JNIEnv* env, jstring jstr) {  
    16.     char* rtn = NULL;  
    17.     jclass clsstring = (*env)->FindClass(env, "java/lang/String"); //String  
    18.     jstring strencode = (*env)->NewStringUTF(env, "GB2312"); // 得到一个java字符串 "GB2312"  
    19.     jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes",  
    20.             "(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");  
    21.     jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid,  
    22.             strencode); // String .getByte("GB2312");  
    23.     jsize alen = (*env)->GetArrayLength(env, barr); // byte数组的长度  
    24.     jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);  
    25.     if (alen > 0) {  
    26.         rtn = (char*) malloc(alen + 1); //""  
    27.         memcpy(rtn, ba, alen);  
    28.         rtn[alen] = 0;  
    29.     }  
    30.     (*env)->ReleaseByteArrayElements(env, barr, ba, 0); //  
    31.     return rtn;  
    32. }  
    33.   
    34. JNIEXPORT jint JNICALL Java_com_example_ndktransferdata_DataProvider_add(  
    35.         JNIEnv * env, jobject obj, jint x, jint y) {  
    36.     LOGD("x = %d", x);  
    37.     LOGD("y = %d", y);  
    38.     return x + y;  
    39. }  
    40.   
    41. JNIEXPORT jstring JNICALL Java_com_example_ndktransferdata_DataProvider_sayHelloInC(  
    42.         JNIEnv * env, jobject obj, jstring jstr) {  
    43.     char* cstr = Jstring2CStr(env, jstr);  
    44.     LOGD("cstr = %s", cstr);  
    45.     char arr[7] = { ' ', 'h', 'e', 'l', 'l', 'o', '' };  
    46.     strcat(cstr, arr);  
    47.     LOGD("new cstr = %s", cstr);  
    48.     return (*env)->NewStringUTF(env, cstr);  
    49. }  
    50.   
    51. JNIEXPORT jintArray JNICALL Java_com_example_ndktransferdata_DataProvider_intMethod(  
    52.         JNIEnv * env, jobject obj, jintArray jarr) {  
    53.     //获取传递进来数组的长度  
    54.     int len = (*env)->GetArrayLength(env, jarr);  
    55.     //获取传递进来数组的元素,即数组首地址  
    56.     jint* intArr = (*env)->GetIntArrayElements(env, jarr, 0);  
    57.     int i = 0;  
    58.     for (; i < len; i++) {  
    59.         //打印处理前的数组元素  
    60.         LOGD("intArr[%d] = %d", i, intArr[i]);  
    61.         //遍历数组元素+10  
    62.         *(intArr + i) += 10;  
    63.     }  
    64.     return jarr;  
    65. }  

    4,配置Android.mk文件

    [javascript] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. LOCAL_PATH := $(call my-dir)  
    2.   
    3. include $(CLEAR_VARS)  
    4.   
    5. LOCAL_MODULE    := Hello  
    6.   
    7. LOCAL_SRC_FILES := Hello.c  
    8.   
    9. LOCAL_LDLIBS += -llog  
    10.   
    11. include $(BUILD_SHARED_LIBRARY)  

    光配置Android.mk文件大致就可以了,但是还需要解决一个版本兼容问题,做法是在jni目录下新建Application.mk文件,加上

    [javascript] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. APP_PLATFORM := android-8  

    5,编译C语言代码

    6,Java代码中处理返回的数据

           Java中传递数据到C代码中,C代码处理完后返回给Java,这时候Java拿到数据后就可以做自己的一些业务操作了,首先我们编译完Native代码后,先Refresh一下工程,然后clean一下工程,编写如下的测试案例:

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public class MainActivity extends Activity implements OnClickListener {  
    2.   
    3.     // 加载本地库文件  
    4.     static {  
    5.         System.loadLibrary("Hello");  
    6.     }  
    7.   
    8.     private Button btn1, btn2, btn3;  
    9.     private DataProvider provider;  
    10.     @Override  
    11.     protected void onCreate(Bundle savedInstanceState) {  
    12.         super.onCreate(savedInstanceState);  
    13.         setContentView(R.layout.activity_main);  
    14.         btn1 = (Button) findViewById(R.id.btn1);  
    15.         btn2 = (Button) findViewById(R.id.btn2);  
    16.         btn3 = (Button) findViewById(R.id.btn3);  
    17.         btn1.setOnClickListener(this);  
    18.         btn2.setOnClickListener(this);  
    19.         btn3.setOnClickListener(this);  
    20.         provider = new DataProvider();  
    21.     }  
    22.     @Override  
    23.     public void onClick(View v) {  
    24.         switch (v.getId()) {  
    25.             case R.id.btn1 : // 传递2个int给C代码  
    26.                 int result = provider.add(3, 5);  
    27.                 Toast.makeText(this, "相加的结果:" + result, 0).show();  
    28.                 break;  
    29.             case R.id.btn2 : // 传递string给C代码  
    30.                 String str = provider.sayHelloInC("zhang san");  
    31.                 Toast.makeText(this, str, 0).show();  
    32.                 break;  
    33.             case R.id.btn3 : // 传递int数组给C代码  
    34.                 int[] arr = {1, 2, 3, 4, 5};  
    35.                 provider.intMethod(arr);  
    36.                 for (int i = 0; i < arr.length; i++) {  
    37.                     System.out.println("arr[" + i + "] = " + arr[i]);  
    38.                 }  
    39.                 break;  
    40.             default :  
    41.                 break;  
    42.         }  
    43.     }  
    44.   
    45. }  

            运行一下工程,注意:这里只能开启arm模拟器,如果是x86模拟器会安装apk时候报错,因为这段native代码只编写了arm支持的版本,没有支持x86。如果在运行测试的时候出现一些错误的话,请参考上篇文章中提示慢慢解决。Android NDK开发——常见错误集锦以及LOG使用

    测试1:Java传递2个int给C


    Logcat输出:


    测试2:Java传递string给C


    Logcat输出:


    测试3:Java传递int数组给C

    Logcat输出:


    总结:

            上述这个简单的示例可以说明ndk开发中,Java是怎样将数据传递给C代码的了,程序中只是简单的介绍了3种数据类型int,string和int[],这是远远不够的,因为Java支持的数据类型比较多,这时候怎么办?好,有了上面的例子,我们可以举一反三了,在之前的博客中我也强调过ndk解压包下的jni.h这个文件的重要性,这个文件不仅仅定义了Java数据类型在C语言中的表示,看一下源码,就发现一种一一映射的关系:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. ......  
    2. typedef uint8_t         jboolean;       /* unsigned 8 bits */  
    3. typedef int8_t          jbyte;          /* signed 8 bits */  
    4. typedef uint16_t        jchar;          /* unsigned 16 bits */  
    5. typedef int16_t         jshort;         /* signed 16 bits */  
    6. typedef int32_t         jint;           /* signed 32 bits */  
    7. typedef int64_t         jlong;          /* signed 64 bits */  
    8. typedef float           jfloat;         /* 32-bit IEEE 754 */  
    9. typedef double          jdouble;        /* 64-bit IEEE 754 */  
    10. #else  
    11. typedef unsigned char   jboolean;       /* unsigned 8 bits */  
    12. typedef signed char     jbyte;          /* signed 8 bits */  
    13. typedef unsigned short  jchar;          /* unsigned 16 bits */  
    14. typedef short           jshort;         /* signed 16 bits */  
    15. typedef int             jint;           /* signed 32 bits */  
    16. typedef long long       jlong;          /* signed 64 bits */  
    17. typedef float           jfloat;         /* 32-bit IEEE 754 */  
    18. typedef double          jdouble;        /* 64-bit IEEE 754 */  
    19. ......  

    另外还有一个非常重要的结构体JNINativeInterface,这里面定义了很多C函数,只要看懂大致意思就可以试着去调用这些函数,这些函数在native开发中显得特别重要:

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. ......    
    2.   jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize);  
    3.     jbyteArray    (*NewByteArray)(JNIEnv*, jsize);  
    4.     jcharArray    (*NewCharArray)(JNIEnv*, jsize);  
    5.     jshortArray   (*NewShortArray)(JNIEnv*, jsize);  
    6.     jintArray     (*NewIntArray)(JNIEnv*, jsize);  
    7.     jlongArray    (*NewLongArray)(JNIEnv*, jsize);  
    8.     jfloatArray   (*NewFloatArray)(JNIEnv*, jsize);  
    9.     jdoubleArray  (*NewDoubleArray)(JNIEnv*, jsize);  
    10.   
    11.     jboolean*   (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*);  
    12.     jbyte*      (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*);  
    13.     jchar*      (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*);  
    14.     jshort*     (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*);  
    15.     jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);  
    16.     jlong*      (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*);  
    17.     jfloat*     (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*);  
    18.     jdouble*    (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*);  
    19. ......  

    源码比较长,有兴趣的朋友自己翻看一下,这里只贴部分。

    源码请在这里下载

  • 相关阅读:
    面向对象 课堂记录
    面向对象10个案例 (来源网络)
    面向对象举例
    面向对象
    36选7
    Android ImageView的scaleType属性与adjustViewBounds属性
    安卓计算器 简单代码
    安卓单选、复选按钮简单练习
    安卓发送邮箱界面 线形、表格、相对布局方式
    UI和View 三种控制方式
  • 原文地址:https://www.cnblogs.com/zzb-Dream-90Time/p/6070491.html
Copyright © 2020-2023  润新知