在某个 iOS 项目中,需要一个服务器来保存一些用户数据,例如用户信息、评论等,我们的服务器端使用了 PHP+MySQL 的搭配。在测试过程中我们发现,用户在 iOS 端里输入了 Emoji 表情提交到服务器以后,PHP 无法在 MySQL 数据库里正确保存,会遇到乱码的问题。下面是原因探析和解决办法。
说起 Emoji 的历史说开去那可谓是源远流长了,这里简要介绍 Emoji 在 iOS 系统中的变迁史。不知道何时起,日本人喜欢发 Emoji 表情图标,于是日本的运营商 DoCoMo、KDDI、SoftBank 推出了自己的 Emoji 字符集。在这些字符集中,一个 Emoji 表情的实际上是一个 UTF8 字符,这点不同于 QQ 或者微信的表情,用户发短信的时候一个 Emoji “图像”只占用了一个字符,那是相当的方便。苹果的 iOS 和 Mac OS X 系统内置了 Apple Color Emoji(Apple 彩色表情符号)的字体用来显示 Emoji 表情,iOS 里有一个 Emoji 输入键盘,可以输入各种 Emoji 表情图标,在 Mac OS X 里使用 Option + Command + T 调出输入键盘里选择“表情符号”输入,Mac OS X 里的字体文件位于 /System/Library/Fonts/Apple Color Emoji.ttf
。抛开版权因素,把这个文件拷贝到 Windows 的字库文件夹里,Windows 也能支持 Emoji 的显示了。
从技术上说,单个 Emoji 表情实际上是使用 UTF8 编码的一个字符,苹果在 iOS4 开始支持了 Emoji 的显示,当时用的是 SoftBank 的方案。在日本的三家运营商之间,用来表示同一个表情的 UTF8 码并不相同(见对比列表),他们之间要做一个转换才能正确显示。以相同一个表情笑脸为例,SoftBank 表示为 U+E04A
,KDDI 则表示为 U+E488
。
iOS4 的解决方案
不过唯一的好处在于,这时候的每个 Emoji 图标,都是使用 3 字节的 UTF8 字符编码。说句题外话,在刚开始 PHP 开发的时候,大家都知道一个中文在 UTF8 字符集占据 3 个字节。 回到 iOS 和 Web 服务器之间的通信方案,我们可以直接把 iOS 中用户输入的 Emoji 表情,通过 PHP 存入 MySQL 数据库中,如果在 iOS 中展示,直接把数据传递给 iOS,客户端应该就能正确展示表情图标了,如果把用户在 iOS 端输入的表情在 Web 端展示,需要把 Emoji 字符转化为对应的图片(当然支持 Emoji 的 Mac OS X 系统可以通过指定网页 CSS 的 font-family
属性直接显示)。Emoji for PHP 组件能够方便地在各种 Unified、DoCoMo、KDDI、SoftBank、Google 编码之间进行转换。
iOS5 的解决方案
从 iOS5 开始,苹果开始支持标准的 Unified 编码的 Emoji 表情。这其中最大的优点莫过于统一了规格,方便了用户和开发者。但是,Web 开发者在开发网页端的时候就会遇到一些问题,以往直接把 Emoji 表情存储在数据库中现在则会导致乱码。这是由于采用 Unified 标准的单个 Emoji 字符一般为 4 个字节,超过了 MySQL 数据库 utf8 字符集支持的每个字符 3 个字节的限制,存入数据库以后会产生乱码或者破坏数据。
如果说解决该问题,最方便的做法是在存入数据库之前,把每个 Emoji 字符转化为 UBB 代码,或者是其他可辨认的形式,如 HTML 转义字符。还是以太阳符号为例,它的 Unified Emoji 编码为 U+2600
,在存入数据库时,可以把它转换成 UBB 代码 [emoji]2600[/emoji]
保存,也可以使用 HTML 转义字符 ☀
储存。如果在 Web 前端展示,用 HTML 转义字符可以直接输出,用 UBB 代码则方便论坛等需要严格安全验证用户输入的地方使用。
还有一种做法你可能也猜到了,采用 SoftBank 方案的单个 Emoji 表情占用 3 个字节,可以直接保存在数据库里,所以可以把 Unified Emoji 转化为 SoftBank Emoji 格式,存储在数据库中就不会有任何问题了。
另外一种方式从 MySQL 自身解决。从 MySQL 5.5.3 开始,MySQL 支持一种 utf8mb4 的字符集,这个字符集能够支持 4 字节的 UTF8 编码的字符。 utf8mb4 字符集能够完美地向下兼容 utf8 字符串。在数据存储方面,当一个普通中文字符存入数据库时仍然占用 3 个字节,在存入一个 Unified Emoji 表情的时候,它会自动占用 4 个字节。所以在输入输出时都不会存在乱码的问题了。要使用 MySQL 的这个特性,首先需要把 MySQL 升级到 5.5.3 以上的版本。其次,需要修改数据结构中的字符集为 utf8mb4 ,如 utf8mb4_general_ci
。由于 utf8mb4 是 utf8 的超集,从 utf8 升级到 utf8mb4 不会有任何问题,直接升级即可;如果从别的字符集如 gb2312 或者 gbk 转化而来,一定要先备份数据库。然后,修改 MySQL 的配置文件 /etc/my.cnf
,修改连接默认字符集为 utf8mb4 ,如果是自己写的 PHP 脚本,也可以在连接数据库以后首先执行一句 SQL: SET NAMES utf8mb4;
。这时候,PHP 应该就可以正常保存 Emoji 到数据库了。
iOS6 的解决方案
苹果在 iOS6 和 Mac OS X 10.8 中又增加了 300 多个 Emoji 表情,而 SoftBank 编码因为渐渐被历史淘汰,已经没有了相应的升级,所以上文提到的把 Unified Emoji 转化为 SoftBank Emoji 后保存在数据库中的方案已经落伍了。
最终解决方案
说了那么多,最终还是要归纳总结一下解决方法。
从数据库中保存的形态出发,要么用 UBB 或者 HTML 转义字符等替代方法保存,要么用数据库直接存储 Emoji 字符。如果直接保存 Emoji 字符,则必须将 MySQL 升级到 5.5.3 以上并且采用 utf8mb4 字符集。如果用替代方法,则不用升级数据库,不影响现有的服务器,是比较省事的方法。
在用户输入方面,以某著名门户新闻客户端为例,常见的几个平台包括:Web,iOS,安卓等。 Web 端上主要是让用户通过点击图标输入 Emoji 表情,在文本框里可以直接以 UBB 代码[emoji]2600[/emoji]
的形式显示,也要考虑 Mac OS X 或 iOS 移动网页端用户直接通过键盘输入 Emoji 字符,这时候最好在 CSS 里添加 textarea{font-family:"Apple Color Emoji","Arial";}
,以便 Mac OS X 上的 Chrome / Firefox 浏览器用户输入了表情以后能直接显示出来,而不是黑框框。iOS 客户端不必说了,直接让用户使用 iOS 自带的表情键盘即可。至于 Android, Windows Phone 或者黑莓等其他平台,一般要通过第三方的输入法来输入 Emoji 表情。至于用户输入的 Emoji 字符还是 UBB 代码,服务器端应该都要有处理的能力。值得注意的是,可能 SoftBank 等 Emoji 字符集都已经不是主流了,在中国的用户估计很少,但如果你想做一个像微信那么跨国界的 APP,还是不妨加一个转换,首先将各种形式的 Emoji 字符首先转换为 Unified Emoji。
在 Web 端显示方面,出于各平台的兼容性考虑,把 Emoji 表情通过图片输出是最合适的做法。无论在数据库中我们保存的形态是什么,把它转化为图片都需要一个对应表。Emoji for PHP已经实现了 iOS5 的 500 多个表情的支持,但尚未提供 iOS6 新增的 300 多个表情。我虽然已经做了对应关系,但是因为没有整理出代码,暂时没办法放出来。
在 iOS 端显示方面,鉴于用户普遍升级到 iOS5 以上,自然是显示原生支持的 Unified Emoji。在安卓和其他移动平台客户端上,我暂时没有研究过,但估计显示图片是比较好的选择。
参考资料: