Android JNI 概述

      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、关键接口介绍

1System.loadLibrary()函数

          Java程序通过System.loadLibrary加载完JNI动态库,完成对C++函数的调用。

	static{
		System.loadLibrary("HelloJNI");
	}

2JNI_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()方法,因此在该方法中进行各种资源的初始化操作最为恰当,而动态注册的工作就是在这里完成的。

3JNINativeMethod结构体

JNINativeMethod结构体建立JavaC++函数的联系,在jni.h中被定义,如下:

typedef struct {

const char* name;

const char* signature;

void* fnPtr;

} JNINativeMethod;

第一个变量nameJava中函数的名字;

第二个变量signature,用字符串是描述了函数的参数和返回值;

第三个变量fnPtr是函数指针,指向C++函数。

4RegisterNatives()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

【ps】

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下载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值