(二)JNI基础

一、什么是JNI?

JNI是 Java Native Interface 的缩写,表示 Java 本地接口。从 Java 1.1 开始,JNI 标准便成为了 Java 平台的一部分,它允许Java 代码和其他语言写的代码进行交互。

二、JNI基础

2.1 JNI的功能结构

JNI 最初是由 Sun 提供的Java 与本地系统中的原生方法交互的技术,用于在Windows/Linux 系统中实现 Java 与 Native Method (本地方法) 的相互调用。JVM(Java虚拟机)在封装各种操作系统实际的差异性的同时提供了JNI 技术,使得开发者可以通过Java程序(代码)调用到操作系统相关的技术实现的库函数,从而与其他技术和系统交互,使用其他技术实现的功能。同时,其他技术和系统也可以通过JNI提供的相应原生接口调用Java应用系统内部实现的功能。

2.2 JNI的调用层次

JNI的调用层次主要风味3层,在Android系统中这三层从上到下依次为Java→JNI→C/C++(.so库),Java可以访问到C/C++中的方法,同样C/C++也可以修改Java对象,三者间关系如下图:
在这里插入图片描述
由图可知,JNI的调用关系为 Java→JNI→Native。

2.3 分析JNI的本质

要想弄明白JNI的本质,还要从Java的本质说起。从本质上说,Java语言的运行完全依赖与脚本引擎对Java的代码进行解释和执行。因为现代的Java可以从源代码编译成 .class 之类的中间格式的二进制文件,所以这种处理会加快 Java 脚本的运行速度。尽管如此,基本的执行方式仍然不变,有脚本引擎(被称之为 JVM)来执行。与Python、perl之类的纯脚本相比,只是把脚本变成了二进制格而已。另外,Java本身就是一门面向对象的编程语言,可以调用完善的哦功能库。当把这个脚本引擎移植到所在的平台上之后,这个脚本就很自然的实现“跨平台”了。绝大多数的脚本引擎都支持一个很显著的特性,就是可以通过C/C++编写模块,并在脚本中调用这些模块。Java也是如此,Java 一定要提供一种在脚本中调用 C/C++编写的模块的机制,才能称得上是一个完善的脚本引擎。

从本质上来看,Android平台是由 arm-linux 操作系统和一个 Dalvik 虚拟机组成的。所有在 Android 模拟器上看到的界面效果都是用 Java 语言编写的,具体请看源代码中的 framenworks/base 目录。Dalvik 虚拟机只是提供了一个标准的支持 JNI 调用的 Java 虚拟机环境。

在 Android 平台中,使用 JNI 技术封装了所有和硬件相关的操作,通过 Java 去调用 JNI 模块,而 JNI 模块使用 C/C++ 调用 Android 系统本身的 arm-linux 底层驱动,这样便实现了对硬件的调用。

2.4 Java 和 JNI基本数据类型的转换

在Android 5.0中,Java 和 JNI 基本数据类型转换信息表如下所示。

JavaNative类型本地C类型字长
booleanjboolean无符号8位
bytejbyte无符号8位
charjchar无符号16位
shortjshort有符号16位
intjint有符号32位
longjlong有符号64位
floatjfloat有符号32位
doublejdouble有符号64位

数组类型的对应关系如下表所示。

字符Java类型C类型
[IjintArrayint[]
[FjfloatArrayfloat[]
[BjbyteArraybyte[]
[CjshortArrayshort[]
[DjdoubleArraydouble[]
[JjlongArraylong[]
[SjshortArrayshort[]
[ZjbooleanArrayboolean[]

对象数据类型的对应关系如下。

对象Java类型C类型
LJava/lang/StringStringjstring
LJava/net/SocketSocketjobject

2.5 JNIEnv接口

在Android 5.0中,Native Method(本地方法)中的JNIEnv作为第一个参数被传入。JNIEnv的内部结构如下图所示。
在这里插入图片描述

当JNIEnv不作为参数传入时,JNI提供了如下两个函数来获得JNIEnv口。

(*jvm)->AttachCurrentThread(jvm,(void **)&env,NULL);
(*jvm)->GetEnv(jvm,(void**)&env,JNI_VERSION_1_2);
#上述两个函数都利用 Java VM 接口获得了 JNIEnv接口,并且 JNI 可以将获得的 JNIEnv 封装成一个函数。

Java通过JNI机制调用C/C++写的程序,C/C++开发的Native程序需要遵循一定的 JNI 规范。例如,下面就是一个 JNI 函数声明的例子。

JNIEXPORT jint JNICALL  Java_jniTest_MyTest_test(JNIEnv * env,jobject obj,jint arg);

JVM 负责从 Java Stack 转入C/C++ Native Stack。当 Java 进入 JNI调用,除了函数本身的参数(arg)外,会多多出两个参数:JNIEnv 指针和 Jobject 指针。其中,JNIEnv 是 JVM 创造的,被 Native 的 C/C++ 方法用来操纵 Java 执行栈中的数据,例如 Java Class、Java Method 等。

三、开发JNI程序

开发JNI程序的一般步骤如下所示。

  1. 编写Java中的调用类
  2. 用 javah 生成C/C++ 原生函数的头文件
  3. 在 C/C++ 中调用需要的其他函数功能实现原生函数,原则上可以调用任何资源
  4. 将项目依赖生成的所有原生库和资源加入到 Java 项目
  5. 生成 Java程序

3.1 开发 JNI 程序

  1. 使用 Android Studio创建工程,在项目目录下创建 cpp 文件夹用来存放 C/C++ 文件
    在这里插入图片描述
  2. 创建 C/C++ 源文件,并将原文件关联到项目,根据不同的构建工具选择构建文件,如果采用的是ndk-build,则需要编写Android.mk 文件,并将它指向项目,如果采用的是 CMake 工具,则需要编写 CMakeLists.txt 文件,并将它指向项目。具体详细操作可参考:向您的项目添加 C 和 C++ 代码一文。
  3. 编写 JNI 函数
public class JNIInterface {
    public static native int NativeInit();
    public static native int NativeUninit();
    public static native int Start(int type);
    public static native int Stop();
    private static native byte[] GetMessage(int type, int defaultLen);//注:defaultLen要足够大,要能放下可能的最长的命令
    public static native int FreeData(byte[] data);
    public static native int Test(int param);

    public static native boolean SendDataToDev(int type, byte[] data, int len);

    static {
        System.loadLibrary("allong");
    }
}
  1. 注册 JNI 函数
    在现实应用中,Java 的 Native 函数与JNI 函数是一一对应关系。在Android 系统中,使用 JNINativeMethod 的结构体来记录这种对应关系。
    在 Android 系统中,使用一种特定的方式来定义其 Native 函数,这与传统定义的 Java JNI 的方式有所差别。其中很重要的区别是在 Android 中使用了一种 Java 和 C 函数的映射表数组,并在其中描述了函数的参数和返回值。这个数组类型是 JNINativeMethod ,具体定义如下所示。
typedef struct {
    const char* name; 	//Java 中函数的名字
    const char* signature; // 描述了函数的参数和返回值
    void*       fnPtr;	// 函数指针,指向C 函数
} JNINativeMethod;

示例如下:

JNINativeMethod methods[] = {
        {"NativeInit",    "()I",     (void *) NativeInit},
        {"NativeUninit",  "()I",     (void *) NativeUninit},
        {"Start",         "(I)I",    (void *) Start},
        {"Stop",          "()I",     (void *) Stop},
        {"GetMessage",    "(II)[B",  (void *) GetMessage},
        {"FreeData",      "([B)I",   (void *) FreeData},
        {"SendDataToDev", "(I[BI)Z", (void *) SendDataToDev}
};

上述代码中,比较难以理解的是第二个参数,例如:

"()V"
"(II)V"
"(Ljava/lang/String;Ljava/lang/Striing;)V"

实际上这些字符是与函数的参数一一对应的,具体说明如下所示。

  • ()中的字符表示参数,后面的则代表返回值。例如,"()V"就表示 void Func() ;
  • (II)V 表示 void Func(int ,int)。
    具体的每一个字符的对应关系如下表所示
字符Java 类型C类型
Vvoidvoid
Zjbooleanboolean
Ijintint
Jjlonglong
Djdoubledouble
Fjfloatfloat
Bjbytebyte
Cjcharchar
Sjshortshort

而数组则以 [ 开始,用两个字符表示,例如 “[F”,“[B”等。
上面的都是基本数据类型,如果 Java 函数的参数是 class ,则以 L 开始,以 “;”结尾,中间部分使用 “/”隔离开的包名及类名。而其对应的 C 函数名的参数则为 jobject 。一个例外是 String 类,其对应的类为 jstring,即:

  • Ljava/lang/String 中的 String jstring ;
  • Ljava/net/Socket 中的Socket jobject 。
    如果 Java 函数位于一个嵌入类,则使用“$”作为类名间的分隔符。例如:
(Ljava/lang/String$Landroid/os/FileUtils$FileStatus;)

实现注册工作

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    UnionJNIEnvToVOid uenv;
    uenv.env = NULL;
    jint result = -1;
    JNIEnv *env = NULL;

    LOGI("JNI_OnLoad");

    if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {

        LOGE("Error:GetEnv failed");
        return JNI_VERSION_1_4;
    }

    env = uenv.env;

    if (registerNatives(env) != JNI_TRUE) { //注册函数
        LOGE("ERROR: registerNatives failed");
        goto bail;
    }

    result = JNI_VERSION_1_4;

    bail:
    return result;
}

Java 虚拟机在加载 JNI 函数时,首先会调用 JNI_OnLoad 方法,在其中实现注册逻辑。

const char * classPathName = "com/yj/example/natives/JNIInterface" //native 方法所在的类名

static int registerNatives(JNIEnv *env) {
    if (!registerNativeMethods(env, classPathName, methods, sizeof(methods) / sizeof(methods[0]))) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}
static int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *gMethods,
                                 int numMehthods) {
    jclass clazz = env->FindClass(className);
    if (clazz == NULL) {
        LOGE("Native register unable to find class '%s'", className);
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMehthods)) {
        LOGE("RegisterNatives failed for '%s'", className);
        return JNI_FALSE;
    }
    return JNI_TRUE;
}
  1. 编写 CMakeLists.txt ,可参考 向您的项目添加 C 和 C++ 代码 中 CMakeLists.txt 的编写规则。
# Sets the minimum version of CMake required to build your native library.
# This ensures that a certain set of CMake features is available to
# your build.

cmake_minimum_required(VERSION 3.4.1)

# Specifies a library name, specifies whether the library is STATIC or
# SHARED, and provides relative paths to the source code. You can
# define multiple libraries by adding multiple add.library() commands,
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.

file(GLOB native_src "src/main/cpp/*.cpp")

add_library( # Specifies the name of the library.
             allong

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             ${native_src} )
find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )
#Specifies a path to native header files
target_link_libraries( # Specifies the target library.
                       allong

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )
include_directories(src/main/cpp/include/)
  1. 使用 ndk-build 编译生成 .so 文件,将最后生成 .so 文件拷贝到 jniLibs 目录下。
    在这里插入图片描述
  2. 编写 Java 调用代码,并测试自己写的程序。
   static {
        System.loadLibrary("allong");
    }

参考内容

  1. 向您的项目添加 C 和 C++ 代码
  2. 《深入理解 Android 系统》 张元亮 编著 清华大学出版社
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值