ndk

NDK学习笔记(三)

Posted by alonealice on 2017-01-07

在ndk开发中,很多时候并不需要我们实现全部的代码,往往可以通过NDK的API来实现相应的功能,那么ndk中要如何使用NDK的api呢?

添加 NDK API

在介绍如何使用NDK的api时,会以打印的log为例,该包可以实现将native层的信息打印到log中。

首先,我们需要在源文件中引入log的头文件:

1
#include <android/log.h>

引入头文件后,我们需要在CmakeLists文件中做相应的配置:

1
2
3
4
5
6
7
8
9
10
find_library( # 定义库名字(这个名字可以自定义)
log-lib

#定义库(这是log库)
log )
target_link_libraries( # 需要关联的库
String_test

# 关联库
${log-lib})

配置完之后就可以在文件中使用了。

在log.h文件中,可以看到打印函数:

1
int __android_log_print(int prio, const char* tag, const char* fmt, ...)

同时也可以看到不同的打印类型:

1
2
3
4
5
6
7
8
9
10
11
typedef enum android_LogPriority {
ANDROID_LOG_UNKNOWN = 0,
ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
ANDROID_LOG_VERBOSE,
ANDROID_LOG_DEBUG,
ANDROID_LOG_INFO,
ANDROID_LOG_WARN,
ANDROID_LOG_ERROR,
ANDROID_LOG_FATAL,
ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
} android_LogPriority;

__android_log_print函数需要传入至少3个参数,一个是打印类型,一个tag标签,一个是具体的value值。但是我们可以看到,该方法名字较长,传入的参数也比较多,使用起来不是很方便。所以在这种情况下,我们一般会使用宏定义来方便调用。

1
2
#define TAG "native打印"
#define LOGV(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__);

首先是要定义打印的标签,标签就是一个普通的字符串,后面用到了不定参数。

使用时:

1
LOGV("%s","输出的值");

调用Java静态方法和静态变量

调用静态方法和静态变量时主要有3步:首先,要获取到需要调用的类,获取到调用的类之后,要获取需要调用端静态变量或静态方法,最后调用相应的方法和变量。

获取类的示例:

1
jclass clas=env->FindClass("win/whitelife/jnistudy/java/JavaHelper");

获取类需要用到FindClass函数,输入类的完整路径,中间的点由/代替,FindClass返回一个jclass,代表被调用的类。

然后需要得到调用的静态变量或方法的methodID或jfiedlID:

1
2
3
4
5
//找到要调用的静态变量
jfieldID _fieldId=env->GetStaticFieldID(clas,"staticAge","I");

//找到要调用的静态方法
jmethodID _methodId=env->GetStaticMethodID(clas,"getStaticName","(Ljava/lang/String;)Ljava/lang/String;");

GetStaticFieldIDGetStaticMethodID函数主要需要3个参数,一个是之前获取到的jclass变量,另一个是调用的变量或调用的方法的名字,最后是该方法或变量的签名。

什么是签名呢?我们知道Java是有重载方法的,可以定义方法名相同,但参数不同的方法,正因为如此,在JNI中仅仅通过方法名是无法找到 Java中的具体方法的,JNI为了解决这一问题就将参数类型和返回值类型组合在一起作为方法签名。通过方法签名和方法名就可以找到对应的Java方法。

java类型 签名
所有对象 L+classname +;
Class Ljava/lang/Class;
String Ljava/lang/String;
Throwable Ljava/lang/Throwable;
byte B
char C
double D
float F
int I
short S
long J
boolean Z
数组 [+类型

从上表可以看到,除了boolean和long类型,其他的基本类型都是首字母大写,而数组类型都是[+基本类型,如int[]则是[I;而对象都是L+完整的类路径。可是依旧记不住怎么办,可以通过命令直接生成签名。

通过这个签名,我们就可以定位到具体的java方法和变量了。

最后调用相应的方法和变量:

1
2
3
4
5
env->SetStaticIntField(clas,_fieldId,111);//设置数据
env->GetStaticIntField(clas,_fieldId);//获取数据

jstring j=env->NewStringUTF("静态方法构建的名字");
jstring o=(jstring)env->CallStaticObjectMethod(clas, _methodId, j);

调用实例方法和成员变量

调用实例方法和成员变量与调用静态的差不多,区别是在获取到jclass之后,还需要调用其构造方法,产生实例。

1
2
3
jmethodID _consMethodId=env->GetMethodID(clas,"<init>","(I)V");  //获取构造方法的methodID

jobject ob=env->NewObject(clas,_consMethodId,612);//创建实例

在创建实例之后,就可以调用其方法或变量了:

1
2
3
//找到要调用的静态方法
jfieldID _fieldId=env->GetFieldID(clas,"age","I"); //获取数据
env->SetIntField(ob,_fieldId,123); //设置数据