美文网首页
零基础带你吃掉JNI全家桶(一)

零基础带你吃掉JNI全家桶(一)

作者: 来自怀旧的你 | 来源:发表于2019-02-14 17:05 被阅读0次

    前言

    大家好!我又来了,这次准备着手写一个JNI开发系列,毕竟,现在JNI开发也是在各个公司越来越重要了,如果项目毕竟大,可能涉及的模块较多,比如你作为应用层的开发,难免避免不了需要使用一些库,一些加密操作等等,一般都会放在本地方法里面,比较安全,人家丢给你so文件或者静态a文件。。你不会用岂不是很尴尬。网上资料比较杂,而且很乱,大部分还是在用.mk的方法,本系列就基于CMake形式,希望能够带着一些希望学习JNI开发的小伙伴一起学会JNI开发~

    零基础带你吃掉JNI全家桶(二)

    零基础带你吃掉JNI全家桶(三)

    从一个栗子说起

    c++ support
    注意:要支持CMake,此时我们需要勾选 Include C++ support,然后点击Next--->Finish,完成工程的创建。
    创建完成后,我们打开工程目录,发现增加了几个不一样的地方:
    image.png
    发现AS已经帮我们生产一个cpp目录以及一个native-lib.cpp的c++文件,在根目录下,也多了一个CMakeLists.txt文件,我们主要来关注CMakeLists.txt里面的东东
    #定义cmake支持的最小版本号
    cmake_minimum_required(VERSION 3.4.1)
    
    
    add_library( # 设置生成so库的文件名称,例如此处生成的so库文件名称应该为:libnative-lib.so
                 native-lib
    
                 # 设置生成的so库类型,类型只包含两种:
                 # STATIC:静态库,为目标文件的归档文件,在链接其他目标的时候使用
                 # SHARED:动态库,会被动态链接,在运行时被加载
                 SHARED
    
                 # 设置源文件的位置,可以是很多个源文件,都要添加进来,注意相对位置
                 src/main/cpp/native-lib.cpp )
    
    # 从系统里查找依赖库,可添加多个
    find_library( # 例如查找系统中的log库liblog.so
                  log-lib
    
                  # liblog.so库指定的名称即为log,如同上面指定生成的libnative-lib.so库名称为native-lib一样
                  log )
    # 配置目标库的链接,即相互依赖关系
    target_link_libraries( # 目标库(最终生成的库)
                           native-lib
    
                            # 依赖于log库,一般情况下,如果依赖的是系统中的库,需要加 ${} 进行引用,
                            # 如果是第三方库,可以直接引用库名,例如:
                            # 引用第三方库libthird.a,引用时直接写成third;注意,引用时,每一行只能引用一个库
                           ${log-lib} )
    

    这里我把注释进行了缩减,标注了中文注释,比较详细,不明白的可以看下每个的作用,当然还有很多API可以使用,后续再详细说明,也可以看看官方文档,[戳我戳我]
    (https://developer.android.google.cn/ndk/guides/cmake)

    我们新建一个Helper类来编写native方法

    public class NativeHelper  {
        static {
            System.loadLibrary("native-lib");
        }
        public  native String stringFromJNI();
        public  native int add(int a,int b);
     
    }
    

    打开我们的MainActivity

    public class MainActivity extends AppCompatActivity {
    
        // Used to load the 'native-lib' library on application startup.
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            // Example of a call to a native method
            TextView tv = (TextView) findViewById(R.id.sample_text);
            tv.setText(stringFromJNI());
        }
    }
    

    可以看到最上面,静态代码块引用了native-lib这个库,然后直接调用native本地方法,将C++中返回的字符串拿到进行显示。然后看看C++具体是怎么是实现的

    #include <jni.h>
    #include <string>
    
    extern "C" JNIEXPORT jstring
    
    JNICALL
    Java_com_example_hik_cmake_MainActivity_stringFromJNI(
            JNIEnv *env,
            jobject /* this */) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
    

    代码很简单,引用两个头文件,然后定义了一个方法,返回“Hello from C++”这个字符串,那有的朋友要问了,为什么java层直接调用stringFromJNI()方法能够直接映射到C++里面的方法呢,细心的小伙伴可能发现了,C++里面的这个方法名很长而且很熟悉。。这不是Java包名加上方法名拼凑而成的字符串吗,这种方式呢叫做静态注册,这样就能通过这个映射方式找到C++中的方法。

    有的朋友又要说了,这么长方法名也太麻烦了,虽然可以自动生成,但是多不美观,多不优雅。。是滴!有静态注册,那当然就有动态注册了~我们来改一改代码:

    #include <jni.h>
    #include <string>
    #include <android/log.h>
    
    #define TAG "JNI_"
    #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)
    JNICALL
    jstring backStringToJava(JNIEnv *env, jobject /* this */) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
    //动态注册
    jint registerMethod(JNIEnv *env) {
        jclass clz = env->FindClass("com/example/taolin/jni_project/NativeHelper");
        if (clz == NULL) {
            LOGD("con't find class: com/example/taolin/jni_project/NativeHelper");
        }
        JNINativeMethod jniNativeMethod[] = {{"stringFromJNI", "()Ljava/lang/String;", (void *) backStringToJava}};
        return env->RegisterNatives(clz,jniNativeMethod, sizeof(jniNativeMethod)/ sizeof(jniNativeMethod[0]));
    }
    jint JNI_OnLoad(JavaVM *vm, void *reserved){
        JNIEnv * env = NULL;
        if (vm->GetEnv((void**)&env,JNI_VERSION_1_6)!=JNI_OK){
            return JNI_ERR;
        }
        jint result = registerMethod(env);
        LOGD("RegisterNatives result: %d", result);
        return JNI_VERSION_1_6;
    }
    

    这里呢,为了在C++中打印日志,我们需要引入log.h头文件,然后我们把之前的方法名改成backStringToJava,然后因为没有了静态注册的规则,Java层调用的使用当然就找不到我们对应的方法了,我们定义一个动态注册的方法,将Java中的方法和C++中的方法进行动态的绑定:

    • 通过env指针,拿到MainActivity的class对象,具体env指针后续会详细说明
    • 定义一系列的方法对象,每个包含三个参数,第一个是java中的方法名,第二个是方法对应的签名,第三个是C++中的方法名
    • 在JNI_OnLoad方法中,调用动态注册绑定方法进行绑定

    有些朋友可能对方法签名不太明白,后续语法会详细说明,这里先简单说下,方法签名也就是一个方法唯一性的一个标准,上面的()Ljava/lang/String;就是stringFromJNI的签名,前面的括号里面是参数的签名,因为这里没有参数,所以为空,紧接着后面是返回值得签名,规则是,如果是基本数据类型就是相对应的基本数据类型,如果不是基本数据类型,那么就是L+对象包名+“;”,注意这里的分号不可省略!!根据这个规则,下面那个方法的签名就是(II)I,依次类推,没明白的也没关系,后面会详细对JNI中的语法详细解释,先知道有这么回事就好了。

    public native String stringFromJNI(); 
    //签名:()Ljava/lang/String;
    public native int add(int a int b) 
    //签名:"(II)I"
    

    然后我们直接运行APP,可以发现页面上显示出来了“Hello from C++”字符串,然后看看我们生成的so文件在哪:


    image.png

    OK!大功告成,我们的第一步就完成了,成功的完成了Java调用C++的方法,但别高兴的太早,这只是第一步,好了,看到这的奖励自己跟辣条吧~,溜了溜了。。

    相关文章

      网友评论

          本文标题:零基础带你吃掉JNI全家桶(一)

          本文链接:https://www.haomeiwen.com/subject/hgldeqtx.html