Emoji表情在Android JNI中的兼容性问题详解
起因
最近遇到一个问题,把某个字符串计算MD5,之后把该字符串加密与MD5一起上传到服务端,服务端解密后重新计算md5发现与上传的MD5不一致,而出问题的字符串中无一例外都有Emoji表情。但我自己弄个带表情的字符串上传却没有什么问题。
最终确认这是在Android 5.1以下 jstring – char数组 时出的问题。下面通过一个示例来还原这个过程。
事件还原
假设有一个字符串s,String s = "uD83DuDC8B";
,对应表情?
。通过调用getBytes()
方法,会看到对应的byte数组为[-16, -97, -110, -117]
,按16进制输出为[f0, 9f, 92, 8b]
。
定义一个参数为String的native方法,public native String test(String str);
,在对应的C/C++代码中,通过env- GetStringUTFChars
获取传入的String对应的char数组,把char数组的每一个元素按16进制输出。
在Android 7.1.2的测试机上,native层输出的结果为[f0, 9f, 92, 8b]
,与Java的byte数组是一样的,但是在Android 4.4.4的测试机上,输出结果为[ed, a0, bd, ed, b2, 8b]
。从而导致加密后的结果不一样。
服务端收到旧版Android的数据解密后得到[ed, a0, bd, ed, b2, 8b]
,计算MD5自然无法与[f0, 9f, 92, 8b]
计算MD5一样。
Unicode、UTF-8、UTF-16
可能有人不是很清楚上面那2种byte数组是怎么来的。首先我们要知道,UTF-8和UTF-16都是Unicode的实现。uD83DuDC8B
其实是UTF-16大端的表现形式,对于大于0xFFFF(0x10000~0x10FFFF)的Unicode,转换为UTF-16的步骤如下:
- 将Unicode减去0x10000,结果将是一个长度为20bit的值。
- 将第一步的20bit的高10bit与0xD800进行或运算,得到UTF-16的高位代理。
- 将第一步的20bit的低10bit与0xDC00进行或运算,得到UTF-16的低位代理。
- 高位代理+低位代理即Unicode对应的UTF-16的大端形式。
按照这个步骤反推:
- uD83DuDC8B的二进制位1101 1000 0011 1101 1101 1100 1000 1011,则高位代理为1101 1000 0011 1101,低位代理为1101 1100 1000 1011。
- 高位代理由高10bit与0xD800进行或运算得到,因此高10bit为00 0011 1101。
- 低位代理由低10bit与0xDC00进行或运算得到,因此低10bit为00 1000 1011。
- 所有20bit的值为0000 1111 0100 1000 1011。
- 加上0x10000,为0001 1111 0100 1000 1011,即0x1F48B。
所以,表情?对应的Unicode为0x1F48B。
UTF-8的规则是,对于占N个字节的符号(N 1),第一个字节前N位都是1,N+1位是0,后面的字节前2位为10,然后把Unicode的二进制位填入空缺的二进制位中,空出的位置补0。因此,上面的Unicode 0x1F48B转为UTF-8需要占4个字节,为:
11110 000 10 011111 10 010010 10 001011
即0xF09F928B,这也就是[f0, 9f, 92, 8b]这个byte数组的由来。
那么[ed, a0, bd, ed, b2, 8b]这个byte数组又是怎么来的呢?这是把uD83DuDC8B当成2个单独的字符处理了,按照上面Unicode转UTF-8的逻辑,Unicode 0xD83D转为UTF-8为1110 1101 10 100000 10 111101,即0xEDA0BD,Unicode 0xDC8B转为UTF-8为1110 1101 10 110010 10 001011,即0xEDB28B。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对ZaLou.Cn的支持。
- nyoj------170网络的可靠性
- HDUOJ-------1052Tian Ji -- The Horse Racing(田忌赛马)
- 初学Java之Pattern与Matcher类
- 初学java之StringBuffer类的常用方法
- 初学java之大数处理
- hdu---1024Max Sum Plus Plus(动态规划)
- Go语言异步服务器框架原理和实现
- nyoj------布线问题(kruscal+求最小值)
- nyoj-----127星际之门(一)
- nyoj------20吝啬的国度
- HDUOJ-------2493Timer(数学 2008北京现场赛H题)
- go sync.Mutex 设计思想与演化过程 (一)
- HDUOJ--------A simple stone game(尼姆博弈扩展)(2008北京现场赛A题)
- HDUOJ----2485 Destroying the bus stations(2008北京现场赛A题)
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释