JNI的编译过程
当使用JNI时,实现在xxx.c文件中的方法,会经过编译,链接之后打包到apk文件中,其中分为编译,链接两个步骤
编译
xxx.c文件生成xxx.obj文件(Windows平台)或xxx.o文件(linux平台)
在编译过程,会做语法检查
链接
.o文件链接成xxx.so文件
将多个.c文件和.h文件生成.so文件,并且检查多个文件的引用的方法是否存在(比如a文件引用了b文件的方法,但是b中没有这个方法,那么就会链接出错)
编译规则
eclipse中,使用的是GUN规则,写在Android.mk文件中
在AndroidStudio中,使用的是LLVM规则,写在CMakeList.txt文件中
它们都三段式编译器
因为Android Studio中主要使用的是CMakeList,所以这里就略去Android.mk的语法说明,相关Android.mk的语法可以查看Google的官方文档或者使用搜索引擎
因为CMakeList语法说明较长,所以单独说明
添加现有项目支持JNI
如果想在新项目中使用JNI,那么只需要勾上NDK工具的选项即可,但是如果想修改现有的项目支持JNI,那么就稍微麻烦一些了,具体步骤如下
- 首先需要配置NDK路径,下载所以编译需要的插件(LLDB、NDK和CMake)官方说明
-
在现有的main目录下新建cpp目录,和java目录同级
- 在app的build.gradle文件中添加说明
//写在Android标签下
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
//这里是创建项目时勾选的支持
cppFlags "-frtti -fexceptions"
}
}
}
externalNativeBuild {
cmake {
//这里是CMakeLists文件的路径
path "CMakeLists.txt"
}
}
}
- -frtti:Runtime Type Information Support,如果想支持 RTTI,那么可以在创建项目的时候勾选它。或者在module层的build.gradle文件中的cppFlags中添加-frtti标志,两者方法效果是一样的。
- -fexcetions:Exceptions Support,有关C++的异常处理的支持,可以在创建项目的时候勾选,或者在 module层的build.gradle文件中的cppFlags中添加-fexcetions标志,两者方法效果是一样的。
-
添加CMakeList文件
- 添加native方法,并在在cpp目录下添加对应的实现
两种实现native方法的方式
jni中有两种实现native方法的方式,分别为静态注册和动态注册
静态注册
静态注册就是通过对应方法名的方式来实现native方法对应的c方法,方法命名规则:Java_类的全名_方法名
例如
//这是声明在Java文件中的方法
public native void test();
//这是对应在c中的方法名
//我的包名是:com.demo.tianyl.jnidemo
Java_com_demo_tianyl_jnidemo_MainActivity_test
动态注册
- 首先需要定义一个结构体
typedef struct {
const char* name; /*Java 中函数的名字*/
const char* signature; /*描述了函数的参数和返回值*/
void* fnPtr; /*函数指针,指向 C 函数*/
} JNINativeMethod;
例如系统源码的写法
static const JNINativeMethod gMethods[] = {
{"setCamera", "(Landroid/hardware/Camera;)V", (void *)android_media_MediaRecorder_setCamera},
{"setVideoSource", "(I)V", (void *)android_media_MediaRecorder_setVideoSource},
{"setAudioSource", "(I)V", (void *)android_media_MediaRecorder_setAudioSource},
...
};
- 编写注册方法,这里registerNativeMethods方法需要写在registerNatives之上
static int registerNativeMethods(JNIEnv* env
, const char* className
, JNINativeMethod* gMethods, int numMethods) {
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
static int registerNatives(JNIEnv * env){
if(!registerNativeMethods(env,JNIREG_CLASS,getMethods,sizeof(getMethods)/sizeof(getMethods[0])))
return JNI_FALSE;
return JNI_TRUE;
}
- 实现JNI_OnLoad方法
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
if (!registerNatives(env)) {//注册
return -1;
}
result = JNI_VERSION_1_4;
return result;
}
注册的方法就是从JNI_OnLoad方法中调用的,这里的JNI版本不需要写最新,只需要合适即可
两者的优缺点
一般来讲,静态注册是书写简单,但是由于方法名的对应关系,所以耦合性强,如果包名发生变化,那么需要大量修改,而且初次运行的效率也不如动态注册,所以如果没有特别愿意,推荐使用动态注册
网友评论