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支持重载
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重新定义
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中直接访问。
jboolean isCopy; //返回JNI_TRUE表示原字符串的拷贝,返回JNI_FALSE表示返回原字符串的指针
(*env)->GetStringUTFChars(env, j_str, &isCopy);
printf("isCopy:%d\n", isCopy);
访问字符串 jstring 类型是指向JVM内部的一个字符串,在JNI中不能把jstring当做普通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);
字符串操作总结
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);
}