转载请注明出处: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方法,如下:
package com.example.ndktransferdata;public class DataProvider { /** * 把两个java中的int传递给C语言,c语言处理完毕后,把相加的结果返回给java * * @param x * @param y * @return */ public native int add(int x, int y); /** * 把java中的String传递给c语言,c语言获取后,在string后面添加一个hello字符串,返回给java * * @param s * @return */ public native String sayHelloInC(String s); /** * 把java中的一个int数组传递给C语言,C语言接收这个数组,把int数组中的每一个元素+10,然后返回给Java * * @param iNum * @return */ public native int[] intMethod(int[] iNum);}
2,用Javah编译头文件
做到这一步发现了一个问题,从描述上看应该是编码错误,这里错误的使用了GBK来编译java文件了,改成UTF-8就没问题,只要在javah命令后面添加 -encoding utf-8,为编译器提供编码环境就行,下面是改正后的结果:
说明native代码的函数签名已经生成了,我们将这个生成的函数签名头文件剪切到jni目录下,在c代码中引用这个头文件就好了。
/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class com_example_ndktransferdata_DataProvider */#ifndef _Included_com_example_ndktransferdata_DataProvider#define _Included_com_example_ndktransferdata_DataProvider#ifdef __cplusplusextern "C" {#endif/* * Class: com_example_ndktransferdata_DataProvider * Method: add * Signature: (II)I */JNIEXPORT jint JNICALL Java_com_example_ndktransferdata_DataProvider_add (JNIEnv *, jobject, jint, jint);/* * Class: com_example_ndktransferdata_DataProvider * Method: sayHelloInC * Signature: (Ljava/lang/String;)Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_example_ndktransferdata_DataProvider_sayHelloInC (JNIEnv *, jobject, jstring);/* * Class: com_example_ndktransferdata_DataProvider * Method: intMethod * Signature: ([I)[I */JNIEXPORT jintArray JNICALL Java_com_example_ndktransferdata_DataProvider_intMethod (JNIEnv *, jobject, jintArray);#ifdef __cplusplus}#endif#endif
3,编写C语言代码
之前在第二步的时候我们编译好了函数签名的头文件,所以这里我们就需要用过头文件中的方法签名了,一共包含3个这样的native函数,函数里实现是这样的:
#include<stdio.h>#include<jni.h>#include<malloc.h>#include<string.h>#include"com_example_ndktransferdata_DataProvider.h"#include<android/log.h>#define LOG_TAG "System.out.c"#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)/** * 返回值 char* 这个代表char数组的首地址 * Jstring2CStr 把java中的jstring的类型转化成一个c语言中的char 字符串 */char* Jstring2CStr(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = (*env)->FindClass(env, "java/lang/String"); //String jstring strencode = (*env)->NewStringUTF(env, "GB2312"); // 得到一个java字符串 "GB2312" jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312"); jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312"); jsize alen = (*env)->GetArrayLength(env, barr); // byte数组的长度 jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE); if (alen > 0) { rtn = (char*) malloc(alen + 1); //"\0" memcpy(rtn, ba, alen); rtn[alen] = 0; } (*env)->ReleaseByteArrayElements(env, barr, ba, 0); // return rtn;}JNIEXPORT jint JNICALL Java_com_example_ndktransferdata_DataProvider_add( JNIEnv * env, jobject obj, jint x, jint y) { LOGD("x = %d", x); LOGD("y = %d", y); return x + y;}JNIEXPORT jstring JNICALL Java_com_example_ndktransferdata_DataProvider_sayHelloInC( JNIEnv * env, jobject obj, jstring jstr) { char* cstr = Jstring2CStr(env, jstr); LOGD("cstr = %s", cstr); char arr[7] = { ' ', 'h', 'e', 'l', 'l', 'o', '\0' }; strcat(cstr, arr); LOGD("new cstr = %s", cstr); return (*env)->NewStringUTF(env, cstr);}JNIEXPORT jintArray JNICALL Java_com_example_ndktransferdata_DataProvider_intMethod( JNIEnv * env, jobject obj, jintArray jarr) { //获取传递进来数组的长度 int len = (*env)->GetArrayLength(env, jarr); //获取传递进来数组的元素,即数组首地址 jint* intArr = (*env)->GetIntArrayElements(env, jarr, 0); int i = 0; for (; i < len; i++) { //打印处理前的数组元素 LOGD("intArr[%d] = %d", i, intArr[i]); //遍历数组元素+10 *(intArr + i) += 10; } return jarr;}
4,配置Android.mk文件
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := Hello LOCAL_SRC_FILES := Hello.c LOCAL_LDLIBS += -llog include $(BUILD_SHARED_LIBRARY)光配置Android.mk文件大致就可以了,但是还需要解决一个版本兼容问题,做法是在jni目录下新建Application.mk文件,加上
APP_PLATFORM := android-8
5,编译C语言代码
6,Java代码中处理返回的数据
Java中传递数据到C代码中,C代码处理完后返回给Java,这时候Java拿到数据后就可以做自己的一些业务操作了,首先我们编译完Native代码后,先Refresh一下工程,然后clean一下工程,编写如下的测试案例:
public class MainActivity extends Activity implements OnClickListener { // 加载本地库文件 static { System.loadLibrary("Hello"); } private Button btn1, btn2, btn3; private DataProvider provider; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btn1 = (Button) findViewById(R.id.btn1); btn2 = (Button) findViewById(R.id.btn2); btn3 = (Button) findViewById(R.id.btn3); btn1.setOnClickListener(this); btn2.setOnClickListener(this); btn3.setOnClickListener(this); provider = new DataProvider(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn1 : // 传递2个int给C代码 int result = provider.add(3, 5); Toast.makeText(this, "相加的结果:" + result, 0).show(); break; case R.id.btn2 : // 传递string给C代码 String str = provider.sayHelloInC("zhang san"); Toast.makeText(this, str, 0).show(); break; case R.id.btn3 : // 传递int数组给C代码 int[] arr = {1, 2, 3, 4, 5}; provider.intMethod(arr); for (int i = 0; i < arr.length; i++) { System.out.println("arr[" + i + "] = " + arr[i]); } break; default : break; } }}运行一下工程,注意:这里只能开启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语言中的表示,看一下源码,就发现一种一一映射的关系:
......typedef uint8_t jboolean; /* unsigned 8 bits */typedef int8_t jbyte; /* signed 8 bits */typedef uint16_t jchar; /* unsigned 16 bits */typedef int16_t jshort; /* signed 16 bits */typedef int32_t jint; /* signed 32 bits */typedef int64_t jlong; /* signed 64 bits */typedef float jfloat; /* 32-bit IEEE 754 */typedef double jdouble; /* 64-bit IEEE 754 */#elsetypedef unsigned char jboolean; /* unsigned 8 bits */typedef signed char jbyte; /* signed 8 bits */typedef unsigned short jchar; /* unsigned 16 bits */typedef short jshort; /* signed 16 bits */typedef int jint; /* signed 32 bits */typedef long long jlong; /* signed 64 bits */typedef float jfloat; /* 32-bit IEEE 754 */typedef double jdouble; /* 64-bit IEEE 754 */......另外还有一个非常重要的结构体JNINativeInterface,这里面定义了很多C函数,只要看懂大致意思就可以试着去调用这些函数,这些函数在native开发中显得特别重要:
...... jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize); jbyteArray (*NewByteArray)(JNIEnv*, jsize); jcharArray (*NewCharArray)(JNIEnv*, jsize); jshortArray (*NewShortArray)(JNIEnv*, jsize); jintArray (*NewIntArray)(JNIEnv*, jsize); jlongArray (*NewLongArray)(JNIEnv*, jsize); jfloatArray (*NewFloatArray)(JNIEnv*, jsize); jdoubleArray (*NewDoubleArray)(JNIEnv*, jsize); jboolean* (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*); jbyte* (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*); jchar* (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*); jshort* (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*); jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*); jlong* (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*); jfloat* (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*); jdouble* (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*);......源码比较长,有兴趣的朋友自己翻看一下,这里只贴部分。
源码请在这里下载