JNI是Java Native Interface(Java本地接口),它是为了方便Java调用C,C++等本地代码所封装的一层接口。
NDK是Android所提供的一个工具集合,通过NDK可以在Android中更加方便地通过JNI来访问本地代码,比如C或者C++。NDK还提供了交叉编译器,开发人员只需要简单的修改mk文件就可以生成特定的CPU平台的动态库。
NDK有以下优点:
1.提高代码安全性。由于so库反编译比较困难,因此NDK提高了Android程序的安全性。
2.可以很方便的使用目前已有的C/C++开源库。
3.便于平台间的移植。通过C/C++实现的动态库可以很方便地在其他平台上使用。
4.提高程序在某些特定情形下的执行效率,但是并不能明显提升android程序的性能。
JNI和NDK开发所用到的动态库的格式是以.so为后缀的文件。
JNI开发
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 Homeless");
}
public native String get();
public native void set(String string);
}
2.生成class文件与h文件
javac JniTest.java
javah -classpath . -jni com.xupt.will.jnitest.JniTest
在命令下会生成一个文件。
内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xupt_will_jnitest_JniTest */
#ifndef _Included_com_xupt_will_jnitest_JniTest
#define _Included_com_xupt_will_jnitest_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_xupt_will_jnitest_JniTest
* Method: get
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_xupt_will_jnitest_JniTest_get
(JNIEnv *, jobject);
/*
* Class: com_xupt_will_jnitest_JniTest
* Method: set
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_xupt_will_jnitest_JniTest_set
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
函数名格式:Java_包名_类名_方法名
JNIEnv*:表示一个指向JNI环境的指针,可以通过它来访问JNI提供的接口方法。
JNIEXPORT和JNICALL:它们是JNI所定义的宏,在jni,h这个头文件中可以找到。
extern C :它指定extern C内部的函数采用C语言的命名风格来编译。
3.实现JNI方法
在主目录下创建一个子目录,名称随意,一般选择jni作为这个子目录的名称。然后将生成的.h文件复制到这个子目录中,然后创建test.cpp和test.c两个文件。
test.cpp
#include "com_xupt_will_jnitest_JniTest.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_xupt_will_jnitest_JniTest_get(JNIEnv *env,jobject thiz){
printf("invoke get from C++\n");
return env->NewStringUTF("Hello from JNI !");
}
JNIEXPORT void JNICALL Java_com_xupt_will_jnitest_JniTest_set(JNIEnv *env,jobject thiz,jstring string){
printf("invoke get from C++\n");
char* str = (char*)env->GetStringUTFChars(string,NULL);
printf("%s\n",str);
env->ReleaseStringUTFChars(string,str);
}
test.c
#include "com_xupt_will_jnitest_JniTest.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_xupt_will_jnitest_JniTest_get(JNIEnv *env,jobject thiz){
printf("invoke get from C\n");
return (*env)->NewStringUTF(env,"Hello from JNI !");
}
JNIEXPORT void JNICALL Java_com_xupt_will_jnitest_JniTest_set(JNIEnv *env,jobject thiz,jstring string){
printf("invoke get 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++: env->NewStringUTF("Hello from JNI !");
C: (*env)->ReleaseStringUTFChars(env,string,str);
4.编译库并在Java中调用
//根据C++生成dll库
gcc -shared -I E:/JavaJDK/include -I E:/JavaJDK/include/win32 -fPIC test.cpp -o jni-test.dll
//根据C生成dll库
gcc -shared -I E:/JavaJDK/include -I E:/JavaJDK/include/win32 -fPIC test.c -o jni-test.dll
这里生成so库时需要jni.h文件与jni_md.h文件,所以我们需要-I指出地址,分别在你Java路径的include目录下和include\win32目录下。还有需要注意的是,在windows平台下,这些地址要反斜杠,就是与你直接复制目录的斜杆相反,否则会找不到地址,因为JNI和NDK适合在Linux平台下做开发。
还需要注意的是在Linux及Android平台下,所需要的文件是so库,而在Windows平台下所需要的文件是dll。
5.运行
运行时我们需要指定dll文件所在的位置,否则会报出UnsatisfiedLinkError。我们将生成好的dll文件放在lib目录下,然后指定lib目录。
点击Run > Edit Configurations,然后再VM options中输入
-Djava.library.path=E:\IdeaProjects\JniTest\lib
,即你存放dll文件的位置。
结果如下图所示:
C生成的dll文件
C++生成的dll文件
至此,在IDEA上,使用JNI大体步骤便完成了。
JNI的数据类型和类型签名。
JNI的数据类型包含两种:基本类型和引用类型。
基本类型
JNI类型 | Java类型 | 描述 |
---|---|---|
jboolean | boolean | 无符号8位整型 |
jbyte | byte | 无符号8位整型 |
jchar | char | 无符号16位整型 |
jshort | short | 有符号16位整型 |
jint | int | 32位整型 |
jlong | long | 64位整型 |
jfloat | float | 32位浮点型 |
jdouble | double | 64位浮点型 |
void | void | 无类型 |
引用类型
JNI类型 | Java类型 | 描述 |
---|---|---|
jobject | Object | Object类型 |
jclass | Class | Class类型 |
jstring | String | String类型 |
jobjectArray | Object[] | 对象数组 |
jbooleanArray | boolean[] | boolean数组 |
jbyteArray | byte[] | byte数组 |
jcharArray | char[] | char数组 |
jshortArray | short[] | short数组 |
jintArray | int[] | int数组 |
jlongArray | long[] | long数组 |
jfloatArray | float[] | float数组 |
jdoubleArray | double[] | double数组 |
jthrowable | Throwable | Throwable |
JNI的类型签名则标识了一个特定的Java类型,这个类型既可以是类和方法,也可以是数据类型。
类签名:它采用" L+包名+类型+; "的形式, 只需要将其中的.替换为/即可. 例如java.lang.String, 它的签名为Ljava/lang/String; , 注意末尾的;也是签名一部分。
基本数据签名:
Java类型 | 签名 | Java类型 | 签名 | Java类型 | 签名 |
---|---|---|---|---|---|
boolean | Z | byte | B | char | C |
short | S | int | I | long | J |
float | F | double | D | void | V |
数组签名:
[+类型签名, 例如byte数组. 其类型为byte, 而byte的签名为B, 所以byte数组的签名就是[B.同理可以得到如下的签名对应关系:
char[] [C
float[] [F
double[] [D
long[] [J
String[] [Ljava/lang/String
Object[] [Ljava/lang/Object
如果是多维数组那么就根据数组的维度多少来决定[的多少, 例如int[][]的签名就是[[I,其他情况可以依此类推.
方法签名:参数类型签名)+返回值类型签名。
举个例子, 如下方法: boolean fun1(int a, double b, int[] c). 参数类型的签名连在一起是ID[I, 返回值类型的签名为Z, 所以整个方法的签名就是(ID[I)Z。
例如方法:boolean fun1(int a, String s, int[] c), 那么签名就是(ILjava/lang/String;[I)Z。
为了更好的理解方法的签名格式, 下面再给出两个示例:
int fun1() 对应签名()I
int fun1(int i) 对应签名(I)I