NDK开发之JNI基础(2),静态注册和动态注册

2019-02-14  本文已影响0人  tianyl

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,那么就稍微麻烦一些了,具体步骤如下

  1. 首先需要配置NDK路径,下载所以编译需要的插件(LLDB、NDK和CMake)官方说明
  2. 在现有的main目录下新建cpp目录,和java目录同级


  3. 在app的build.gradle文件中添加说明
//写在Android标签下
android {
    ...
    defaultConfig {
        ...
        externalNativeBuild {
            cmake {
                //这里是创建项目时勾选的支持
                cppFlags "-frtti -fexceptions"
            }
        }
    }
    
    externalNativeBuild {
        cmake {
            //这里是CMakeLists文件的路径
            path "CMakeLists.txt"
        }
    }
}
  1. 添加CMakeList文件


  2. 添加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

动态注册

  1. 首先需要定义一个结构体
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},
    ...
};
  1. 编写注册方法,这里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;
}
  1. 实现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版本不需要写最新,只需要合适即可

两者的优缺点

一般来讲,静态注册是书写简单,但是由于方法名的对应关系,所以耦合性强,如果包名发生变化,那么需要大量修改,而且初次运行的效率也不如动态注册,所以如果没有特别愿意,推荐使用动态注册

上一篇下一篇

猜你喜欢

热点阅读