由Emoji表情发现的JNI GetStringUTFChars()隐藏的问题

转载自:http://www.jianshu.com/p/f604a4224098


我们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形式字符串的计算方式):

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值