美文网首页
JNI面试指南

JNI面试指南

作者: anonymous_6bb6 | 来源:发表于2020-04-23 17:48 被阅读0次

    1. 对 JNI 的了解

    java因其跨平台的特性导致其本地交互能力不够,为了便于 Java 与本地代码(C、C++)交互,所以提供了JNI( Java Native Interface)。故名思意,JNI提供的就是将Java层的需求(native 方法)转变成C语言中的接口类,然后由 C 去实现具体方法的功能。

    NDK (Native Develop Kit),是 Android 提供的本地开发工具的集合。其优点有:

    • 提高代码安全性(so 库反编译困难)
    • 方便目前已有的C/C++库
    • 便于平台移植
    • 提高某些情形下的程序执行效率

    2. JNI 函数的注册方法

    静态方法:
    • 创建Java类,声明 native 方法
    • javah 生成头文件 .h文件的作用
    • 创建 C/C++ 文件,实现对应的native方法

    如何连接 Java 层方法和 native 层方法的:

    Java方法被调用时,JVM会生成对应的 native 方法名,例如 com.example.StrHelper.getStr() ,JVM会在JNI库中查找 Java_com_example_StrHelper_getStr 函数,如果找到了,就会保存一个该 JNI 函数的指针,直接调用该指针。如果没找到就会报错。

    上代码:

    1.准备工作,Android Studio 中安装好NDK、 CMAK、 LLDB 工具
    2.起一个新的项目,在选择Activity类型的时候直接选择最后的Native C++ 类型一路 next 下去,这样你就不用配置gradle 等文件了,最简单。
    3.创建一个java文件,用来声明native方法

    package com.cn.jnitest;
    public class NativeHelper {
        static {
            System.loadLibrary("native-lib");//一定要确保在调用native方法前加载了so库
        }
        public static native String getAppKeys();
    }
    

    4.javac xxx.java 或者 build 生成class文件
    5.将 .class 生成 .h文件,javah -cp <class文件所在包的绝对路径> -jni com.cn.jnitest.NativeHelper如果有问题

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_cn_jnitest_NativeHelper */
    
    #ifndef _Included_com_cn_jnitest_NativeHelper
    #define _Included_com_cn_jnitest_NativeHelper
    #ifdef __cplusplus
    extern "C" {
    #endif
    JNIEXPORT jstring JNICALL Java_com_cn_jnitest_NativeHelper_getAppKeys
      (JNIEnv *, jclass);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    6.最后就是实现native方法了,创建一个C++文件,最好保持名字和.h文件一致

    #include <jni.h>
    #include "com_cn_jnitest_NativeHelper.h"
    JNIEXPORT jstring JNICALL Java_com_cn_jnitest_NativeHelper_getAppKeys(JNIEnv *env, jclass type)
    {
        char* app_key = "5465465416948";
    
        return env->NewStringUTF(app_key);
    }
    

    再在CMakeLists.txt中加入

    add_library( 
                 native-lib
                 SHARED
                 native-lib.cpp
                 com_cn_jnitest_NativeHelper.cpp)//这个.cpp文件
    

    就大功告成了,直接运行,可以调用native方法来获取string了

    你也可以在原有项目上添加JNI代码,只不过需要改一下gradle 配置和添加CMakeLists.txt 文件
    如下

    //build.gradle 文件中最少指定 CMakeLists.txt 的位置
    externalNativeBuild {
            cmake{
                path "src/main/jni/CMakeLists.txt"
            }
        }
    
    //CMakeLists.txt 指定cpp文件即可
    add_library(
            main
            SHARED
            hun.cpp)
    

    然后就可以了,其实还有一些其他的设置项,日后再出一篇来讲解。

    弊端:

    • 编写不方便,JNI方法名字遵循规则,很长
    • 编写过程步骤太多,每个声明 native 方法的类都要生成一个 .h 头文件。
    • 初次调用需要在JNI 层根据函数名查找建立对应关系,耗时
    动态注册:
    • 创建Java类,声明native方法
    • 创建对应 C++ 类,在该类中实现JNI_OnLoad方法,定义JNINativeMethod列表,以及Java native方法具体实现(此时,方法的名称可以是任意的)

    通过System.LoadLibrary()加载so库的时候,JVM会调用JNI_OnLoad方法,而我们可以通过在该方法中调用JNIEnv->RegisterNatives()方法将我们的native方法声明注册到JNI中,那是如何将native方法与Java方法联系起来的呢,就是通过JNINativeMethod结构体将两者联系起来的。
    上代码:

    typedef struct {
        const char* name;//这个是java层函数的名字
        const char* signature;//这个是Java层函数的签名,其他两个很好理解,这个函数签名是个啥???下面会讲
        void*       fnPtr;//这个是native层函数的名字
    } JNINativeMethod;//这就是结构体的主要内容,然后我们怎么写呢
    

    TestJni.java

    package com.cn.mydynamic;
    public class TestJni {
        static {
            System.loadLibrary("main");
        }
        public native String sayHello();//定义了一个native方法
    }
    

    上面的JNINativeMethod结构体中的第二项,Java层函数签名,就是按照一定的规则,将java层函数的参数返回值进行转化,为啥整出个这玩意儿?因为java支持函数重载,仅凭函数名称是找不对对应函数的,所以就用参数和返回值结合函数名称来找。
    规则如下:
    当参数的类型是引用类型时,其格式是" L包名;",其中包名中的"." 换成"/"。
    很容易写错,但是可以通过javap -s -p xxx.class直接生成转换好的签名,上述的TestJni转换后为

    >javap -s -p TestJni.class
    Compiled from "TestJni.java"
    public class com.cn.mydynamic.TestJni {
      public com.cn.mydynamic.TestJni();
        descriptor: ()V
    
      public native java.lang.String sayHello();
        descriptor: ()Ljava/lang/String;
    
      static {};
        descriptor: ()V
    }
    

    有了签名有了Java 函数,有了native函数,就可以放进结构体里了

    //建立Java层函数与native层函数的对应关系
    static const JNINativeMethod getMethod[] = {
            {"sayHello",
             "()Ljava/lang/String;",
             (void*)sya_hello
            }
    };
    
    JNIEXPORT jstring JNICALL sya_hello
            (JNIEnv *env, jobject job)
    {
        char* app_key = "不要回答!!!不要回答!!!";
    
        return env->NewStringUTF(app_key);
    }
    

    接下啦就要把对应关系注册上

    #define NELEM(m) (sizeof(m) / sizeof((m)[0]))
    
    jint JNI_OnLoad(JavaVM* vm, void* reserved) {
        JNIEnv* env = NULL;
        if (vm ->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
            return -1;
        }
    
        assert(env != NULL);
    
        jclass clazz;
        clazz = env->FindClass("com/cn/mydynamic/TestJni");
        if (clazz == NULL) {
            return -1;
        }
    
        if (env->RegisterNatives(clazz, getMethods, NELEM(getMethods)) < 0) {
            return -1;
        }
    
        return JNI_VERSION_1_4;
    };
    
    运行结果 device-2020-04-23-174651.png

    相关文章

      网友评论

          本文标题:JNI面试指南

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