JNI 全称 Java Native Interface,它是为了方便 Java 调用 C、C++ 等本地代码所封装的一层接口。
NDK 是 Android 所提供的一个工具集合,通过 NDK 可以在 Android 中更加方便的用过 JNI 来访问本地代码。此外 NDK 还提供了交叉编译器,只需简单的配置即可生成特定 CPU 平台的动态库。
使用 NDK 的好处:
在 Java 中声明 native 方法。
public class JniTest { static { System.loadLibrary("jni-test"); // 加载动态库,库文件完整名为 libjni-test.so } ... public native String get(); public native void set(String str); }
编译 Java 源文件得到 class 文件,然后通过 javah
命令导出 JNI 头文件。
javac ./com/liyu/JniTest.java javah com.liyu.JniTest
在当前目录自动产生一个 com_liyu_JniTest.h
的头文件。
本流程结合书籍和网络资料,当然也可以直接通过 Android Studio 新建 Project 时勾选 Include C++ support
,这时 Android Studio 会自动生成一个 JNI 示例项目。
创建一个 Android 项目,并声明所需的 native 方法
package com.example.liyu.studytest; public class JniTest { static { System.loadLibrary("jni-test"); } public native String get(); public native void set(String str); }
右键 app -> New -> Folder -> JNI Folder 创建 jni 目录
通过 javah
命令生成头文件至 jni 目录
javah -d src/main/jni/ -classpath build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/ com.example.liyu.studytest.JniTest
实现所声明的 native 方法
创建 test.cpp 实现头文件中的方法
#include <jni.h> #include <stdio.h> #ifdef __cplusplus extern "C" { #endif jstring Java_com_example_liyu_studytest_JniTest_get(JNIEnv *env, jobject thiz) { printf("invoke get in c++/n"); callJavaMethod(env, thiz); return env->NewStringUTF("Hello from JNI in libjni-test.so !"); } void Java_com_example_liyu_studytest_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); } #ifdef __cplusplus } #endif
创建 Application.mk 文件
APP_MODULES := jni-test APP_ABI :=all
创建 Android.mk 文件
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := jni-test LOCAL_SRC_FILES := test.cpp include $(BUILD_SHARED_LIBRARY)
切换到 jni
目录下,执行 ndk-build
命令,即可生成对应平台的 so 库。
app/src/main
中新建一个名为 jniLibs
的目录,将生成的 so 库复制进去即可。( jniLibs
的目录是 Android Studio 所能识别的默认目录,也可修改)
以上为手动创建的步骤,其实还可以使用 Android Studio 来自动编译产生 so 库,需要修改 build.gradle
:
android { ... defaultConfig { ... ndk { moduleName "jni-test" } } sourceSets.main { jni.srcDirs 'src/main/jni' } }
如果只打包特定平台的 so 库,也需要修改 build.gradle
:
android { ... defaultConfig { ... ndk { abiFilters "armeabi", "armeabi-v7a" } } }
JNI 类型 | Java 类型 | 描述 |
---|---|---|
jbyte | byte | 有符号,8 位,整型 |
jshort | short | 有符号,16 位,整型 |
jint | int | 有符号,32 位,整型 |
jlong | long | 有符号,64 位,整型 |
jfloat | float | 32 位,浮点型 |
jdouble | double | 64 位,浮点型 |
jboolean | boolean | 无符号,8 位,整型 |
jchar | char | 无符号,16 位,整型 |
void | void | 无类型 |
JNI 类型 | Java 类型 | 描述 |
---|---|---|
jclass | Class | 类 |
jobject | Object | Java 对象 |
jstring | String | 字符串 |
jobjectArray | Object[] | 对象数组 |
jbyteArray | byte[] | byte 数组 |
jshortArray | short[] | short 数组 |
jintArray | int[] | int 数组 |
jlongArray | long[] | long 数组 |
jfloatArray | float[] | float 数组 |
jdoubleArray | double[] | double 数组 |
jbooleanArray | boolean[] | boolean 数组 |
jcharArray | char[] | char 数组 |
jthrowable | Throwable | Throwable |
JNI 的类型签名标识了一个特定的 Java 类型,这个类型可以是类、方法或数据类型。
采用 L+包名+类名+;
的形式,且将包名中的 .
替换为 /
。
如: java.lang.String
的签名为 Ljava/lang/String;
Java 类型 | 签名 |
---|---|
byte | B |
short | S |
int | I |
long | J |
float | F |
double | D |
boolean | Z |
char | C |
void | V |
[+类型签名
如: String[]
的签名为 [Ljava/lang/String;
, byte[]
的签名为 [B
n个[+类型签名
如: int[][]
的签名为 [[I
方法的签名为: (各参数类型签名) + 返回值类型签名
。
int func1()
的签名为 ()I
void func2(int i)
的签名为 (I)V
boolean func3(int a, double b, String[] c)
的签名为 (ID[Ljava/lang/String;)Z
JNI 调用 Java 静态方法的流程是先找到类,然后根据方法名找到方法的 id,最后就可以调用这个方法了。调用非静态方法时需要先构造出类的对象后才能调用它。
下面是一个 JNI 调用 Java 静态方法的示例:
void callJavaMethod(JNIEnv *env, jobject thiz) { // 获取 jclass jclass clazz = env->GetObjectClass(thiz); // 根据类、方法名以及方法签名获取静态方法的 ID // 非静态方法的 ID 通过 GetMethodID 获取,参数一致 jmethodID id = env->GetStaticMethodID(clazz, "methodCalledByJni", "(Ljava/lang/String;)V"); if (id == NULL) { printf("find method methodCalledByJni error!"); } // String 参数需要通过 NewStringUTF 生成 jstring 使用,否则会报错 jstring msg = env->NewStringUTF("msg send by callJavaMethod in test.cpp."); // 非静态方法通过 CallVoidMethod 调用,参数一致 env->CallStaticVoidMethod(clazz, id, msg); }