通过前面章节接触到字符串类型,知道字符串类型的数据是不能直接操作的。本节来介绍不能操作的原因及操作字符串的一些函数。
JNI 把 Java 中的所有对象当作一个C指针传递到本地方法中,这个指针指向 JVM 中的内部数据结构,而内部的数据结构在内存中的存储方式是不可见的。只能从 JNIEnv 指针指向的函数表中选择合适的 JNI 函数来操作 JVM 中的数据结构。
前面示例中,访问 java.lang.String 对应的 JNI 类型 jstring 时,没有像访问基本数据类型一样直接使用,因为它在 Java 是一个引用类型,所以在本地代码中只能通过 GetStringUTFChars 这样的 JNI 函数来访问字符串的内容。
下面通过实例介绍处理jstring的一些重要函数:
String to Char
extern "C"
jstring Java_com_honjane_ndkdemo_JNIUtils_handlerString( JNIEnv* env, jobject jobj,jstring jstr){
//utf8
const char* chr = env->GetStringUTFChars(jstr,0);
//unicode
const jchar* chr1 = env->GetStringChars(jstr,0);
jsize jlen = env->GetStringUTFLength(jstr);
jsize jlen1 = env->GetStringLength(jstr);
char c[120] = "你好 2017";
//把chr拼接到c
strcat(c,chr);
jstring new_str = env->NewStringUTF(c);
env->ReleaseStringUTFChars(jstr,chr);
env->ReleaseStringChars(jstr,chr1);
return new_str;
}
输出结果:
I/main----handlerString: 你好 2017 happy new year
这里使用GetStringUTFChars方法将传进来的jstr(jstring类型)转换成为UTF-8的格式,就能够在本地方法中使用了。
注意:在使用完你所转换之后的对象之后,需要显示调用ReleaseStringUTFChars方法,让JVM释放转换成UTF-8的string的对象的空间,如果不显示的调用的话,JVM中会一直保存该对象,不会被垃圾回收器回收,因此就会导致内存溢出。
◆GetStringUTFChars将jstring转换成为UTF-8格式的char*
◆GetStringChars将jstring转换成为Unicode格式的char*
◆ReleaseStringUTFChars释放指向UTF-8格式的char*的指针
◆ReleaseStringChars释放指向Unicode格式的char*的指针
◆NewStringUTF创建一个UTF-8格式的String对象
◆NewString创建一个Unicode格式的String对象
◆GetStringUTFLengt获取UTF-8格式的char*的长度
◆GetStringLength获取Unicode格式的char*的长度
上面实例都是把string转换为char类型,有些场景需要把string转换成byte
String to Byte
java代码
public native byte[] handlerStrToByte(String text);
byte [] bytes = jniUtils.handlerStrToByte("honjane");
for (byte b : bytes) {
Log.i("main---handlerStrToByte", (char)b+"");
}
JNI实现:
char * jstringToChar(JNIEnv * env,jstring jstr){
char* rtn = NULL;
//等到String的class
jclass jcls = env->FindClass("java/lang/String");
//定义string的编码格式
jstring strcode = env->NewStringUTF("utf-8");
//获得string的getBytes method Id
jmethodID jmid = env->GetMethodID(jcls, "getBytes", "(Ljava/lang/String;)[B");
//调用getBytes方法 返回一个jbyteArray
jbyteArray byteArr= (jbyteArray)env->CallObjectMethod(jstr, jmid, strcode);
jsize alen = env->GetArrayLength(byteArr);
//数组指向一个byte指针
jbyte* pbyte = env->GetByteArrayElements(byteArr, 0);
if (alen > 0)
{
//申请数组长度+1个内存空间
rtn = (char*)malloc(alen + 1);
//从源pbyte所指的内存地址的起始位置开始拷贝alen个字节到目标rtn所指的内存地址的起始位置中
memcpy(rtn, pbyte, alen);
rtn[alen] = 0;
}
//释放
env->ReleaseByteArrayElements(byteArr, pbyte, 0);
return rtn;
}
//string to byte
extern "C"
jbyteArray Java_com_honjane_ndkdemo_JNIUtils_handlerStrToByte( JNIEnv* env, jobject jobj,jstring jstr){
char * chr = NULL;
chr =jstringToChar(env, jstr);
jbyteArray RtnArr = NULL; //下面一系列操作把chr转成jbyteArray 返回出去
RtnArr = env->NewByteArray(strlen(chr));
env->SetByteArrayRegion(RtnArr, 0, strlen(chr), (jbyte*)chr );
//释放chr
if(chr) {
free(chr);
}
return RtnArr;
}
输出结果:
I/main---handlerStrToByte: h
I/main---handlerStrToByte: o
I/main---handlerStrToByte: n
I/main---handlerStrToByte: j
I/main---handlerStrToByte: a
I/main---handlerStrToByte: n
I/main---handlerStrToByte: e
上面代码注释都解释的很明白了,不在多强调,这里主要介绍下SetByteArrayRegion,该函数将本地的数组数据拷贝到了 Java 端的数组中。这个函数的功能不多说主要注意Region。
GetStringRegion和GetStringUTFRegion
字符串操作也有类似的函数GetStringRegion和GetStringUTFRegion,这对函数会把源字符串复制到一个预先分配的缓冲区内,将指向Java字符串的 jstring 以相应编码 截取之后 传入 C++分配出来存储字符串的空间buf。
GetStringUTFRegion与 GetStringUTFChars 比较相似,不同的是,GetStringUTFRegion 内部不分配内存,不会抛出内存溢出异常。不分配内存,不抛出内存溢出异常,这个多好的功能,会经常使用到这个函数。
注意:GetStringUTFRegion 和 GetStringRegion 这两个函数由于内部没有分配内存,所以 JNI 没有提供ReleaseStringUTFRegion 和 ReleaseStringRegion 这样的函数。
GetStringCritical
除了GetStringUTFChars,GetStringUTFRegion这2对函数之外还有一对函数GetStringCritical 与 ReleaseStringCritical
为了增加**直接传回Java字符串的指针的可能性(不是拷贝)**JDK1.2 之后增加了这对函数。
const jchar * GetStringCritical(jstring string, jboolean *isCopy) {
return functions->GetStringCritical(this,string,isCopy);
}
void ReleaseStringCritical(jstring string, const jchar *cstring) {
functions->ReleaseStringCritical(this,string,cstring);
}
1.这两个函数之间是一个关键区,在这个关键区中不能调用JNI的其它函数或可能造成当前线程中断、等待的任何本地代码 , 否则将造成关键区代码执行期间垃圾回收器停止运作,任何触发垃圾回收器的线程也将暂停,其它触发垃圾回收器的线程不能前进,直到当前线程结束来激活垃圾回收器
2.关键区中不能出现中断操作,不能在 JVM 中分配新对象,否则造成 JVM 死锁.
3.虽然增加了直接传回Java字符串指针的可能性,不过还是需要根据实际情况传回拷贝过的字符串,如 Java 字符串是UTF-16 ,要想转成UTF-8编码还是需要进行拷贝,所以也就没有GetStringUTFCritical 这个函数
源码下载:https://github.com/honjane/JNIDemo