JNI面试指南

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 -jni com.cn.jnitest.NativeHelper。如果有问题

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* 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 
#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面试指南)