07月09, 2016

排查:IE6都支持的图标字体, 在安卓4.3上却不能显示

昨天接到客户反馈: APP在他们机器上拍照等按钮(图标字体)没了。于是开始排查这个问题。

PS: 本文不带技术含量, 全是分析过程, 也是绕了弯才找到原因并解决, 所以尝试写出来。

PS: 处女作, 欢迎提出建议/指出错误/评论交流, 全文较长, 对于只想看结论的朋友 点这里

1. 收集信息

收集到信息如下:

机型: 酷派9190L

系统: 安卓4.3

问题: APP内的所有图标均显示为空白

日志: 异常监控系统中无任何相关异常

异常DEMO截图 正常&不正常

这里先说明一下目前APP的实现: Hybrid APP, 所有页面都是JS+HTML+CSS编写, 运行在内嵌的原生WebView上, 页面内所有图标都是通过icomoon.io生成的Icon Font。

根据收集的信息, 初步分析:

  1. 没有异常信息, 不能直接定位问题
  2. 使用图标字体都采用了ttf/woff/svg3种格式兼容, caniuse.com上显示: ttf在安卓4.3内置浏览器上完全支持, 并没有不能解析的BUG, 即使woff不支持svg部分支持也不影响功能, 因为ttf写在前面, 优先采用。

APP目前使用图标字体核心片段如下:

CSS

/* 通过icomoon.io生成 */
@font-face {
    font-family: "icomoon";
    src:
        url("fonts/icomoon.ttf?fjceel") format("truetype"),
        url("fonts/icomoon.woff?fjceel") format("woff"),
        url("fonts/icomoon.svg?fjceel#icomoon") format("svg");
}
[class^="icon-"], [class*=" icon-"] {
    font-family: "icomoon" !important;
}
.icon-about:before {
    content: "\e900";
}

HTML

<i class="icon-about" />

2. 重现问题

整个公司都没有这个机型, 试了手上的机型(包括安卓4.1), 但都没有问题。

对于这类不知其然的问题, 若没有机器重现就很难排查, 即使按猜测改完也不能验证是否真正解决了问题。另一方面, 客户描述的问题还是要自己先重现确认的, 避免被客户带错了, 走弯路。

要拿到这个机型进行测试是很不现实的, 客户在北京不可能跑北京去吧, 直接买二手那也更难了。

好在最近云测很火, 各型号手机远程租就可以了。于是在优测上找到一个相近的机型并重现了问题, 机器为安卓4.3的酷派8730L。 确认了问题和客户描述的一致, 所有Icon Font都显示为空白, 也没有任何JS错误。

3. 排查原因

3.1. 请教

感觉这不是一个容易搞的问题, 如果别人有现成方案那我就不用白忙活了, 于是先问了有经验的同事回复如下: 处理过icon的兼容问题, 其他机器都OK了, 只有酷派的机器一直没解决。

于是就自己排查了。

PS: 当然可以选择直接在网上发表问题等待回复, 或者直接找通讯录中的大牛一一请教, 但毕竟谁都不喜欢一有问题就请教别人, 至少得先自己尝试解决吧。

PS: 对这个奇葩机型我们也可以直接告诉客户说系统BUG建议换个任意其他机器, 但是了解到他们是公司免费发了很多这个机型, 导致很多人都用此机型, 所以还是希望能解决这个问题。

3.2. 搜索

Google搜索coolpad iconfont无果, 换成酷派 iconfont 图标字体找到很多相关结果, 但大都没有方案, 只在amazeui上找到有效信息:

主要描述如下:

7.1 关于部分奇葩用户代理不显示字体图标

以酷派为代表的部分安卓手机自带浏览器、微信/QQ WebView 等用户代理无法正常显示 Icon Font,原因可能是这些用户代理无法正确处理伪元素 content 的五位数的 Icon Font 十六进制编码,详情参考 Iconfont 在移动端遇到问题的探讨,可以通过这个页面进行测试。

解决方式有两种:

  • 将 Icon Font 编码限制在 4 位:Amaze UI 直接使用 Font Awesome,不可能去调整近 500 个图标的编码。
  • 将 Icon Font 的编码直接以内容的形式写进 HTML。

v2.3 update: 有用户在评论中说以下写法可以解决图标不显示的问题,v2.3 中已经调整为以下写法,遇到过问题的用户可以测试一下。

/* 安卓手机端Icon不能正确显示的处理办法:*/
[class*="am-icon-"]:before {
  display: inline-block;
  font: normal normal normal 14px/1 FontAwesome;
  font-size: inherit;
  text-rendering: auto;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

3.3. 尝试

按上面搜索到的3个方案(包括v2.3 update提到的css fix方案)进行尝试, 看是否能解决问题。

PS: 虽然问题是在出现APP上,但因为我们使用的是系统自带WebView, 所以问题同样出现在安卓自带浏览器上,也就说我们可以通过自带浏览器进行测试, 而不需要安装APP来调试, 毕竟安卓APP安卓和调试很费时, 而且是用的优测远程。

显示不正常

在尝试了搜索到的方案后, 仍然没能解决, 虽然上文说他们没有收到任何图标不能显示的反馈。 于是我在刚刚那个提供方案的issue上提交了反馈, 同时也反馈了他们站点上的icon在此手机上都不能正常显示。

3.4. 再次尝试

按照上面提到的方案思路, 出现的问题点主要在于编码和CSS写法上, 于是想到其他站点生成的Icon Font是否已经处理了这个问题?

所以就在各大站点生成Icon Font(包括各种可选编码方式和HTML插入方式)放到VPS下, 并再次尝试了以下DEMO:

无奈结果还是一样, 全部不能显示。

接着再都加上方案3的fix css, 分别进行了测试:

结果一致, 不行。

仍然不行

3.5. 换个思路

到此停下来思考一下, 为何都不行? 是不是方式不对? 难道这个机型真有硬伤? 那BAT的网站是不是一样有BUG? 于是在机器上开始测试各站点:

刚打开第一个测试站点, 就发现他们底部的图标居然显示正常, 而且确实用的是图标字体。 瞬间膜拜...看来淘宝是遇到并解决了这个问题。

于是开始看淘宝的实现。虽然前端代码有压缩/混淆, 但通过搜索还是很容易就找到核心代码: 淘宝通过JS动态添加图标字体CSS, 并将字体文件用Data URI BASE64 编码嵌入页面, 页面用HTML直接插入的方式使用图标字体。为了便于理解和测试, 我将淘宝的实现核心代码整理成静态HTML和CSS形式, 并写了CSS伪类插入和HTML直接插入两种方式:

在机器上测试发现CSS伪类插入和HTML直接插入两种方式都能完美支持。

淘宝首页 & DEMO

那到底是什么原因导致我们的图标字体不能正常显示呢?

对比一下实现的代码差别:

/* 淘宝触屏首页-定义方式*/
@font-face {
    font-family: "h5index-iconfont";
    src: url(data:;base64,AAEAA...AAA==) format("truetype");
}

/* 其他站点-定义方式(举例icomoon)*/
@font-face {
    font-family: "icomoon";
    src:
        url("fonts/icomoon.ttf?fjceel") format("truetype"),
        url("fonts/icomoon.woff?fjceel") format("woff"),
        url("fonts/icomoon.svg?fjceel#icomoon") format("svg");
}

分析只有3处不同, 对应以下3种可能:

  1. 字体格式问题。淘宝字体文件只使用了truetype(.ttf)格式字体, 而其他生成的字体都指定了各种类型字体。
  2. 外链引入问题。淘宝字体文件使用Data URI方式引入字体, 而其他生成的字体都是外链引入。
  3. 编译/编码字体方式问题。淘宝使用的字体文件可能经过了特殊处理, 或者使用特定的编码编译, 从而修复了此问题。

于是开始一一尝试。当带着第一种猜想进行测试时, 发现只要把其他站点生成的字体引入CSS改为只使用truetype(.ttf)格式, 就都可以正常显示了。修改后如下:

显示正常 显示正常

从上面w3cmark测试页面注意到还有通过CSS伪类嵌入的5位编码仍然不能显示, 不过没关系, 上面提到的图标生成站点都是用的4位编码。

虽然能解决, 但还是要考虑下此方案有没有负面影响。因为改为使用ttf字体了, 所以需要看一下ttf的支持情况, caniuse.com显示如下:

  • iOS Safari 4.3+
  • Android Browser 2.2+

OK, 我们APP只会在 iOS7.0+ 和 Android4.1+ 上运行, 所以 ttf 完全够了。

4. 最终验证

已经得到解决方案了, 自带浏览器也已测试成功, 同理也可以解决使用原生WebView的APP页面图标字体兼容问题。

但最终还是要在APP上验证, 确定结果。

于是修改APP引入图标字体的CSS为只使用后truetype(.ttf)格式, 再最终测试结果 OK (效果见本文第一张图)。

PS: 前面提到客户机型为酷派9190L-安卓4.3而因为没有找对对应机型, 只在优测上找到相似机型酷派8730L-安卓4.3进行重现并验证修复。 考虑到系统版本一致所以应有99%把握客户机型是同样的问题。实际结果待客户应用更新后反馈。

5. 思考

我们的问题解决了, 但是, 酷派在哪里出了问题?

为什么font-face src加了多种type的兼容字体却不能显示?

5.1. 再来尝试

尝试前先看一下W3C对font-face src的规范说明: CSS Fonts Module Level 3

A conformant user agent would never load the font ‘gentium.eot’ in the example below, since it is included in the first definition of the ‘src’ descriptor which is overridden by the second definition in the same @font-face rule:

@font-face {
  font-family: MainText;
  src: url(gentium.eot);                     /* for use with older user agents */
  src: local("Gentium"), url(gentium.woff);  /* Overrides src definition */
}

In the case of SVG fonts, the URL points to an element within a document containing SVG font definitions. If the element reference is omitted, a reference to the first defined font is implied. Similarly, font container formats that can contain more than one font must load one and only one of the fonts for a given @font-face rule. Fragment identifiers are used to indicate which font to load. If a container format lacks a defined fragment identifier scheme, implementations should use a simple 1-based indexing scheme (e.g. "font-collection#1" for the first font, "font-collection#2" for the second font).

src: url(fonts.svg#simple);    /* load SVG font with id "simple" */

整理和本文相关的内容如下:

  • src可以引入单个或多个外部文件
  • src属性可设置多次, 浏览器以第一个能识别的字体为准来加载
  • src中的format为可选参数
  • src引入svg的URL可选加入#id的入口

再对比刚刚有问题和没问题的CSS:

/* 没问题 */
@font-face {
    font-family: "h5index-iconfont";
    src: url("fonts/icomoon.ttf?fjceel") format("truetype");
}

/* 有问题 */
@font-face {
    font-family: "icomoon";
    src:
        url("fonts/icomoon.ttf?fjceel") format("truetype"),
        url("fonts/icomoon.woff?fjceel") format("woff"),
        url("fonts/icomoon.svg?fjceel#icomoon") format("svg");
}

结合前面caniuse各字体格式的支持度, 分析可能问题在以下两点:

  1. 指定多个src时不解析(多次scr属性赋值或src指定多个type值)。
  2. 指定多个src时处理优先级不正确(多次scr属性赋值或src指定多个type值)。

5.2. 组合分析

对于酷派这种可能对安卓系统有定制的国产机器, 不可能像Chromium那样直接上code分析或提交issue反馈问题, 当然也不具备直接调试浏览器的能力。目前能想到的, 只能是各种尝试, 来限定问题。

下面就要开始机械的组合分析了。

虽然caniuse上写明了字体支持情况, 但还是需要确认一下酷派这个奇葩机型的实际情况。 通过分别单独指定 ttf/woff/svg 格式字体, 再进行组合分析, 结果如下:

  • ttf。支持
  • woff。不支持
  • svg。不支持(这点和caniuse不一致, 特意再测试了同为安卓4.3的三星Note3为支持)
  • ttf,woff,svg。不支持
  • ttf,svg,woff。不支持
  • svg,ttf,woff。不支持
  • svg,woff,ttf。不支持
  • woff,svg,ttf。不支持
  • woff,ttf,svg。不支持
  • ttf,woff。支持
  • woff,ttf。支持
  • svg,ttf。不支持
  • ttf,svg。不支持

PS: 多次scr属性赋值或src指定多个type值两种方式在酷派的表现一致。

通过上面复杂的组合分析, 终于得出结论:

  1. 指定多个src能正常解析(两个就是多个)。
  2. 指定两个src时处理优先级正确, 但加入svg后就会直接解析失败。

至此, 缩小了目标范围, 确定为svg导致了解析失败。

再做细化尝试:

  • 去掉引入svg src的id。不支持
  • 去掉引入svg src的query string。不支持
  • 去掉引入svg src的format。支持

好了, 最终抓到罪魁祸首, 原来是format('svg')导致酷派解析图标字体失败。 看来我们前面的解决方案一刀砍了, 实际并不需要去掉svg文件。

再回顾一下。也就是说我们只需把引入svg的format('svg')去掉, 在酷派上就能正常显示图标字体了, 也不需要改为只使用ttf, 这样对于一些需全面支持各终端版本的网站是个好消息。

最后, 经测试只指定src为svg且去掉format('svg')/src的id/src的query在酷派上仍然不支持。

6. 总结

问题现象: 在酷派指定机型上(已知机型包括酷派9190L、酷派8730L, 均为安卓4.3), 使用自带浏览器或自带WebView内嵌H5时, 通过各流行站点(已测站点包括icomoon.io、iconfont.cn、fontello.com)生成的图标字体不能显示。

问题原因: 酷派指定机型中, 设置font-face src时只要包含format('svg')就会导致 图标字体不能解析, 和指定ttf/woff/svg不同优先顺序无关。

根本原因: 酷派安卓机为国产, 暂未找到系统开源代码, 也未找到系统BUG反馈入口, 可能对安卓系统的相关定制导致异常, 具体原因暂未知。

解决方案:

  1. 对于只需在iOS4.3+/Android2.2+上运行的网页, 可只使用truetype(.ttf)这一种字体格式
  2. 对于需兼容各浏览器的网页, 在指定font-size src时去掉format('svg')这个对svg类型的指定

如:

/* 只需在iOS4.3+/Android2.2+上运行的网页 */
@font-face {
    font-family: "iconfont";
    src: url("iconfont.ttf") format("truetype");
}

/* 需兼容各浏览器的网页。注意: 最后的svg没有指定format */
@font-face {
    font-family: "iconfont";
    src: url("iconfont.eot"); /* IE9*/
    src: url("iconfont.eot?#iefix") format("embedded-opentype"), /* IE6-IE8 */
    url("iconfont.woff") format("woff"), /* chrome、firefox */
    url("iconfont.ttf") format("truetype"), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/
    url("iconfont.svg#iconfont"); /* iOS 4.1- */
}

7. 附:

感谢 @minweGitHub上的回复 给我启发, 附一些站点当前在此测试机上的图标字体显示结果:

站点 图标字体显示情况(酷派8730L-安卓4.3)
淘宝触屏版首页 正常
amazeui demo 不正常
bootstrap doc 不正常
weui.io 正常,和淘宝方案一致
ant.design 不正常
百度首页 不正常
通过iconfont.cn生成的图标字体 不正常
通过fontello.com生成的图标字体 不正常
通过icomoon.io生成的图标字体 不正常

amazeui demo & ant.design bootstrap doc & 百度首页

bootstrap这个站点的图标在机器上显示很奇怪,前2排图标显示出来了但图形有些区别,后面的直接显示不出来。

大家也可以通过点击这里 酷派8730L-优测云手机远程租用 打开页面进行体验(目前每个人都可以免费体验100分钟)。

本文链接:https://meicj.com/post/iconfont-compatible-issue-on-android.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。