JNI和NDK编程
Java JNI
的本意是Java Native Interface(Java本地接口)
,它是为了方便Java
调用C、C++
等本地代码所封装的一层接口。Java
提供了JNI
专门用于和本地代码交互,这样就增强了Java
语言的本地交互能力。通过Java JNI
,用户可以调用用C
、C++
所编写的本地代码。
NDK
是Android
所提供的一个工具集合,通过NDK
可以在Android
中更加方便地通过JNI
来访问本地代码,比如C
或者C++
。NDK
还提供了交叉编译器,开发人员只需要简单地修改mk
文件就可以生成特定CPU
平台的动态库。
JNI
和NDK
比较适合在Linux
环境下开发。JNI
和NDK
主要用于底层和嵌入式开发,在Android
的应用层开发中使用较少,加上它们本身更加侧重于C
和C++
方面的编程。
JNI的开发流程
JNI
的开发流程有如下几步,首先需要在Java
中声明native
方法,接着用C
或者C++
实现native
方法,然后就可以编译运行了。
1. 在Java中声明native方法
public class JniTest {
static {
System.loadLibrary("jni-test");
}
public static void main(String args[]) {
JniTest jniTest = new JniTest();
System.out.println(jniTest.get());
jniTest.set("hello world");
}
public native String get();
public native void set(String str);
}
声明了两个native
方法:get
和set(String)
,这两个就是需要在JNI
中实现的方法。在JniTest
的头部有一个加载动态库的过程,其中jni-test
是so
库的标识,so
库完整的名称为libjni-test.so
,这是加载so
库的规范。
2. 编译Java源文件得到class文件,然后通过javah命令导出JNI的头文件
C:\Users\WuMeng>E:
E:\>cd E:\Android\Practice\app\src\main\java
E:\Android\Practice\app\src\main\java>javac com/study/wumeng/practice/JniTest.java
E:\Android\Practice\app\src\main\java>javah com.study.wumeng.practice.JniTest
在当前的java
目录下,会生成一个com_study_wumeng_practice_JniTest.h
的头文件,它是javah
命令自动生成的,内容如下所示:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_study_wumeng_practice_JniTest */
#ifndef _Included_com_study_wumeng_practice_JniTest
#define _Included_com_study_wumeng_practice_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_study_wumeng_practice_JniTest
* Method: get
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_study_wumeng_practice_JniTest_get
(JNIEnv *, jobject);
/*
* Class: com_study_wumeng_practice_JniTest
* Method: set
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_study_wumeng_practice_JniTest_set
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
首先函数名的格式遵循如下规则:Java_包名_类名_方法名
。比如Java_com_study_wumeng_practice_JniTest_set(JNIEnv *, jobject, jstring)
, jstring
代表的是set
方法的String
类型的参数。这里只需要知道Java
的String
对应于JNI
的jstring
即可。JNIEXPORT
、JNICALL
、JNIEnv
和jobject
都是JNI
标准中所定义的类型或者宏。
JNIEnv
- 表示一个指向
JNI
环境的指针,可以通过它来访问JNI
提供的接口方法;
jobject
- 表示
Java
对象中的this
JNIEXPORT和JNICALL
- 它们是
JNI
中所定义的宏,可以在jni.h
这个头文件中查找到。
下面的宏定义是必需的,它指定extern "C"
内部的函数采用C语言
的命令风格来编译。否则当JNI
采用C++
来实现时,由于C
和C++
编译过程中对函数的命名风格不同,这将导致JNI
在链接时无法根据函数名查找到具体的函数,那么JNI
调用就无法完成。更多的细节实际上是有关C
和C++
编译时的一些问题。
#ifdef __cplusplus
extern "C"
#endif
3. 实现JNI方法
JNI方法是指Java中声明的native方法,这里可以选择用C++或者C来实现,它们的实现过程是类似的,只有少量的区别,下面分别用C++和C来实现JNI方法。
首先我们右键包,新建一个floder,选择jni,就会在java同级目录下,生成jni文件夹,我们先把生成的.h头文件复制过去。接着创建test.cpp和test.c两个文件
// test.cpp
#include "com_study_wumeng_practice_JniTest.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_study_wumeng_practice_JniTest_get(JNIEnv *env, jobject thiz) {
printf("invoke get in C++\n");
return env->NewStringUTF("Hello from JNI");
}
JNIEXPORT void JNICALL Java_com_study_wumeng_practice_JniTest_set(JNIEnv *env, jobject thiz, jstring string) {
printf("invoke set from C++\n");
char* str = env->GetStringUTFChars(string,NULL);
printf("%s\n",str);
env->ReleaseStringUTFChars(string,str);
}
// test.c
#include "com_study_wumeng_practice_JniTest.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_study_wumeng_practice_JniTest_get(JNIEnv *env, jobject thiz) {
printf("invoke get in c\n");
return (*env)->NewStringUTF(env,"Hello from JNI !");
}
JNIEXPORT void JNICALL Java_com_study_wumeng_practice_JniTest_set(JNIEnv *env, jobject thiz, jstring string) {
printf("invoke set from C\n");
char* str = (char*)(*env)->GetStringUTFChars(env,string,NULL);
printf("%s\n", str);
(*env)->ReleaseStringUTFChars(env,string, str);
}
可以发现,test.cpp和test.c的实现很类似,但是它们对env的操作方式有所不同,因此用C++和C来实现同一个JNI方法,它们的区别主要集中在对env的操作上,其他都是类似的。
C++: env->NewStringUTF("Hello from JNI");
C: (*env)->NewStringUTF(env,"Hello from JNI !");
4. 编译so库并在Java中调用
so库的编译这里采用gcc,切换到jni目录中,对于test.cpp和test.c来说,它们的编译指令如下所示:
C++:gcc -shared -I /usr/lib/jvm/java-7-openjdk-amd64/include -fPIC test.cpp -o libjni-test.so
C:gcc -shared -I /usr/lib/jvm/java-7-openjdk-amd64/include -fPIC test.c -o libjni-test.so
上面的编译命令中,/usr/lib/jvm/java-7-openjdk-amd64是本地的jdk的安装路径,在其他环境编译时将其指向本机的jdk路径即可。而libjni-test.so则是生成的so库的名字,在Java中可以通过如下方式加载: System.loadLibrary("jni-test"),其中so库名字中的"lib"和".so"是不需要明确指出的。so库编译完成后,就可以在Java程序中调用so库了,这里通过Java指令来执行Java程序,切换到主目录,执行如下指令,java -Djava.library.path=../jni com.wumeng.practice.JniTest,其中-Djava.library.path=jni指明了so库的路径。
首先,采用C++产生so库,程序运行后产生的日志如下所示:
invoke get in C++
Hello from JNI!
invoke set from C++
hello world
然后,采用C产生so库,程序运行后产生的日志如下所示:
invoke get from C
Hello from JNI!
invoke set from C
hello world
可以发现,在Java中成功地调用了C/C++的代码,这就是JNI典型的工作流程。