JNI开发笔记(二)

native_init 和 Java_android_media_MediaScanner_native_init 建立关联关系,其实保存JNI层函数的函数指针
1.需要编译所有声明了native函数的Java类,每个所生成的class文件都得用javah生成头文件
2.初次调用native函数需要根据函数名字搜索对应的JNI层函数建立关联,影响运行效率
    直接让native函数知道JNI对应函数的函数指针,动态注册

动态注册代码

static JNINativeMethod gMethod[]={
	"processDirectory",
	"(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V" ,
	(void *)android_media_MediaScanner_processDirectory
}
Java对应的签名信息  参数类型和繁殖类型 共同组成,Java支持重载
括号内是参数类型的标识,最右边是返回值类型,void类型对应的标识V
当参数的类型是引用类型时,格式是“L包名;”,  包名中的"."换成"/"

JNI数据类型 与 Java数据类型 的映射关系
Java中基本数据类型8中:byte/char/short/int/long/float/double/boolean
其他都是引用数据类型:Object/String/Array[]
所有的JNI引用数据类型全部都是 jobject类型,JNI定义了引用类型集合,集合当中所有类型都是jobject子类,子类和Java中的引用类型相对应:jstring字符串 jclass字节码对象 jthrowable表示异常 jarray数组,另外jarray派生了8个子类,对应8种基本数据类型(jintArray/jshortArray/jlongArray/)
jobject

  jclass jstring jarray jobjectArray 

 jbooleanArray jbyteArray jcharArray jshortArray jintArray jlongArray jfloatArray jdoubleArray

 jthrowable


JNI如果使用C++编写,所有引用类型派生自jobject,使用C++的继承结构特性 

class _jobejct{};
  class _jclass  : public _jobject{};
  class _jstring : public _jobect{};
  class _jarray  : public _jobject{};
  class _jbooleanArray : public _jobject{};
  class _jbyteArrya : public _jobject{};
JNI如果使用C语言编写,所有引用类型使用jobject,其他引用类型使用typedef重新定义
jvalue是一个union联合类型,在JNI中将基本数据类型与引用数据类型定义在一个联合类型中,表示用jvalue定义的变量,可以存储任意JNI类型的数据
typedef union jvalue{
  jboolean  z;
  jbyte     b;
  jchar     c;
  jshort    s;
  jint      i;
  jlong     j;
  jfloat    f;
  jdouble   d;
  jobject   l;
}jvalue;   

函数签名也是联合,Java类型是数组,标识中会有个"["

定义函数签名信息

类型标识
Z          B C S I J F D L/java/languageString; [I    [L/java/lang/object;
boolean                  String                 int[] Object[]

Java类型是数组,标识中有一个“[”,另外引用类型标识最后都有一个“;”
javap 帮助生成函数或变量的签名
  ()Ljava/lang/String String f()
  (ILjava/lang/Class;)J long f(int i, Class c)
javap -s -p xxx
xxx编译后的class文件,s表示输出内部数据类型的签名信息,p表示打印所有函数和成员的签名信息



Java中String引用类型 JNI中jstring表示Java中的String类型  依靠JNIEnv

NewString(JNIEnv *env, const jchar *unicodeChars, jsize len)

JNI 存储的是Unicode字符串,NewString函数也必须是Unicode字符串
将Native的一个UTF-8字符创得到一个jstring,最多 NewStringUTF
GetStringChars   GetStringUTFChars 得到一个Unicode字符串,得到UTF-8

JNI 字符串处理

typedef unsigned char	jboolean;
typedef unsigned short	jchar;
typedef short		jshort;
对C/C++中用的基本类型用typedef重新定义一个新的名字,在JNI中直接访问。
JNI把Java中的所有对象当做一个C指针传递到本地方法中,这个指针指向JVM中的内部数据结构,而内部的数据结构在内存中存储方式是不可见的。只能从JNIEnv指向的函数表中选择合适的JNI函数来操作JVM中的数据结构。
java.lang.String 对应的JNI类型jstring,没有直接使用,因为它是Java的引用类型,在本地代码中只能通过 GetStringUTFChars 这样的JNI访问字符串内容
jboolean isCopy;	//返回JNI_TRUE表示原字符串的拷贝,返回JNI_FALSE表示返回原字符串的指针
(*env)->GetStringUTFChars(env, j_str, &isCopy);
printf("isCopy:%d\n", isCopy);
访问字符串   jstring 类型是指向JVM内部的一个字符串,在JNI中不能把jstring当做普通C字符串一样使用
GetStringUTFChar(JNIEnv *, jstring, jboolean *);
jstring : Java传递给本地代码中的字符串指针
jboolean: JNI_TRUE 表示返回JVM内部字符串的一份拷贝,并为新产生的字符串分配内存空间,如果值为JNI_FALSE,表示返回JVM内部源字符串的指针,可以通过指针修改源字符串的内容。通常情况下填NULL


Java默认使用 Unicode编码,C/C++ 默认使用UTF编码,JNI支持字符串在Unicode和UTF-8两种编码转换,GetStringUTFChars 可以把一个jstring指针(指向JVM内部Unicode字符序列)转换成一个UTF-8格式的C字符串。


释放字符串
调用 GetStringUTFChars 函数从JVM内部获取一个字符串后,JVM内部分配一块新的内存,用于存储源字符串的拷贝,便于本地代码访问和修改。通过调用ReleaseStringUTFChars函数通知JVM这块内存已经不使用了,可以清除。配对使用,用了 GetXXX 就必须调用 ReleaseXXX。

创建字符串
调用NewStringUTF函数,构建一个新的java.lang.String字符串对象。这个新创建的字符创会自动转换成Java支持的Unicode编码。如果JVM不能构造 java.lang.String 分配足够内存,NewStringUTF 会抛出一个 OutOfMemoryError 异常,并返回NULL。如果NewStringUTF创建成功,则返回一个JNI引用,这个引用指向新创建的java.lang.String对象。

异常检查
JVM需要为新诞生的字符串分配内存空间,内存不够分配的时候会导致调用失败,失败后GetStringUTFChars会返回NULL,并抛出一个 OutOfMemoryError 异常。Java遇到异常没有捕获,程序或立即停止运行。JNI异常后程序会继续运行,但后面对这个字符串的操作是非常危险的

其他字符串处理函数
GetStringChars/ReleaseStringChars 获取和释放Unicode格式编码的字符串
GetStringLength UTF-8编码的字符串以'\0'结尾,而Unicode字符串不是,在JNI中通过这个函数获取指向Unicode编码的jstring字符串
GetStringUTFLength 获取UTF-8编码字符串,通过标准C函数strlen获取
GetStringCritical/ReleaseStringCritical 提高JVM返回源字符串直接指针的可能性。Get/ReleaseStringChars和Get/ReleaseStringUTFChars这对函数返回的源字符串会分配内存,如果有一个字符串内容相当大,有1M左右,而且只需要读取内容并打印出来,用这两对函数就不合适。在这两个函数之间的本地代码不能调用任何会让线程阻塞或等待JVM中其他线程的本地函数或JNI函数。因为通过 GetStringCritical 得到一个指向JVM内部字符串的直接指针,获取后会导致暂停GC线程。当GC被暂停后,如果其他线程触发GC继续运行的话,都会导致阻塞调用者。所以在Get/ReleaseStringCritical这对函数中任何
记住检查是否因为内存溢出而导致它的返回值为NULL,因为JVM在执行 GetStringCritical 这个函数时,仍有发生数据复制的可能性,尤其是当JVM内部存储的数组不连续时,为了返回一个指向连续空间的指针,JVM必须复制所有数据。
JNI中没有Get/ReleaseStringUTFCritical这样的函数,因为在进行编码转换时很可能会促使JVM对数据进行复制。

GetStringRegion 和 GetStringUTFRegion 表示获取Unicode和UTF-8编码字符串指定范围内的内容。这对函数会把源字符串复制到一个预先分配的缓冲区内。
GetStringRegion 和 GetStringUTFRegion 由于内部没有分配内存,所有JNI没有提供ReleaseStringUTFRegion和ReleaseStringRegion。

void (JNICALL *GetStringRegion)(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);
字符串操作总结
对于小字符,GetStringRegion和GetStringUTFRegion是最佳选择,因为缓冲区可以被编译器提前分配,而且永远不会产生内存溢出的异常。处理字符串一部分时,它们提供了一个开始索引和子字符串的长度值。复制少量开销小。
使用GetStringCritical和ReleaseStringCritical,一定要确保在持有一个由GetStringCritical获取到的指针时,本地代码不会在JVM内部分配新对象,或者做任何其他可能导致系统死锁的阻塞性调用。
获取Unicode字符串和长度,GetStringChars GetStringLength
从Java字符串转换成C/C++字符串,获取UTF-8字符串长度,GetStringUTFChars GetStringUTFLength
创建Unicode字符串,使用NewStringUTF函数
通过GetStringUTFChars GetStringChars GetStringCritical获取字符串,函数内部会分配内存,必须调用相应的ReleaseXXX

package com.study.jnilearn
class MyClass{}

public class HelloWorld{
	public static native void test(short s, int i, long l, float f, double d, char c, boolean z, byte b, 
	String str, Object obj, MyClass p, int[] arr);
	
	public static void main(String[] args){
		String obj = "obj";
		short s = 1;
		long l = 20;
		byte b = 127;
		test(s, 1, l, 1.0f, 10.5, 'A', true, b,   "中国", obj, new MyClass(), new int[]{});
	}
	
	static{
		System.loadLibrary("HelloWorld");
	}
}
JNIEXPORT void JNICALL Java_com_study_jnilearn_HelloWorld_test(JNIEnv *env, jclass cls, jshort s, jint i, jlong l, jfloat f,
jdouble d, jchar c, jboolean z, jbyte b, jstring j_str, jobject jobc1, jobject job2, jintArray j_int_arr)
{
	const char *c_str = NULL;
	c_str = (*env)->GetStringUTFChars(env, j_str, NULL);
	if(c_str == NULL){
		return;
	}
	(*env)->ReleaseStringUTFChars(env, j_str, c_str);
	printf();
}


JNIEXPORT jstring JNICALL Java_com_study_jnilearn_Sample_sayHello
(JNIEnv *env, jclass cls, jstring j_str)
{
	const jchar* c_str = NULL;
	char buff[128] = "hello";	
	char *pBuff = buff + 6;

	/*
		在GetStringCritical/ReleaseStringCritical 之间是一个关键区。
		绝对不能call JNI的其他函数和会造成当前线程中断或让当前线程等待的任何本地代码
	*/
	c_str = (*env)->GetStringCritical(env, j_str, null);
	if(c_str == NULL)
		return NULL;
	while(*c_str)
	{
		*pBuff++ = *c_str++;
	}
	(*env)->ReleaseStringCritical(env, j_str, c_str);
	return (*env)->NewStringUTF(env, buff);
}


JNIEXPORT jstring JNICALL Java_com_study_jnilearn_Sample_sayHello
(JNIEnv *env, jclass cls, jstring j_str)
{
	jsize len = (*env)->GetStringLength(env, j_str);		//获取unicode字符串长度
	printf("str_len:%d\n", len);
	char buff[128] = "hello";
	char *pbuff = buff + 6;
	//将JVM中的字符串以utf-8编码拷入C缓冲区,函数内部不会分配内存空间
	(*env)->GetStringUTFRegion(env, j_str, 0, len, pbuff);
	return (*env)->NewStringUTF(env, buff);
}




你可能感兴趣的:(Android)