使用registerNative方式编写JNI中c/c++函数

上次面试,腾讯小哥听说我写过jni代码,问了一个问题,JNI代码里,Java上层声明的一个native函数,在cpp文件中实现它的时候,函数名过长,有没有什么办法来改进?
一般我们写JNI中cpp的代码,都是Java_包名_类名_native方法名(Java_com_jni_ndkdemo_JniUtils_javaGetString),遇到包名中本身带有下划线的,它会添加上去数字(Java_com_my_1project_test_1jni_JNIUtils_getName)都是以这样的方式去实现c代码,cpp文件中这个函数确实是有点长,一番学习后发现可以有其他的实现方式,核心是registerNative函数,JNI实际有两种注册方式:

  1. 静态注册,就是Java_包名_类名_native方法名这种定位注册方式
  2. 动态注册,registerNative方式注册
    言归正传,我们开始registerNative类型的动态注册的方式来写一个JNI的demo,
    1、首先依然是创建Java的native方法,
package com.jni.ndkdemo;

public class JniUtils {
    public native int javaAdd(int x,int y);
    public native String javaGetString();
    static {
        System.loadLibrary("testLib");
    }
}

2、创建cpp目录以及文件,
在这里插入图片描述
3、创建CMakeLists.txt文件

cmake_minimum_required(VERSION 3.4.1)
add_library(
            testJNI
            SHARED
            src/main/cpp/arraysAdd-lib.cpp)
add_library(
            registerNative
            SHARED
            src/main/cpp/registerNative.cpp)
 add_library(
             testLib
             SHARED
             src/main/cpp/test.cpp)
find_library(
             log-lib
             log
            )
target_link_libraries(testJNI ${log-lib})

这里我们能看到,我们是可以无限编译添加cpp文件的,只要你不停的调用add_library就好,但是有一点要注意,生成so库的名称,不能相同否则编译会失败的,能看到我这里的命名,testJNI、registerNative、testLib都是不相同的
4、在APP的build.gradle文件中添加关于CMake编译方式的配置

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.jni.ndkdemo"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

主要就是上述两个externalNativeBuild 函数的task任务,其实这些和JNI全类名静态注册的方式没有任何区别,静态注册和动态注册的区别只在cpp文件的实现中,下面我们来看registerNative动态注册方式到底是怎么实现的,当jvm启动,执行System.loadLibrary(native-lib),链接到由CMakeLists.txt–Jni中cpp文件生成的so库的时候,系统就会主动的调用cpp文件中的JNI_OnLoad(JavaVM* vm,void* reserve)这个函数名不能乱改,否则系统是无法正常工作的,

jint JNI_OnLoad(JavaVM* vm,void* reserved){
        printf("jni onload called");
        //注册时在JNIEnv中实现的,所以必须首先获取它
        JNIEnv* env =NULL;
        jint result = -1;
        //从JavaVM中获取JNIEnv,一般使用1.4的版本
        if(vm->GetEnv((void **)&env,JNI_VERSION_1_4)!=JNI_OK){
            return -1;
        }
        result = RegisterNative(env);
        printf("RegisterNatives result: %d", result);
        printf("jni onload called end...");
        return JNI_VERSION_1_4; //这里很重要,必须返回版本,否则加载会失败。
    }

这个没什么好说的,JNI_OnLoad是系统函数,JavaVM代表的就是java的虚拟机环境,拿到参数JNIEnv以后,传递给registerNative函数,

jint RegisterNative(JNIEnv* env){
    //获取映射的Java类
        jclass javaClass=env->FindClass(className);//1
        if(javaClass == NULL){
              printf("cannot get class:%s\n", className);
              return -1;
        }
        return env->RegisterNatives(javaClass,getMethods,sizeof(getMethods)/sizeof(getMethods[0]));//2
}

关注点1、FindClass函数是系统函数,直接调用就好,className是java层的native函数所在的全类名字符串,根据java层native函数的全类名找到带有native函数的java类,

//java代码中native方法的全类名
 const char* className="com/jni/ndkdemo/JniUtils";

关注点2、RegisterNatives函数也是系统函数,直接调用传参就好,而getMethods是一个数组,这个数组存放的是native函数相关的信息

const JNINativeMethod getMethods[] = {
        {"javaAdd","(II)I",(void *) cAdd},
        {"javaGetString","()Ljava/lang/String;",(void *) getString}
    };

我们仔细查看这个数组,如果你的demo跑不起来,绝大多数原因都是在这里:单拎出来一个下标的元素,{“javaAdd”,"(II)I",(void *) cAdd},

  1. 参数一:“javaAdd” ----- java层native函数命名是什么,这里就传什么,但要是字符串
  2. 参数二: “(II)I” ------ java层native函数的签名,这个签名是什么,就是能够唯一表示某一个函数的签名,因为java函数是可以重载的,签名是由参数类型列表和返回值共同组成,
  3. 参数三:"(void *) cAdd" --------- cAdds是cpp文件中实现java层native函数的实现方法
jint cAdd(JNIEnv * env,jobject jobj,jint x,jint y){
    printf("cAdd x is :%d ,y is :%d",x,y);
    return x+y;
}

最后使用env这个指针 调用RegisterNatives函数,传入参数,native函数就在底层动态注册好了,根据我的踩坑之路,cpp文件动态注册的代码没有什么问题,大家主要是弄不懂这个JNI中函数的签名是怎么回事,下篇文章我们重点分析一下,https://www.cnblogs.com/CCBB/p/3978847.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值