在 Android Studio 开发 JNI 的时候,总感觉被一些细枝末节所迷惑,比如 NDK,.so 文件,动态库,library ,javah 编译等等,感觉很困惑,所以这次打算撇开细枝末节,只关注最必要的东西。其实 JNI 开发和 Android 关系不大,即使在纯 Java 世界,JNI 也是一项成熟的技术,所以这次撇开 android,单纯从 Java 开发的角度,来走一遍 JNI 开发的流程。
编写 Java 代码,这里使用 IntelliJ IDEA,编写 C/C++ 代码,使用 IntelliJ Clion
第一步:首先在 IntelliJ IDEA 中创建一个 Java 项目,新建一个 JniExample.java 文件,内容如下:
其中 test 表示需要加载的由 C 文件生成的库名,window 平台下是 .dll,ubuntu 平台下是 .so 文件。下面这条语句:
public native String getStringFromC();
表示需要由 C 语言实现,由 Java 调用的方法,和普通Java 方法的区别是在 public 后面多了一个 native 关键字。
写好之后,运行 Build -> Build Project
然后会在 build/class/main/ 目录下看到生成了一个 JniExample.class 文件
这时,可以通过 shell 界面,进入该路径,然后执行如下命令(javah 需要先配置到 环境变量中)
>$ javah JniExample
注意,此时不要加上 .class 后缀,否则会报错。运行结束后,可以看到在该目录下生成了文件JniExample.h
其内容如下:
到这里时,我们进入第二步,打开 IntelliJ Clion,然后新建一个 Demo 项目,并且将刚刚生成的 JniExample.h 拷贝到该项目下,并且将 {$JAVA_HOME}/include 目录下的jni.h以及{$JAVA_HOME}/include/win32 目录下的jni_md.h 也拷贝到该目录下,拷贝完后如下所示:
其中 main.cpp 是自动生成的,可以不用管他。此时会发现,JniExample.h 文件有编译错误,此时只要把第二行的 #include <jni.h> 修改为 #include "jni.h" 即可。然后我们新建一个 JniExample.c 文件,然后把 JniExample.h中的函数
JNIEXPORT jstring JNICALL Java_JniExample_getStringFromC(JNIEnv *, jobject);
拷贝到 JniExample.c即可,如下所示:
编写完成后,生成一个 .dll(win32平台)或者 .so(ubuntu平台),注意 ubuntu 平台下的命名:libxxx.so。下面是 ubuntu 下生成动态链接库的命令
$ gcc JniExample.c JniExample.h jni.h jni_md.h -fPIC -shared -o libtest.so
第三步:将刚才生成的 .dll 或者 .so 文件,拷贝到第一步创建的 java 项目,与 src 相同的目录下,最后在 main 函数中调用,如下所示:
注意:System.loadLibrary("test") 中的 test,对应的 .so 文件为 libtest.so,也就是说不要带上 lib 或者 .so,否则会提示错误 java.lang.UnsatisfiedLinkError: no test.so in java.library.path。
完成上面三步骤后,即可看到输出结果:
小结:在完成上面三步的过程中,有几个坑需要注意:
1、用 javah 将 JniExample.class 文件生成 JniExample.h 文件的过程中,不要带上 .class 后缀
2、将 JniExample.h拷贝到 C 项目后,需要把 #include <jni.h>修改为 #include "jni.h"
3、生成的 .so,注意命名规则 libXXX.so,lib 和 .so 不能省略,在 java 中引用时,不能带上 lib和 .so