1. JNI 和 NDK 概述
- JNI 即 Java Native Interface Java 本地接口,是为了方便 Java 调用 C、C++ 等本地代码所封装的一层接口
- NDK 是 Android 所提供的一个工具集合,通过 NDK 可以在 Android 中更加方便地通过 JNI 来访问本地代码
- NDK 还提供了交叉编译器,开发人员只需要简单地修改 mk 文件就可以生产特定 CPU 平台的 so 动态库
- 使用 NDK 的好处:
- 提高代码的安全性。由于 so 库反编译比较困难,因此 NDK 提高了 Android 程序的安全性
- 可以很方便地使用目前已有的 C/C++ 开源库
- 便于平台间的移植。通过 C/C++ 实现的动态库可以很方便地在其他平台上使用
- 提高程序在某些特性情形下的执行效率,但是并不能明显提升 Android 程序的性能
2. JNI 开发流程概述
在 Java 中声明 native 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// Demo
package com.szy;
import java.lang.System;
public class JniTest {
static {
System.loadLibrary("jni-test"); // 加载 so 库的规范,前缀 lib 和 后缀 .so 是不需要明确指出的
}
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);
}编译 Java 源文件得到 class 文件,然后通过 javah 命令导出 JNI 的头文件
1
2javac com/szy/JniTest.java
javah com.szy.JniTest1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// 通过 javah 命令会在当前目录下产生一个 com_szy_JniTest.h 的头文件
/* DO NOT EDIT THIS FILE - it is machine generated */
/* Header for class com_szy_JniTest */
// 此宏定义的含义:指定 extern "C" 内部的函数采用 C 语言的命名风格来编译
// 否则当 JNI 采用 C++ 来实现时,由于 C 和 C++ 编译过程中对函数的命名风格不同
// 这将导致 JNI 在链接时无法根据函数名查找到具体的函数,那么 JNI 调用就无法完成
extern "C" {
/*
* Class: com_szy_JniTest
* Method: get
* Signature: () Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_szy_JniTest_get
(JNIEnv *, jobject);
/*
* Class: com_szy_JniTest
* Method: set
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_szy_JniTest_set
(JNIEnv *, jobject, jstring);
}- 函数名的格式:
Java_包名_类名_方法名
- JNIEnv*: 表示一个指向 JNI 环境的指针,可以通过它来访问 JNI 提供的接口方法
- jobject: 表示 Java 对象中的 this
- JNIEXPORT 和 JNICALL: 它们是 JNI 中所定义的宏,可以在 jni.h 这个头文件中查找到
- 函数名的格式:
实现 JNI 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// test.cpp
#include "com_szy_JniTest.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_szy_JniTest_get(JNIEnv *env, jobject thiz) {
printf("invoke get in C++\n");
return env->NewStringUTF("Hello from JNI");
}
JNIEXPORT void JNICALL Java_com_szy_JniTest_set(JNIEnv *env, jobject thiz, jstring string) {
printf("invoke set from C++\n");
char* str = (char*) env -> GetStringUTFChars(string, NULL);
printf("%s\n", str);
env -> ReleaseStringUTFChars(string, str);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// test.c
#include "com_szy_JniTest.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_szy_JniTest_get(JNIEnv *env, jobject thiz) {
printf("invoke get from C\n");
return (*env) -> NewStringUTF(env, "Hello from JNI");
}
JNIEXPORT void JNICALL Java_com_szy_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);
}编译 so 库并在 Java 中调用
1
2
3
4
5
6// 这里采用 gcc 编译 so 库
C++: gcc -shared -I 本地的 jdk 安装路径/include -fPIC test.cpp -o libjni-test.so
C: gcc -shared -I 本地的 jdk 安装路径/include -fPIC test.c -o libjni-test.so
// 这里通过 Java 命令执行 Java 程序
java -Djava.library.path=jni com.szy.JniTest1
2
3
4
5
6
7
8
9
10
11// 采用 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