JNI
JNI是Java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以调用Java代码。JNI 是本地编程接口,Java和C/C++互相通过的接口。Java通过C/C++使用本地的代码的一个关键性原因在于C/C++代码的高效性。
动态链接库
Android NDK动态链接库(so文件)
程序编制一般需经编辑、编译、连接、加载和运行几个步骤。在我们的应用中,有一些公共代码是需要反复使用,就把这些代码编译为“库”文件;在连接步骤中,连接器将从库文件取得所需的代码,复制到生成的可执行文件中。这种库称为静态库,其特点是可执行文件中包含了库代码的一份完整拷贝;缺点就是被多次使用就会有多份冗余拷贝。
为了克服这个缺点可以采用动态连接库。这个时候连接器仅仅是在可执行文件中打上标志,说明需要使用哪些动态连接库;当运行程序时,加载器根据这些标志把所需的动态连接库加载到内存。
另外在当前的编程环境中,一般都提供方法让程序在运行的时候把某个特定的动态连接库加载并运行,也可以将其卸载(例如Win32的LoadLibrary()&FreeLibrary()和Posix的dlopen()&dlclose())。这个功能被广泛地用于在程序运行时刻更新某些功能模块或者是程序外观。
so文件
一种CPU架构 = 一种对应的ABI参数 = 一种对应类型的SO库
安卓中的so文件是什么?Android中用到的so文件是一个C++的函数库。在Android的JNI中,要先将相应的C语言打包成so库,然后导入到lib文件夹中供Java调用。
早期的Android系统几乎只支持ARM的CPU架构,不过现在至少支持以下七种不同的CPU架构:ARMv5,ARMv7,x86,MIPS,ARMv8,MIPS64和x86_64。每一种CPU类型都对应一种ABI(Application Binary Interface),这7种CPU类型对应的SO库的文件夹名是:armeabi,armeabi-v7a,x86,mips,arm64-v8a,mips64,x86_64。
不同类型的移动设备在运行APP时,需要加载自己支持的类型的SO库,一般情况下,不需要开发者自己去判断ABI,Android系统在安装APK的时候,不会安装APK里面全部的SO库文件,而是会根据当前CPU类型支持的ABI,从APK里面拷贝最合适的SO库,并保存在APP的内部存储路径的libs下面。(这里说一般情况,是因为有例外的情况存在,比如我们动态加载外部的SO库的时候,就需要自己判断ABI类型了。
NDK
NDK是一系列工具的集合。它提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和Java应用一起打包成Apk。这些工具对开发者的帮助是巨大的。它集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。它可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。
NDK的使用主要有两种形式:一种是拿到第三方的so文件,我们通过JNI封装其提供的Native接口给Java层使用;另一种是自己使用 C/C++编写底层代码,然后编译生成so文件,接着重复第一种的做法。
C/C++ — so文件—Native接口—JNI—Java层
翻译:将C/C++打包成so库,然后导入到lib文件夹中供Java调用(so文件是一个C/C++函数库)。获取so文件,JNI封装其提供的Native接口给Java层使用,Java通过JNI调用本地的C/C++代码。
为什么使用NDK?
1、代码的保护。由于Apk的Java层代码很容易被反编译,而C/C++库反汇难度较大。
2、可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。
3、提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。
4、便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。
JNI和NDK之间的区别
JNI是一套用于帮助Java和Native Code互操作的机制,在DVM有DVM的实现,在我们平常的PC上运行的JVM,也同样有它的实现。而NDK则只是一套工具,它可以帮助开发者在Android开发中,使用JNI机制而已。Native Code可以是C Code,也可以是C++Code。
从工具上说,NDK其实多了一个把.so和.apk打包的工具,而JNI开发并没有打包,只是把.so文件放到文件系统的特定位置。
从编译库说,NDK开发C/C++只能能使用NDK自带的有限的头文件,而使用JNI则可以使用文件系统中带的头文件。
从编写方式说,它们一样。
JNI书写步骤
编写带有native声明的方法的java类
使用javac命令编译所编写的java类
然后使用javah + java类名生成扩展名为h的头文件
使用C/C++实现本地方法
将C/C++编写的文件生成动态连接库
本地方法 Native Method
什么是Native Method
简单地讲,一个Native Method就是一个Java调用非Java代码的接口。一个Native Method是这样一个Java的方法:该方法的实现由非Java语言实现,比如C。这个特征并非Java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数。
在定义一个Native Method时,并不提供实现体(有些像定义一个Java Interface),因为其实现体是由非Java语言在外面实现的。下面给了一个示例:
public class INatives {
native public void Native1( int x ) ;
native static public long Native2() ;
native synchronized private float Native3( Object o ) ;
native void Native4( int[] ary ) throws Exception ;
}
这些方法的声明描述了一些非Java代码在这些Java代码里看起来像什么样子。
一个Native Method方法可以返回任何Java类型,包括非基本类型,而且同样可以进行异常控制。这些方法的实现体可以制一个异常并且将其抛出,这一点与Java的方法非常相似。当一个Native Method接收到一些非基本类型时如Object或一个整型数组时,这个方法可以访问这非些基本型的内部,但是这将使这个Ntive方法依赖于你所访问的Java类的实现。有一点要牢牢记住:我们可以在一个Native Method的本地实现中访问所有的Java特性,但是这要依赖于你所访问的Java特性的实现,而且这样做远远不如在Java语言中使用那些特性方便和容易。
本地方法非常有用,因为它有效地扩充了JVM。事实上,我们所写的Java代码已经用到了本地方法,在Sun的Java的并发(多线程)的机制实现中,许多与操作系统的接触点都用到了本地方法,这使得Java程序能够超越Java运行时的界限。有了本地方法,Java程序可以做任何应用层次的任务。
为什么要使用Native Method
Java使用起来非常方便,然而有些层次的任务用Java实现起来不容易,或者我们对程序的效率很在意时,问题就来了。
- 与Java环境外交互:
有时Java应用需要与Java外面的环境交互。这是本地方法存在的主要原因,你可以想想java需要与一些底层系统如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解Java应用之外的繁琐的细节。
- 与操作系统交互:
JVM支持着Java语言本身和运行时库,它是Java程序赖以生存的平台,它由一个解释器(解释字节码)和一些连接到本地代码的库组成。然而不管怎 样,它毕竟不是一个完整的系统,它经常依赖于一些底层(underneath在下面的)系统的支持。这些底层系统常常是强大的操作系统。通过使用本地方法,我们得以用Java实现了Jre的与底层系统的交互,甚至JVM的一些部分就是用C写的,还有,如果我们要使用一些Java语言本身没有提供封装的操作系统的特性时,我们也需要使用本地方法。
JVM怎样使Native Method跑起来
我们知道,当一个类第一次被使用到时,这个类的字节码会被加载到内存,并且只会回载一次。在这个被加载的字节码的入口维持着一个该类所有方法描述符的List,这些方法描述符包含这样一些信息:方法代码存于何处,它有哪些参数,方法的描述符(public之类)等等。
如果一个方法描述符内有Native,这个描述符块将有一个指向该方法的实现的指针。这些实现在一些DLL文件(动态链接库)内,但是它们会被操作系统加载到Java程序的地址空间。当一个带有本地方法的类被加载时,其相关的DLL并未被加载,因此指向方法实现的指针并不会被设置。当本地方法被调用之前,这些DLL才会被加载,这是通过调用Java.System.loadLibrary()
实现的。
最后需要提示的是,使用本地方法是有开销的,它丧失了Java的很多好处。如果别无选择,我们可以选择使用本地方法。