由Emoji表情发现的JNI GetStringUTFChars()隐藏的问题
2016-10-31 09:40
211 查看
转载自:http://www.jianshu.com/p/f604a4224098
我们App的消息收发底层由C++实现,自然就需要使用JNI,开始的方案是将消息内容String字符串直接向下传,然后在JNI中解析为C++ string形式,
当然我们使用的是
我更改了消息发送协议,在java层把消息内容由String改为
那不禁要问为什么会这样呢?
先看Java的
那这个方式和JNI得到的为什么不一样呢?经过查找发现,问题的根源竟是这样...(戳这里看原因)
先看下oracle给
GetStringUTFChars
const char GetStringUTFChars(JNIEnv env, jstring string, jboolean *isCopy);
该方法返回一个指向字节数组的指针,这个字节数组就是变种UTF-8(modified UTF-8)编码的string.
这个字节数组在ReleaseStringUTFChars()调用之前都是有效的.
关键点就是这个
Modified UTF-8(变种UTF-8格式):
标准和变种的UTF-8有两个不同点。第一,空字符(null character,U+0000)使用双字节的0xc0 0x80,而不是单字节的0x00。这保证了在已编码字符串中没有嵌入空字节。因为C语言等语言程序中,单字节空字符是用来标志字符串结尾的。当已编码字符串放到这样的语言中处理,一个嵌入的空字符将把字符串一刀两断。
第二个不同点是基本多文种平面之外字符的编码的方法。在标准UTF-8中,这些字符使用4字节形式编码,而在改正的UTF-8中,这些字符和UTF-16一样首先表示为代理对(surrogate pairs),然后再像CESU-8那样按照代理对分别编码。这样改正的原因更是微妙。Java中的字符为16位长,因此一些Unicode字符需要两个Java字符来表示。语言的这个性质盖过了Unicode的增补平面的要求。尽管如此,为了要保持良好的向后兼容、要改变也不容易了。这个改正的编码系统保证了一个已编码字符串可以一次编为一个UTF-16码,而不是一次一个Unicode码点。不幸的是,这也意味着UTF-8中需要4字节的字符在变种UTF-8中变成需要6字节。
因为变种UTF-8并不是UTF-8,所以用户在交换信息和使用互联网的时候需要特别注意不要误把变种UTF-8当成UTF-8数据。(摘自维基百科)
以笑脸Emoji表情为例(例子下面会给出Emoji表情是转化为UTF-8以及变种UTF-8形式字符串的计算方式):
我们App的消息收发底层由C++实现,自然就需要使用JNI,开始的方案是将消息内容String字符串直接向下传,然后在JNI中解析为C++ string形式,
当然我们使用的是
GetStringUTFChars方法。然而消息发送后,发现Emoji表情在服务端无法正确解析。在java层和jni层分别加log后,我发现java层的消息内容的16进制字符串与JNI使用
GetStringUTFChars方法得到C++格式string的16进制字符串内容并不一样,我想这应该就是产生问题的原因,当然想法需要实际的验证。
我更改了消息发送协议,在java层把消息内容由String改为
byte[]数组形式,这样JNI层就不再需要使用
GetStringUTFChars方法转换消息内容。再次测试,bingo,Emoji表情收发解析成功。
那不禁要问为什么会这样呢?
GetStringUTFChars到底做了什么?
先看Java的
String.getBytes()方法得到UTF-8编码
byte[]的源码,
public byte[] getBytes() { return getBytes(Charset.defaultCharset()); } public static Charset defaultCharset() { return DEFAULT_CHARSET;//就是UTF-8了 } public byte[] getBytes(Charset charset) { String canonicalCharsetName = charset.name(); if (canonicalCharsetName.equals("UTF-8")) { return CharsetUtils.toUtf8Bytes(this, 0, count); } else if (canonicalCharsetName.equals("ISO-8859-1")) { return CharsetUtils.toIsoLatin1Bytes(this, 0, count); } else if (canonicalCharsetName.equals("US-ASCII")) { return CharsetUtils.toAsciiBytes(this, 0, count); } else if (canonicalCharsetName.equals("UTF-16BE")) { return CharsetUtils.toBigEndianUtf16Bytes(this, 0, count); } else { ByteBuffer buffer = charset.encode(this); byte[] bytes = new byte[buffer.limit()]; buffer.get(bytes); return bytes; } }
那这个方式和JNI得到的为什么不一样呢?经过查找发现,问题的根源竟是这样...(戳这里看原因)
先看下oracle给
GetStringUTFChars的定义
GetStringUTFChars
const char GetStringUTFChars(JNIEnv env, jstring string, jboolean *isCopy);
该方法返回一个指向字节数组的指针,这个字节数组就是变种UTF-8(modified UTF-8)编码的string.
这个字节数组在ReleaseStringUTFChars()调用之前都是有效的.
关键点就是这个
Modified UTF-8,那么它又是什么呢?
Modified UTF-8(变种UTF-8格式):
标准和变种的UTF-8有两个不同点。第一,空字符(null character,U+0000)使用双字节的0xc0 0x80,而不是单字节的0x00。这保证了在已编码字符串中没有嵌入空字节。因为C语言等语言程序中,单字节空字符是用来标志字符串结尾的。当已编码字符串放到这样的语言中处理,一个嵌入的空字符将把字符串一刀两断。
第二个不同点是基本多文种平面之外字符的编码的方法。在标准UTF-8中,这些字符使用4字节形式编码,而在改正的UTF-8中,这些字符和UTF-16一样首先表示为代理对(surrogate pairs),然后再像CESU-8那样按照代理对分别编码。这样改正的原因更是微妙。Java中的字符为16位长,因此一些Unicode字符需要两个Java字符来表示。语言的这个性质盖过了Unicode的增补平面的要求。尽管如此,为了要保持良好的向后兼容、要改变也不容易了。这个改正的编码系统保证了一个已编码字符串可以一次编为一个UTF-16码,而不是一次一个Unicode码点。不幸的是,这也意味着UTF-8中需要4字节的字符在变种UTF-8中变成需要6字节。
因为变种UTF-8并不是UTF-8,所以用户在交换信息和使用互联网的时候需要特别注意不要误把变种UTF-8当成UTF-8数据。(摘自维基百科)
GetStringUTFChars得到的是一个修改过的UTF-8编码的字符串,那这个字符串到底有什么不同呢?
以笑脸Emoji表情为例(例子下面会给出Emoji表情是转化为UTF-8以及变种UTF-8形式字符串的计算方式):
相关文章推荐
- firefox浏览器下用getElementById取一个隐藏表单域内容时发现的问题
- JNI: Get/ReleaseStringUTFChars和Get/ReleaseIntArrayElements的区别,isCopy是否重新分配内存的问题
- JNI GetStringUTFChars
- JNI GetStringUTFChars 函数错误
- jni GetStringUTFChars
- findbug 发现的一些隐藏问题总结
- 发现一个SSH action中蛋疼的问题,方法名不能以get开头
- 发现Java StringBuffer使用中的一个隐藏问题,你中招了吗?
- 发现Java StringBuffer使用中的一个隐藏问题,你中招了吗?
- android jni问题之JNI WARNING: threadid=1 using JNI after critical get (GetObjectClass)
- Fedora Core 2系统备份的一些发现和问题
- [Firefox]新发现的Firefox功能及存在的问题
- 暂时还没有发现问题:)
- 最近在使用sps类库过程中发现了一个让我比较疑惑的问题(有关items属性的)
- Java与C之间通过JNI传递中文字符串及乱码问题
- 今天发现了一个sql的小问题
- 最近在使用sps类库过程中发现了一个让我比较疑惑的问题(有关items属性的)
- 一个无法使用 GetGraphics() 的问题
- 项目管理中发现的问题。
- ASP.NET中Panel服务器控件的隐藏与显示应该注意的问题