1.JNI概述
Android系统按语言来划分的话由两个世界组成,分别是Java世界和Native世界。那为什么要这么划分呢?Android系统由Java写不好吗?除了性能的之外,最主要的原因就是在Java诞生之前,就有很多程序和库都是由Native语言写的,因此,重复利用这些Native语言编写的库是十分必要的,况且Native语言编写的库具有更好的性能。
这样就产生了一个问题,Java世界的代码要怎么使用Native世界的代码呢,这就需要一个桥梁来将它们连接在一起,而JNI就是这个桥梁。
通过JNI,Java世界的代码就可以访问Native世界的代码,同样的,Native世界的代码也可以访问Java世界的代码。
静态注册
原理:根据函数名来建立 java 方法与 JNI 函数的一一对应关系;
实现流程:
编写 java 代码;
利用 javah 指令生成对应的 .h 文件;
对 .h 中的声明进行实现;
弊端:
编写不方便,JNI 方法名字必须遵循规则且名字很长;
编写过程步骤多,不方便;
程序运行效率低,因为初次调用native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建立对应关系,这个过程比较耗时;
extern "C" JNIEXPORT jstring JNICALL
Java_cn_com_jni_dexshell_MainActivity_stringFromJNI(
JNIEnv *env,
jobject jobj/* this */,jstring id) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello .c_str());
}
动态注册
原理:利用 RegisterNatives 方法来注册 java 方法与 JNI 函数的一一对应关系;
实现流程:
利用结构体 JNINativeMethod 数组记录 java 方法与 JNI 函数的对应关系;
实现 JNI_OnLoad 方法,在加载动态库后,执行动态注册;
调用 FindClass 方法,获取 java 对象;
调用 RegisterNatives 方法,传入 java 对象,以及 JNINativeMethod 数组,以及注册数目完成注册;
优点:
流程更加清晰可控;
效率更高;
JNINativeMethod
在动态注册的过程中使用到了结构体 JNINativeMethod 用于记录 java 方法与 jni 函数的对应关系
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
结构体的第一个参数 name 是java 方法名;
第二个参数 signature 用于描述方法的参数与返回值;
第三个参数 fnPtr 是函数指针,指向 jni 函数;
其中,第二个参数 signature 使用字符串记录方法的参数与返回值,具体格式形如“()V”、“(II)V”,其中分为两部分,括号内表示的是参数,括号右侧表示的是返回值;
数据类型映射
基本数据类型
java 类型 | native 类型 | 域描述符 |
boolean | jboolean | Z |
byte | jbyte | B |
char | jchar | C |
short | jshort | S |
int | jint | I |
long | jlong | J |
float | jfloat | F |
double | jdouble | D |
void | void | V |
2. 数组引用类型
如果是一维数组则遵循下表,如果是二维数组或更高维数组则对应的 native 类型为 jobjectArray,域描述符中使用 ‘[’ 的个数表示维数
java 类型 | native 类型 | 域描述符 |
int[] | jintArray | [I |
float[] | jfloatArray | [f |
byte[] | jbyteArray | [B |
char[] | jcharArray | [C |
short[] | jshortArray | [S |
double[] | jdoubleArray | [D |
long[] | jlongArray | [F |
boolean[] | jbooleanArray | [Z |
3. 对象引用类型
对于其它引用类型,即 java 中的对象,其映射规则为
java 类型 | native 类型 | 域描述符 |
类名(如 Surface) | 通常是 jobject,仅有一种例外,如果 java 类型是 String,则对应的native 类型是 jstring | 以”L”开头,以”;”结尾中间是用”/” 隔开的包及类名(如 Landroid/view/Surface;)如果内部类则使用$连接内部类; |
4. 对象数组引用类型
如果是一维数组则遵循下表,如果是二维数组或更高维数组则对应的 native 类型为 jobjectArray,域描述符中使用 ‘[’ 的个数表示维数
java 类型 | native 类型 | 域描述符 |
类名(如 Surface) | 通常是 jobject,仅有一种例外,如果 java 类型是 String,则对应的native 类型是 jstring | 在对象引用类型的域描述符的基础上在左边添加’[‘字符 |
jni 函数默认参数
在 jni 函数中有两个默认参数
JNIEnv *env, jobject thiz
其中 JNIEnv 指代的是当前 java 环境,可以利用 JNIEnv 可以操作 java 层代码;jobject 指代的是 jni 函数对应的 java native 方法的类实例,如果 java 方法是 static,则代表的是 class 对象;
例:
java
package cn.com.jni.jniautoreg;
public class TestJNI {
public static native void native_init();
public static native String stringFromJNI(String name,String address);
}
native
#include <jni.h>
#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <android/log.h>
#include <memory.h>
#define LOG_TAG "my-jni"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)
#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
using namespace std;
static const char *const kClassJniTest =
"cn/com/jni/jniautoreg/TestJNI";
jstring JNI_stringFromJNI(JNIEnv *env, jobject obj, jstring name,jstring address) {
LOGE("JAVA call JNI stringFromJNI");
string hello = "Hello from JNI";
return env->NewStringUTF(hello.c_str());
}
void JNI_native_init(JNIEnv *env, jobject thizz) {
LOGE(" native_1init");
}
static const JNINativeMethod gMethods[] = {
{
"native_init",
"()V",
(void *) JNI_native_init
},
{
"stringFromJNI",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
(void *) JNI_stringFromJNI
},
};
static int registerNativeMethods(JNIEnv *env, const char *className, const JNINativeMethod *gMethod,
int numMethods) {
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
LOGI(" JNI reg faild:%s",className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethod, numMethods) < 0) {
LOGI(" JNI reg method failed:%s",gMethod->name);
return JNI_FALSE;
}
return JNI_TRUE;
}
static int register_along_jni(JNIEnv *env) {
return registerNativeMethods(env, kClassJniTest, gMethods,
NELEM(gMethods));
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void * /* reserved */) {
JNIEnv *env = NULL;
jint result = -1;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
LOGE("ERROR: JNI version error");
return JNI_ERR;
}
if (register_along_jni(env) == -1) {
LOGE("ERROR: JNI_OnLoad failed");
return JNI_ERR;
}
result = JNI_VERSION_1_6;
return result;
}
注意:
在JNI_OnLoad函数的结尾处,我们一定要有返回值,而且必须是JNI_VERSION_1_4 或 JNI_VERSION_1_6,也就是JNI的版本号,我们一定要返回正确的版本号,否则系统也是无法加载的。