带着问题学习

  1. 什么是 JNI?
  1. JNI 的作用是什么?
  1. JNI 怎么操作 Java 中的对象?
  1. JavaVM 与 JNIEnv 的联系与区别是什么?

什么是JNI

JNI 全称为 Java Native Interface,我们在 Android 开发中,现在一般都是基于 Java/Kotlin 进行开发,但是,我们难免需要与用 C/C++ 编写的本地方法进行交互,而我们知道,Java/Kotlin 本身并不能直接调用 C/C++ 的代码,所以,就需要用到 JNI,也就是说 JNI 就像是连接 C/C++ 与 Java/Kotlin 之间的桥梁,使得 Java/Kotlin 能间接地调用到 C/C++ 的代码

怎么使用 JNI

  • 安装 CMake 和 NDK
  • 创建 native C++ 项目
  • 运行

安装 CMake 和 NDK

打开 Android Studio 的 setting 面板,找到 Appearance&Behavior -> System Settings -> Android SDK -> SDK Tools,勾选上 NDK 和 CMake,进行下载安装即可
notion image
notion image

创建 native C++ 项目

打开 new Project 面板,拉到最下面,选择 native C++,选择响应的 C++ 版本并创建就行
notion image

运行

创建完项目之后,可以看到,自动生成了以下几个文件,其中 CMakeLists.txt 是 CMake 用于配置编译选项、源文件、库链接等的构建脚本文件
这时候,初始代码 Android Studio 已经生成完了,可以直接运行看效果
notion image

如何在 Java 中调用 C/C++

引入相应的库

在 JNI 中,所有的 C/C++ 都不是直接调用的,而是会被打包成 so 库的形式,所以,在 Java/Kotlin 中使用时,要先引入相应的库,下面来看看我们刚刚创建完的项目里面的初始化代码是怎么做的

声明方法并调用

上面已经引入了 C/C++ 的库,接下来,就需要在我们的 Java/Kotlin 代码中,声明在 C/C++ 库中实现的方法,然后直接调用即可

JNI 相关

引入

在上面创建的项目中,可以看到,自动生成了一个 .cpp 文件,其中生成了几行代码,其函数名是以 包名+文件名+方法名 构成的,下面来看看其代码
可以看到,上面的函数中,传入了 JNIEnv* env 和 jobject 两个参数,下面,我们来讨论一下其具体作用是什么

JavaVM and JNIEnv

先来看看 Google 官方的解释
JNI defines two key data structures, "JavaVM" and "JNIEnv". Both of these are essentially pointers to pointers to function tables. (In the C++ version, they're classes with a pointer to a function table and a member function for each JNI function that indirects through the table.)
The JavaVM provides the "invocation interface" functions, which allow you to create and destroy a JavaVM. In theory you can have multiple JavaVMs per process, but Android only allows one.
The JNIEnv provides most of the JNI functions. The JNIEnv is used for thread-local storage.

JavaVM

从官方描述中可以看出,理论上,一个进程,是可以有多个 JavaVM 的,但是 在 Android 中,只允许一个进程拥有一个 JavaVM
JavaVM 是一个代表 Java虚拟机实例 的数据结构,具体是一个 struct 类型,可以用于获取 JVM实例 的引用,并通过它,访问一下 JVM 的函数,比如创建 Java 对象、调用 Java 方法、获取当前线程的 JNIEnv 指针等,下面来看看其具体定义:
下面是一个简单使用的例子:

JNIEnv

从官网的描述中,可以知道 JNIEnv 提供了 JNI 大部分的功能,并且是一个 Thread-Local storage,且 JNIEnv 只在创建它的线程中有效,也就是线程私有的,这一点可以和上面的 JavaVM 对比,同时,对于在 native 中创建的线程,如果需要访问我们的 JNI 方法,就需要先用 AttachCurrentThread 方法将其与现有的 JNIEnv 进行绑定,并且在不需要或者线程退出时,调用 DetachCurrentThread() 方法进行解除
从上面我们创建出来的项目中,其初始化代码,在 .cpp 文件的方法中,就传入了一个 JNIEnv 对象,这个对象提供了访问 Java运行时环境 的能力,我们可以通过这个,从而操作我们的 Java对象、Java方法,还可以对 Java memory 进行管理
与 JavaVM 一样,JNIEnv 也是一个 struct 类型的,下面来看看其定义:
可以看到,JNIEnv 定义了很多方法,几乎包含了全部的 JNI 方法,大概可以概括为,在 C/C++ 根据我们的 Java class 调用相应的方法,并返回相应的类型

参考/推荐

 
Retrofit 初探源码Leetcode_64-最小路径和