西门吹雪原创


小白:java代码中,为什么不能直接写c或c++代码?而oc代码却可以直接写!

西门吹雪:你可以写,只是编译不过而已。

小白:那不就是不能写嘛!

不管是编译到jvm,还是dalvik上运行,java的编译器都不会直接生成执行文件,而只是生成字节码(class文件,或者再转一下的dex文件),这跟c/c++编译器的做法大为不同,而很可能因为这个原因,使得java中不能参杂c/c++代码,而objective-c就可以混编c/c++(都是直接生成执行文件)。

JNI(java native interface,即java本地接口)的出现,可以解决java与c++交互的问题,并且不仅限于c++。

小白:可是,我的android程序,为什么要调用c++的代码呢?

西门吹雪:事出有因,比如已经实现某个功能的是c++代码而非java代码,比如要进行大量的数据运算而c++的执行性能远在java之上,比如要调用一些驱动类的接口,比如...

小白:好了,stop,我接受你的理由。

本文演示java如何调用c++的代码,即jni的使用。

jni层,这里把它分为jni上层,跟jni下层。

jni上层用java实现,jni下层用c/c++实现。

大概的调用关系是这样的:
调用jni层

(1)实现jni上层

随便找一个java类(或者新建一个),然后在里面声明一个native方法,就实现了jni上层。

比如,创建一个as项目,然后有这样的native方法声明:
jni上层-native方法声明

(2)实现jni下层

java代码调用一个native函数时,会调用到c++的哪一个函数?

建立起这个映射,有两个办法。

办法一:使用签名

对于native方法,使用javah,可以抽取出对应的签名,然后c++实现这个签名的函数即可。

可以这样生成签名:
使用javah生成签名

这个.h文件的内容是这样的:
native方法的签名

那一长串就是对应的签名,c++代码实现它,这样java在调用jni上层声明的native函数时,就会调用到这里面来。显然,一旦建立了映射,jni上层与下层就不能再改名字(除非重新签名)。

小白:一大串的,还不给改名,大不人性化了。

西门吹雪:要改名就新增接口吧,或者,使用第二个办法。

办法二:使用RegisterNatives

在jni层(so库)加载时,调用RegisterNatives。

这种办法,没有使用javah生成固定的签名,而是使用RegisterNatives来建立navtive方法与具体的实现的关联,具体实现可随意命名。

在JNI_onLoad函数内,触发RegisterNatives的调用:
JNI_onLoad函数
RegisterNatvies使用

小白:这个看起来好复杂啊!

在解决掉jni上下层的映射问题后,就是natvie方法的具体实现了。

在项目的根目录中,创建一个jni目录,在里面写c/c++代码与编译脚本:
jni实现的文件结构

impl.c的内容:
jni实现代码

脚本Android.mk的内容:
jni编译脚本

执行ndk-build,生成so库:
执行ndk-build
so库
用readelf查看so库

小白:这个Android.mk的内容是什么规则?ndk-build又是什么?so库呢?还有,impl.c里面的类型好奇怪!

西门吹雪:ndk-build是native层的编译工具,so是动态链接库了。jni的数据类型与编译脚本的语法,等等,迟点再讲吧。

(3)调用jni层

在java层调用native方法,比如:
调用native方法

执行这个程序,可以看到输出:
程序输出