1. 什么是JNI
JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。
JNI不仅仅是Android特有的,它是属于Java平台的,它允许在Java虚拟机内运行的java代码与其他编程语言(如c, c++和汇编语言)编写的程序和库进行交互。Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是并不是说不能使用其他编程语言,只要调用约定受支持就可以了。不同语言编写的程序之间调用也不是Java和C++特有的,Java可以调用C++,python、go语言都可以。
JNI使得在 Java 虚拟机内运行的 Java 代码能够与其它编程语言互相操作,包括创建本地方法、更新Java对象、调用Java方法,引用 Java类,捕捉和抛出异常等,也允许 Java代码调用 C/C++或汇编语言编写的程序和库。作为一个标准程序接口,它没有对底层 Java虚拟机的实现施加任何限制,并具有以下特点:
- 二进制兼容。本地方法库与同一平台上所有Java 虚拟机之间实现二进制兼容,即对于给定平台开发人员只需要维护一种版本的本地方法库。但是同时也丧失了跨平台性,想想我们Android只编译arm平台的so库,就会导致无法在x86平台运行。
- 效率高。为了实现实时系统,JNI 在效率与虚拟机无关性之间进行了优化,以保障高效运行。
- 功能强。JNI 提供了大量的函数及接口让本地方法与Java 虚拟机内核相互操作,增强两者的功能。
2. 为什么会有JNI
JNI产生的原因和背景是什么呢?我们熟练掌握一门语言后为什么还要再用另一门语言去写呢?它是出于以下几方面的考虑:
- 历史程序复用:很多已经使用其他语言实现的功能,我们可以直接拿来主义,编译完直接使用,比如音视频领域强大的ffmpeg就是用纯c语言实现的,如果再用java实现一遍就是完全重复造轮子;
- 跨平台代码复用:我们想写一份代码Android、iOS、Mac、Linux公用,他们不同平台使用不同语言,怎么办呢?使用C/C++实现核心功能,然后不同平台使用不同语言的本地接口进行调用;
- 弥补Java平台无关性的不足:Java平台无关性导致访问系统底层或者驱动方面存在缺陷,因为我们知道操作系统都是C/C++实现,如果要访问底层硬件能力,必须要能调用C/C++接口。在Android中,我们的摄像头、麦克风、显示屏、硬件编解码器等能力在底层都是由C/C++封装;
- 性能考虑:有些CPU密集型操作可以使用JNI调用C/C++或者汇编实现,来提高效率。
3. JNI接口介绍
JNI对外暴露的接口主要是我们jni.h
头文件,头文件里面为我们封装了JNIEnv环境变量结构和JavaVM结构体。任何方法的调用都离不开JNIEnv变量,C和C++接口还有一些差异: 在C的定义中,env是一个两级指针,而在C++的定义中,env是个一级指针。C形式需要对env指针进行双重deferencing,而且须将env作为第一个参数传给jni函数,举个简单的例子:
- 对于demo.c:JNI 函数调用由
(*env)->
作前缀,目的是为了取出函数指针所引用的值,如:return (*env)->NewStringUTF(env,"HelloJNI!");
- 对于demo.cpp:JNIEnv 类拥有处理函数指针查找的内联成员函数,如:
return env->NewStringUTF("HelloJNI!");
但是其实本质没有什么区别,一个是JNI调C,一个是JNI调C,C再调C++。
4. 经常与我们打交道的JNI
下面介绍下我们日常接触的一些使用了JNI能力的开源库:
- ijkplayer:做移动端播放器的对ijkplayer不会陌生,它是哔哩哔哩推出的跨平台开源播放器,由于它底层是ffmpeg实现,用到了C代码,所以需要通过JNI做包装;
- webrtc:谷歌开源的跨平台实时通信库,包括3A,弱网对抗等核心能力使用C/C++实现,平台层做少量包装适配;
- mars:微信开源的一个跨平台长连接信令库,里面还有一个高性能日志库,核心代码用C/C++实现,然后在上层不同平台做少量适配;
- tenforflow lite:机器学习库,C语言实现,可以一份代码编译成不同平台库。
5. 总结
本介绍了JNI概念作用以及JNI产生的北京、JNI提供的接口,以及我们常用到的一些使用了JNI的开源程序。