在上一篇的Android NDK初体验(上)中讲的是NDK最基础的用法,从工程的建立到最后成功调用native方法的过程。这是最基本的NDK开发,不熟悉NDK具体开发流程的可以先去看看我的上一篇博客。然而仅仅使java层能够调用native层的代码是远远不够的,下面说一些常用的基础扩展知识。
一、在native代码中添加log
我们知道,在Android的开发过程中提供的日志工具logcat是非常有用的,能提供五种控制级别帮助我们有效地过滤信息,排查错误。那么,在NDK开发中我们能否引用类似Android系统的Log工具呢?幸运的是,NDK给我们提供了这样的渠道。若要在我们的C代码中引入log,只需要在.c文件中引入如下头文件就能像Android一样使用log啦:
#include
我们可以按住ctrl打开这里引用的log.h文件,可以看到在这个头文件里有几个函数,其中__android_log_print()函数就是我们要用到的log打印函数。这个函数有几个参数,其中prio就是我们Andorid里面的Log的级别,可以看到上面定义的android_LogPriority里有我们Android对应的五种控制类型。
另外需要注意,引入log需要添加一些配置语句:
- 在eclipse下使用log需要在工程的jni目录下的Android.mk文件中添加:
LOCAL_LDLIBS += -llog
- 在Android Studio下使用log需在工程的app模块下的build.gradle文件中的ndk模块引入:ldLibs "log",否则会出现Error:undefined reference to `__android_log_print'
ndk {
moduleName "hello" //定义NDKlibrary的名字
ldLibs "log" //引入log
}
我们知道eclipse下的logcat信息是有颜色区别的,我们能一眼看出这是提示信息还是警告信息,但是Android Studio中默认的日志颜色只有红色和白色两种,非常的不直观。这里告诉大家一个Android Studio上配置logcat输出不同控制级别日志颜色的小技巧。
接下来我们就可以在我们的C文件中添加相应的log打印语句了:
#include
#include
#include
#include
#define TAG "myTag" //自定义Tag
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE,TAG,__VA_ARGS__) //自定义打印函数LOGV
JNIEXPORT jstring JNICALLJava_com_test_ndk_hellondk_MainActivity_getHelloStringFromC(JNIEnv *env, jclass type)
{
LOGV("log from native!"); //调用log打印函数
return (*env)->NewStringUTF(env,"Get Hello String from C!");
}
可以看出我们的自定义日志myTag打印在logcat栏,且打印级别为我们设置的VERBOSE(V)级别。
到这里日志打印功能已经成功添加进去了。
二、 java层和native层交互###
上一篇文章讲的是ndk的最基础的用法,调用native方法获得native层传来的字符串并显示出来,只涉及到java层调用native层,那么native层能不能用java层的数据呢?答案是肯定的,这里就要涉及到java层和native层的交互。
java层与native层的基本数据类型表示有所不同,对应关系如下表:
从对应关系表中可以看出,native的基本数据类型只需要在java的基本数据类型前加上字母“j”即可。需要注意的是java中的void类型在native中仍是void类型。了解了类型之间的基本转换关系,下面举例说明几种函数的简单扩展用法:
例:在java层调用native层函数,将数组作为参数传入,要求native层修改数组的内容并返回给java层。
要实现这个函数,首先我们需要在MainActivity.java中写native方法:
static{
System.loadLibrary("hello");
}
public static native int[] updateIntArray(int[] data);//将待修改数组作为参数传给native层
写好以后指定updateIntArray()函数使用Alt+Enter,在jni目录下hello.c文件中自动生成对应函数,向函数体内部添加代码即可。
JNIEXPORT jintArray
JNICALLJava_com_test_ndk_hellondk_MainActivity_updateIntArray(JNIEnv *env, jclass type, jintArray data_)
{
jint *data = (*env)->GetIntArrayElements(env, data_, NULL);
// TODO
(*env)->ReleaseIntArrayElements(env, data_, data, 0);
}
这里我们有两种方法来改变传入的int数组的值:
(1)方法一:native层获取java层传递的数组,修改其数据后返回该数组
JNIEXPORT jintArray
JNICALLJava_com_test_ndk_hellondk_MainActivity_updateIntArray(JNIEnv *env, jclass type, jintArray array)
{
jint nativeArray[5];
(*env)->GetIntArrayRegion(env,array,0,5,nativeArray); //生成native层的数组拷贝
int j;
for(j = 0; j<5;j++)
{
nativeArray[j]+=5; //native修改数组内容
LOGV("from c int %d",nativeArray[j]);
}
(*env)->SetIntArrayRegion(env,array,0,5,nativeArray); //设置新的array数组的值
return array;
}
其中GetIntArrayRegion()函数和SetIntArrayRegion()函数在我们引入的jni.h文件里有相关的函数声明,而关于具体函数的定义可以自行上网查阅使用。
(2)方法二:获得指向数组元素的指针,对指针所指向数组内容进行修改
JNIEXPORT jintArray
JNICALLJava_com_test_ndk_hellondk_MainActivity_updateIntArray(JNIEnv *env, jclass type, jintArray array)
{
jint* data =(*env)->GetIntArrayElements(env,array,NULL); //返回jint*
jsize len = (*env)->GetArrayLength(env,array); //返回int数组长度
int j;
for(j = 0;jReleaseIntArrayElements(env,array,data,0);//释放相关的资源
return array;
}
在MainActivity.java中写上相应的调用函数并打印相关log,运行一下,可以看见log的显示表示native层接收到java层的数据并成功修改返回给java层。
Demo很简单,中间要是有任何问题可以检查环境是否配对,代码是否写对,可以参考我的源码工程,已经上传到github上,感兴趣的可以去我的github上下载,下载地址: NDK简单Demo。