或许你知道了jni的简单调用,其实不算什么百度谷歌一大把,虽然这些jni绝大多数情况下都不会让我们安卓工程师来弄,毕竟还是有点难,但是我们还是得打破砂锅知道为什么这样干吧,至少也让我们知道调用流程和数据类型以及处理方法,或许你会有不一样的发现。
其实总的来说从java的角度来看.h文件就是java中的interface(插座),然后.c/.cpp文件呢就是实现类罢了,然后数据类型和java还是有点出入我们还是得了解下(妈蛋,天气真热不适合生存了)。
今天也给出一个JNI动态注册native方法的例子,如图:
JNI 开发流程主要分为以下步骤:
编写声明了 native 方法的 Java 类
将 Java 源代码编译成 class 字节码文件
用 javah -jni 命令生成.h头文件(javah 是 jdk 自带的一个命令,-jni 参数表示将 class 中用native 声明的函数生成 JNI 规则的函数)
用本地代码(c/c++)实现.h头文件中的函数
将(c/c++)文件编译成动态库(Windows:*.dll,linux/unix:*.so,mac os x:*.jnilib)
拷贝动态库至本地库目录下,并运行 Java 程序(System.loadLibrary(“xxx”))
我们安卓开发工程师显然只需要编写native的java类,然后clean下编译器知道把我们的java编译成了class文件,但是我们必须知道是调用了javac命令,javah jni命令我们还是得执行,其他的工作就差不多了,不管是什么编译器,反正jni步骤就这样。
JVM 查找 native 方法JVM 查找 native 方法有两种方式:
按照 JNI 规范的命名规则
调用 JNI 提供的 RegisterNatives 函数,将本地函数注册到 JVM 中。
是不是感到特别的意外,jni还能够利用RegisterNatives 函数查找native方法,其实我也才刚刚知道有这方法,因为要根据包名类名方法名的规范来写是很傻逼的,哈哈,有的人或许觉得这样很直观。
严格按照命名规则实现native方法的调用我们还是按步骤来说吧,先来解读JNI规范的命名规则:
* 我们先来看下.h文件 *
#include #ifndef _Included_com_losileeya_jnimaster_JNIUtils #define _Included_com_losileeya_jnimaster_JNIUtils #ifdef __cplusplus extern { #endif JNIEXPORT jstring JNICALL Java_com_losileeya_jnimaster_JNIUtils_say (JNIEnv *, jclass,jstring); #ifdef __cplusplus } #endif #endif我们再来看下Linux 下jni_md.h头文件内容:
#ifndef _JAVASOFT_JNI_MD_H_ #define _JAVASOFT_JNI_MD_H_ #define JNIEXPORT #define JNIIMPORT #define JNICALL typedef int jint; #ifdef _LP64 /* 64-bit Solaris */ typedef long jlong; #else typedef long long jlong; #endif typedef signed char jbyte; #endif从上面我们可以看出文件以#ifndef开始然后#endif 结尾,不会C的话是不是看起来有点蛋疼,#号呢代表宏,这里来普及一下宏的使用和定义。
#define 标识符 字符串
其中,#表示这是一条预处理命令;#define为宏定义命令;“标识符”为宏定义的宏名;“字符串”可以上常数、表达式、格式串等。
举例如下:
#define PI 3.14 void main() { printf(, PI); }条件编译的命令
#ifndef def 语句1 # else 语句2 # endif 表示如果def在前面进行了宏定义那么就编译语句1(语句2不编译),否则编译语句2(语句1不编译)再看我们.h文件并没有else,所以我们就编译宏定义的本地方法类(com_losileeya_jnimaster_JNIUtils),你突然就会发现我们的宏是我们的native类,然后把包名点类名的点改成了下划线,然后你会发现多了_Included不要多想,就是included关键字加个下划线,这样我们就给本地类进行了宏定义。然后
#ifdef __cplusplus
extern “C” {#endif
这是说明如果宏定义了c++,并且里面有c我们还是支持c的,并且c代码写extern “C” {}里面。可以看出#endif对应上面的#ifdef-cplusplus,#ifdef-cplusplus对应最后的#endif, #ifdef与#endif总是一一对应的,表明条件编译开始和结束。
JNIEXPORT 和 JNICALL 的作用
因为安卓是跑在 Linux 下的,所以从 Linux 下的jni_md.h头文件可以看出来,JNIEXPORT 和 JNICALL 是一个空定义,所以在 Linux 下 JNI 函数声明可以省略这两个宏。
再来看我们的方法:
函数命名规则为:Java_类全路径_方法名。
如:Java_com_losileeya_jnimaster_JNIUtils_say,其中Java_是函数的前缀,com_losileeya_jnimaster_JNIUtils是类名,say是方法名,它们之间用 _(下划线) 连接。