JNI(Java Native Interface) Java本地接口, Java代码使用JNI调用外部的本地C/C++代码,同样,外部的C/C++ 代码可以调用Java代码。
NDK与JNI区别 :
(1)NDK:NDK是Google开发的一套开发和编译工具集, 主要用于Android的JNI开发;
(2)JNI : JNI是一套编程接口, 用来实现Java代码与本地的C/C++代码进行交互。
一、JNI编程步骤:
(1)声明native方法:在Java代码中声明 native method()方法;
(2)实现JNI的C/C++方法:在JNI层实现Java中声明的native方法,并在JNI层,将C/C++代码编译成动态库;
(3)加载动态库 : 在Java代码中的静态代码块中加载JNI编译后的动态共享库。
二、JNI开发方式比较
静态注册 | (1)书写不便,JNI层函数名特别长; (2)初次调用native函数时要根据函数名搜索对应的JNI层函数来建立关联关系,这样很影响效率。 |
动态注册 | (1)书写方便; (2)使用用函数映射表来调用相应的函数,效率较高。 |
三、动态注册
1、关键接口介绍
(1)System.loadLibrary()函数
Java程序通过System.loadLibrary加载完JNI动态库,完成对C++函数的调用。
static{
System.loadLibrary("HelloJNI");
}
(2)JNI_OnLoad()函数
JNI_OnLoad()函数在VM执行System.loadLibrary(xxx)函数时被调用,有两个重要的作用:
a、指定JNI版本:告诉VM该组件使用那一个JNI版本(若未提供JNI_OnLoad()函数,VM会默认该使用最老的JNI 1.1版),如果要使用新版本的JNI,例如JNI 1.4版,则必须由JNI_OnLoad()函数返回常量JNI_VERSION_1_4(该常量定义在jni.h中)来告知VM。
b、初始化设定,当VM执行到System.loadLibrary()函数时,会立即先呼叫JNI_OnLoad()方法,因此在该方法中进行各种资源的初始化操作最为恰当,而动态注册的工作就是在这里完成的。
(3)JNINativeMethod结构体
JNINativeMethod结构体建立Java与C++函数的联系,在jni.h中被定义,如下:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
第一个变量name是Java中函数的名字;
第二个变量signature,用字符串是描述了函数的参数和返回值;
第三个变量fnPtr是函数指针,指向C++函数。
(4)RegisterNatives()与registerNativeMethods()
RegisterNatives()通过调用registerNativeMethods()和JNINativeMethod结构体,将c/c++中的方法映射到Java空间。
(5)JNIEnv 与 JavaVM : 注意区分这两个概念;
-- JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局只有一个;
-- JNIEnv : JavaVM 在线程中的代表, 每个线程都有一个, JNI 中可能有很多个 JNIEnv;
JNIEnv 作用 :
-- 调用 Java 函数 : JNIEnv 代表 Java 运行环境, 可以使用 JNIEnv 调用 Java 中的代码;
-- 操作 Java 对象 : Java 对象传入 JNI 层就是 Jobject 对象, 需要使用 JNIEnv 来操作这个 Java 对象;
下面看关键代码,全部代码下载
static JNINativeMethod methods[] = {
{"hello", "()Ljava/lang/String;", (void*) hello_jni },
};
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
fprintf(stderr, "Native registration unable to find class '%s'", className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
fprintf(stderr, "RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}
return JNI_TRUE;
}
static const char* classPathName = "com/example/hellojni/MainActivity";
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, classPathName,
methods, sizeof(methods) / sizeof(methods[0]))) {
return JNI_FALSE;
}
return JNI_TRUE;
}
typedef union {
JNIEnv* env;
void* venv;
} UnionJNIEnvToVoid;
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
UnionJNIEnvToVoid uenv;
uenv.venv = NULL;
jint result = -1;
JNIEnv* env = NULL;
printf("JNI_OnLoad");
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
fprintf(stderr, "GetEnv failed");
goto bail;
}
env = uenv.env;
if (!registerNatives(env)) {
fprintf(stderr, "registerNatives failed");
}
result = JNI_VERSION_1_4;
bail:
return result;
}
2、JNI类型映射表
Java 类型 | Native 类型 | 类型说明 | Field descriptor |
boolean | jboolean | C/C++8位整型 | Z |
byte | jbyte | C/C++带符号的8位整型 | B |
char | jchar | C/C++无符号的16位整型 | C |
short | jshort | C/C++带符号的16位整型 | S |
int | jint | C/C++带符号的32位整型 | I |
long | jlong | C/C++带符号的64位整型 | J |
float | jfloat | C/C++32位浮点型 | F |
double | jdouble | C/C++64位浮点型 | D |
Object | jobject | 任何Java对象,或者没有对应java类型的对象 | Ljava/lang/Object; |
Class | jclass | Class对象 | Ljava/lang/Object; |
String | jstring | 字符串对象 | Ljava/lang/String; |
Object[] | jobjectArray | 任何对象的数组 | [Ljava/lang/Object; |
boolean[] | jbooleanArray | 布尔型数组 | [Z |
byte[] | jbyteArray | 比特型数组 | [B |
char[] | jcharArray | 字符型数组 | [C |
short[] | jshortArray | 短整型数组 | [S |
int[] | jintArray | 整型数组 | [I |
long[] | jlongArray | 长整型数组 | [J |
float[] | jfloatArray | 浮点型数组 | [F |
double[] | jdoubleArray | 双浮点型数组 | [D |
Void | Void | 空 | V |
byte[][] a 的签名为 [[B
3、JNI调试
(1)添加头文件JNIlog.h,链接下载;
(2)在Android.mk 中加入:
LOCAL_LDLIBS := -llog
(3)调用
LOGD("%s:%u, static_hello", __func__, __LINE__);
4、静态与非静态原生函数的区别
其区别在于第二个参数随本地方法是静态还是非静态而有所不同:
(1)非静态本地方法的第二个参数是对对象的引用;
(2)静态本地方法的第二个参数是对其Java类的引用;
其余的参数对应通常Java方法的参数,参数类型需要根据一定规则进行映射。
四、JNI常用操作
JNI本身并不复杂,难点在于类型转换上,下就对类型转换进行一个总结。
1、基本类型,直接转换
static jbyte byte_test_jni(JNIEnv *env, jclass clazz, jbyte bt)
{
LOGD("%s:%u, static_use_byte!------bt :%d", __func__, __LINE__,(int)bt);
jbyte ret = 't';
return ret;
}
2、返回String
static jstring static_hello_jni(JNIEnv *env, jclass clazz)
{
LOGD("%s:%u, static_hello", __func__, __LINE__);
return (env)->NewStringUTF("static hello jni.");
}
3、数组操作
jint dealArrayJni(JNIEnv *env, jobject thiz, jbyteArray array1,
jbyteArray array2) {
jbyte* jarray1 = NULL;
char* p1 = NULL;
char* p2 = NULL;
int length1 = env->GetArrayLength(array1);
int length2 = env->GetArrayLength(array2);
LOGD("%s:%u, ", __func__, __LINE__);
if (array1 != NULL) {
//p1指向java堆空间的值,因此,java空间会改变
jarray1 = env->GetByteArrayElements(array1, 0);
p1 = (char*) jarray1;
}
if (array2 != NULL) {
//p2指向jni堆空间的值,因此,java空间不会改变
p2 = (char*) malloc(sizeof(char) * length2);
env->GetByteArrayRegion(array2, 0, length2, (jbyte*) p2);
}
LOGI("%s:%u, p1 = %s", __func__, __LINE__, p1);
LOGI("%s:%u, p2 = %s", __func__, __LINE__, p2);
char data1[6] = "hello";
char data2[6] = "world";
memcpy(p1, data1, length1);
memcpy(p2, data2, length2);
if (jarray1 != NULL) {
//释放指针(减少引计数)
env->ReleaseByteArrayElements(array1, jarray1, JNI_COMMIT);
}
if (p2 != NULL) {
//释放指针(防止野指针)
free(p2);
p2 = NULL;
}
return 0;
}
4、类作为参数
(1)通过实例对象获取类
(2)通过类获得方法与字段ID
(3)进行处理
static void class_arg_test_jni(JNIEnv *env, jclass clazz, jobject obj)
{
LOGD("%s:%u, class_arg_test_jni", __func__, __LINE__);
//通过实例对象获取类
jclass objClass = env->GetObjectClass(obj);
if(objClass)
{
/***通过类获取字段ID**********int*****/
jfieldID intID = env->GetFieldID(objClass,"nValue","I");
/***通过字段ID获取对象实例字段的值*********/
jint nValue = (int)env->GetIntField(obj,intID);
LOGD("%s:%u, class_arg_test_jni, nValue :%d", __func__, __LINE__, nValue );
/*****设置对象实例的字段的值********/
env->SetIntField(obj,intID,222);
//str
jfieldID strID = env->GetFieldID(objClass,"strValue","Ljava/lang/String;");
jstring str = env->NewStringUTF("XXXXXXXXXXXXOOOOOOOOOOOOOOOOO");
env->SetObjectField(obj,strID,str);
//array-----------如何设置nArr呢
jfieldID intArrID = env->GetFieldID(objClass,"nArr","[I");
jint nArr[8] = {8,7,6,5,4,3,2,1};
jintArray jnArray = env->NewIntArray(8);
env->SetIntArrayRegion(jnArray,0,8,nArr);
env->SetObjectField(obj,intArrID,jnArray);
//处理enum对象
/*******1、通过类获得字段ID***************/
jfieldID enumID = env->GetFieldID(objClass,"mdate","Lcom/example/data/TestClass$DateIn;");
/*******2、通过ID获取实例对象相应字段的值(对象实例)************/
jobject jenum = (jclass)env->GetObjectField(obj,enumID);
if(jenum == NULL) {
LOGD("%s:%u, get enum failed", __func__, __LINE__);
return;
}
LOGD("%s:%u, ok", __func__, __LINE__ );
/********3、通过类获取方法ID**************/
jclass jenumClass = (env)->FindClass("com/example/data/TestClass$DateIn");
jmethodID getVal = env->GetMethodID(jenumClass, "name", "()Ljava/lang/String;");
jstring value = (jstring)env->CallObjectMethod(jenum, getVal);
const char * valueNative = env->GetStringUTFChars(value, 0);
if (strcmp(valueNative, "MONDAY") == 0)
LOGI("%s:%u, TODAY IS MONDAY!", __func__, __LINE__);
else if(strcmp(valueNative, "TUESDAY") == 0)
LOGI("%s:%u, TODAY IS NOT TUESDAY!", __func__, __LINE__);
else
LOGI("%s:%u, TODAY", __func__, __LINE__);
}
}
5、返回类
static jobject class_return_test_jni(JNIEnv *env, jclass clazz)
{
LOGD("%s:%u, class_return_test_jni", __func__, __LINE__);
//通过实例对象获取类
jclass objectClass = (env)->FindClass("com/example/data/TestClass");
//通过类获得构造函数
jmethodID mid = (env)->GetMethodID(objectClass,"<init>","()V");
/*********生成java中的类TestClass********/
jobject m_obj = env->NewObject(objectClass,mid);
LOGI("%s:%u, static_deal----GetMethodID", __func__, __LINE__ );
/*******************通过类获取字段ID******************/
jfieldID ival = (env)->GetFieldID(objectClass,"nValue","I");
//jfieldID strID = env->GetFieldID(objectClass,"strValue","Ljava/lang/String;");
/*****************在jni层操作java类中的变量*****************/
(env)->SetIntField(m_obj,ival,100);
return m_obj;
}
6、返回枚举
void enum_arg_test_jni(JNIEnv *env, jclass clazz, jobject obj2)
{
LOGD("%s:%u, enum_arg_test_jni", __func__, __LINE__);
//通过对象实例来获取类
jclass enumclass= env->GetObjectClass(obj2);
if(enumclass == NULL) {
LOGD("%s:%u, get enum failed", __func__, __LINE__);
return;
}
//name()函数是enum类型自带的函数,()Ljava/lang/String;是对应的签名
//1、通过类获取方法ID
jmethodID getVal = env->GetMethodID(enumclass, "name", "()Ljava/lang/String;");
//2、获取根据ID方法,调用实例的name()方法
jstring value = (jstring)env->CallObjectMethod(obj2, getVal);
const char * valueNative = env->GetStringUTFChars(value, 0);
if (strcmp(valueNative, "MONDAY") == 0)
LOGI("%s:%u, TODAY IS MONDAY!", __func__, __LINE__);
else if(strcmp(valueNative, "TUESDAY") == 0)
LOGI("%s:%u, TODAY IS NOT TUESDAY!", __func__, __LINE__);
else
LOGI("%s:%u, TODAY", __func__, __LINE__);
}
demo下载