Andoid NDK编程 - 注册native函数
当java代码中执行native的代码时候,首先是通过一定的方法来找到这些native方法。而注册native函数的具体方法的不同,会导致系统在运行时采用不同的方式来寻找这些native方法。JNI有如下两种注册native方法的途径:静态和动态。其中:
静态:先由Java得到本地方法的声明,然后再通过JNI实现该声明方法。
动态:先通过JNI重载JNI_OnLoad()实现本地方法,然后直接在Java中调用本地方法。
静态注册
根据函数名找到对应的JNI函数:Java层调用函数时,会从对应的JNI中寻找该函数,如果没有就会报错,如果存在则会建立一个关联联系,以后在调用时会直接使用这个函数,这部分的操作由虚拟机完成。
静态方法就是根据函数名来遍历java和jni函数之间的关联,而且要求jni层函数的名字必须遵循
特定的格式。
具体的实现很简单,首先在java代码中声明native函数,然后通过javah来生成native函数的具体形式,接下来在JNI代码中实现这些函数即可。
看个例子就更明了了:
Java层:
1 | static { |
接下来通过javah来产生jni代码声明:
假设你的java文件的包名是com.jni.samle 而类名是JniSample。
1 | javah -d ./jni/ -classpath /Users/YOUR_NAME/Library/Android/sdk/platforms/android-21/android.jar:../../build/intermediates/classes/debug/ com.jni.samle.JniSample |
然后就会得到一个JNI的.h文件,里面包含这几个native函数的声明,观察一下文件名以及函数名,会有一定的规律,我会在下一篇文章中对此做一详细介绍,在此不再赘述。
最后实现jni层的native方法即可。
动态注册
对Java程序员来说,可能我们总是会遵循:1.编写带有native方法的Java类;—->2.使用javah命令生成.h头文件;—->3.编写代码实现头文件中的方法,这样的标准流程,但也许有人无法忍受那“丑陋”的方法名称,所以我们可以采用动态注册方法,也就是通过RegisterNatives方法把c/c++中的方法隐射到Java中的native方法,而无需遵循特定的方法命名格式。
JNI 允许你提供一个函数映射表,注册给Jave虚拟机,这样Jvm就可以用函数映射表来调用相应的函数,
就可以不必通过函数名来查找需要调用的函数了。
Java与JNI通过JNINativeMethod的结构来建立联系,它在jni.h中被定义,其结构内容如下:
1 | typedef struct { |
第一个变量name是Java中函数的名字。
第二个变量signature,用字符串是描述了函数的参数和返回值
第三个变量fnPtr是函数指针,指向C函数。
当java通过System.loadLibrary加载完JNI动态库后,紧接着会查找一个JNI_OnLoad的函数,如果有,就调用它, 而动态注册的工作就是在这里完成的。
一起来看一下具体的实现方法:
Java code:
比较简单,仅仅是加载so库。
1 | static { |
JNI code:
在JNI中实现1
jint JNI_OnLoad(JavaVM* vm, void* reserved)
并且在这个函数里面去动态的注册native方法,完整的参考代码如下:
1 |
|
比较
下面我们来比较一下这两种不同的实现方法。
首先是静态方法:
优点
实现起来比较简单,直接用javah就能将Java代码中的native函数的声明转化为native代码的函数。
缺点在于:
javah生成的jni层函数特别长;
初次调用native函数时要根据名字搜索对应的jni层函数来建立关联联系,这样影响效率。
而动态方法的优缺点刚好和静态方法相反。
通过上面的介绍我们发现,尽管静态注册实现起来比较简单,但是会导致效率相对来说比较低。
所以在JNI层提供JNI_OnLoad是一个推荐的做法。如果不提供JNI_OnLoad,那么JNI函数的命名就需要符合规范,并且需要导出该函数。这样做很容易被别人反编译。相反,如果提供JNI_OnLoad,就可以在里面自己注册JNI函数给Dalvik VM,这样JNI函数的命名就可以按照自己的习惯了,而且也不用导出。
一个巧妙的合作
在实际的应用中,我们可以巧妙的将静态注册和动态注册结合起来:也就是在java代码中仍然声明一个native函数,但是这个函数仅仅是用来去触发在JNI层的native函数的动态注册。说的有些绕,看看代码就明白了:
Java层:
1 | static { |
JNI层:
通过javah生成java层声明的native函数的文件,并且在实现代码中去动态注册JNI函数:
1 | JNIEXPORT void JNICALL Java_com_zhixin_jni_JniSample_registerNatives |