JNI 全称 Java Native Interface,Java 本地化接口。即 Java 可以通过 JNI 调用 C/C++ 代码。
它有如下特点:
注意:如果直接运行发现so库未打包到APK,可以尝试使用使用build,然后安装,
android {
defaultConfig {
//配置编译平台
externalNativeBuild {
cmake {
cppFlags ""
//生成 so 的平台,如下表示只生成 armeabi-v7a 一个平台,目前市面上绝大多数机器都是该平台
abiFilters 'armeabi-v7a'
//表示生成如下五种平台
//abiFilters "armeabi","armeabi-v7a" , "arm64-v8a", "x86", "x86_64"
}
}
}
externalNativeBuild {
//指定CMake路径
cmake {
path "CMakeLists.txt"
}
}
}
Java 数据类型和 C/C++ 数据类型存在映射关系,映射关系都定义在jni.h
中,并且如果是 Java 对象的映射还会区分了 C 和 C++的不同环境。
jobject, jclass, jstring, jarray
等都是继承自_jobject
类jobject, jclass, jstring, jarray
等是空类型指针typedef void* jobject
#ifndef JNI_H_
#define JNI_H_
#include
#include
//基本数据类型
typedef uint8_t jboolean; /* unsigned 8 bits */
typedef int8_t jbyte; /* signed 8 bits */
typedef uint16_t jchar; /* unsigned 16 bits */
typedef int16_t jshort; /* signed 16 bits */
typedef int32_t jint; /* signed 32 bits */
typedef int64_t jlong; /* signed 64 bits */
typedef float jfloat; /* 32-bit IEEE 754 */
typedef double jdouble; /* 64-bit IEEE 754 */
typedef jint jsize;
//对象类型
#ifdef __cplusplus
//C++语言环境
typedef _jobject* jobject;
typedef _jclass* jclass;
typedef _jstring* jstring;
typedef _jarray* jarray;
typedef _jobjectArray* jobjectArray;
typedef _jbooleanArray* jbooleanArray;
typedef _jbyteArray* jbyteArray;
typedef _jcharArray* jcharArray;
typedef _jshortArray* jshortArray;
typedef _jintArray* jintArray;
typedef _jlongArray* jlongArray;
typedef _jfloatArray* jfloatArray;
typedef _jdoubleArray* jdoubleArray;
typedef _jthrowable* jthrowable;
typedef _jobject* jweak;
#else
//C语言环境
typedef void* jobject;
typedef jobject jclass;
typedef jobject jstring;
typedef jobject jarray;
typedef jarray jobjectArray;
typedef jarray jbooleanArray;
typedef jarray jbyteArray;
typedef jarray jcharArray;
typedef jarray jshortArray;
typedef jarray jintArray;
typedef jarray jlongArray;
typedef jarray jfloatArray;
typedef jarray jdoubleArray;
typedef jobject jthrowable;
typedef jobject jweak;
#endif
下面是基本的数据类型的描述符,除了 boolean 和 long 类型分别是 Z 和 J 外,其他的描述符对应的都是Java类型名的大写首字母,void 的描述符为 V
基本类型 | 描述符 |
---|---|
byte | B |
char | C |
double | D |
float | F |
int | I |
long | J |
short | S |
boolean | Z |
void | V |
一般引用类型描述符的规则如L + 类描述符 + ;
,注意不要丢掉;
,例如String类型的描述符为:Ljava/lang/String;
。数组的描述符特殊一点,有多少级数组就有多少个[
,数组的类型为类时,则有分号,为基本类型时没有分号[ + 其类型的域描述符
引用类型 | 描述符 |
---|---|
String | Ljava/lang/String; |
Object[] | [Ljava/lang/Object; |
int[] | [I |
double[] | [D |
int[][] | [[I |
double[][] | [[D |
方法描述符格式为:(参数……)返回类型
,示例如下:
Java方法 | 方法描述符 |
---|---|
void init() | ()V |
String getString() | ()Ljava/lang/String; |
int sum(int a, int b) | (II)I |
void main(String[] args) | ([Ljava/lang/String;)V |
JNIEnv 是 jni.h 文件最重要的部分,它的本质是指向函数表指针的指针(JavaVM也是),函数表里面定义了很多 JNI 函数,同时它也是区分 C 和 C++环境的(由上面介绍描述符时也可以看到),在 C 语言环境中,JNIEnv 是strut JNINativeInterface*
的指针别名,而在C++中是_JNIEnv
的指针别名
struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv; //C++中的 JNIEnv 类型
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv; //C语言的 JNIEnv 类型
typedef const struct JNIInvokeInterface* JavaVM;
#endif
JavaVM* jvm->GetEnv()
方法,该方法会返回当前线程所在的 JNIEnv*
;//在java目录中执行
javac -h <输出目录> <源文件>(例如 .\com\whf\jnitestdemo\opengl\JniRendererInterface.java)
原理:根据函数名建立 Java 方法和native函数的一一对应关系:
javac -h
工具生成对应的头文件System.loadLibrary
加载 so 库即可;//Java方法
public native String stringFromJNI();
public native String stringFromJNI(String src);
public native int intFrom_JNI(String src);
//native方法
extern "C"
JNIEXPORT jstring JNICALL
Java_com_whf_jnitestdemo_JniTest_stringFromJNI__(JNIEnv *env, jobject instance) {
// TODO
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_whf_jnitestdemo_JniTest_stringFromJNI__Ljava_lang_String_2(JNIEnv *env, jobject instance, jstring src) {
// TODO
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_whf_jnitestdemo_JniTest_intFrom_1JNI(JNIEnv *env, jobject instance, jstring src_) {
// TODO
}
可以看出 JNI 的调用函数的定义是按照一定规则命名的:JNIEXPORT 返回值 JNICALL Java_全路径类名_方法名_参数签名(JNIEnv* , jclass, 其它参数);
_
要用 _1
连接;__
连接(所有都要);/
改为下划线 _
连接,分号 ;
改为 _2
连接,左方括号 [
改为 _3
连接;优点
:
缺点
:
原理:直接告诉 native 方法其在JNI 中对应函数的指针,通过使用 JNINativeMethod 结构来保存 Java native 方法和 JNI 函数关联关系(Android源码中也大多采用动态注册,注册对照表在AndroidRuntime.cpp
类中),步骤:
JNINativeMethod
保存Java native方法和 JNI函数的对应关系;registerNatives(JNIEnv* env)
注册类的所有本地方法;JNI_OnLoad
方法中调用注册方法;System.loadLibrary
加载完JNI动态库之后,会调用 JNI_OnLoad
函数,完成动态注册;//JNINativeMethod结构体
typedef struct {
const char* name; //Java中native方法的名字
const char* signature; //Java中native方法的描述符
void* fnPtr; //对应JNI函数的指针
} JNINativeMethod;
/**
* @param clazz java类名,通过 FindClass 获取
* @param methods JNINativeMethod 结构体指针
* @param nMethods 方法个数
*/
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
//JNI_OnLoad
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved);
使用示例如下:
//需要动态注册的方法
public class JniInterface {
public native String dynamicRegister();
}
#include
#include
#include
#include "android_log.h"
#ifdef __cplusplus
extern "C" {
#endif
//定义类名
static const char *className = "com/whf/jnitestdemo/JniInterface";
//定义对应Java native方法的 C++ 函数,函数名可以随意命名
static jstring sayHello(JNIEnv *env, jobject) {
LOGI("hello, this is native log.");
const char *hello = "Hello from C++.";
return env->NewStringUTF(hello);
}
/*
* 定义函数映射表(是一个数组,可以同时定义多个函数的映射)
* 参数1:Java 方法名
* 参数2:方法描述符,也就是签名
* 参数3:C++定义对应 Java native方法的函数名
*/
static JNINativeMethod jni_Methods_table[] = {
{"dynamicRegister", "()Ljava/lang/String;", (void *) sayHello},
};
//根据函数映射表注册函数
static int registerNativeMethods(JNIEnv *env, const char *className,
const JNINativeMethod *gMethods, int numMethods) {
jclass clazz;
LOGI("Registering %s natives\n", className);
clazz = (env)->FindClass(className);
if (clazz == NULL) {
LOGE("Native registration unable to find class '%s'\n", className);
return JNI_ERR;
}
if ((env)->RegisterNatives(clazz, gMethods, numMethods) < 0) {
LOGE("Register natives failed for '%s'\n", className);
return JNI_ERR;
}
//删除本地引用
(env)->DeleteLocalRef(clazz);
return JNI_OK;
}
//在Java中通过System.loadLibrary加载完JNI动态库之后,会自动调用JNI_OnLoad函数,完成动态注册;
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
LOGI("call JNI_OnLoad");
JNIEnv *env = NULL;
//判断 JNI 版本是否为JNI_VERSION_1_4
if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
return JNI_EVERSION;
}
registerNativeMethods(env, className, jni_Methods_table,
sizeof(jni_Methods_table) / sizeof(JNINativeMethod));
return JNI_VERSION_1_4;
}
#ifdef __cplusplus
}
#endif