Anroid NDK初体验(下)

在上一篇的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对应的五种控制类型。

Anroid NDK初体验(下)_第1张图片

另外需要注意,引入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输出不同控制级别日志颜色的小技巧。

Anroid NDK初体验(下)_第2张图片

接下来我们就可以在我们的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)级别。

Anroid NDK初体验(下)_第3张图片

到这里日志打印功能已经成功添加进去了。

二、 java层和native层交互###

上一篇文章讲的是ndk的最基础的用法,调用native方法获得native层传来的字符串并显示出来,只涉及到java层调用native层,那么native层能不能用java层的数据呢?答案是肯定的,这里就要涉及到java层和native层的交互。
  java层与native层的基本数据类型表示有所不同,对应关系如下表:

Anroid NDK初体验(下)_第4张图片

从对应关系表中可以看出,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层。

Anroid NDK初体验(下)_第5张图片

  Demo很简单,中间要是有任何问题可以检查环境是否配对,代码是否写对,可以参考我的源码工程,已经上传到github上,感兴趣的可以去我的github上下载,下载地址: NDK简单Demo。

你可能感兴趣的:(Anroid NDK初体验(下))