借助 Magisk 优化/个性化 Android 中文字体显示效果
没有故弄玄虚的意思,Android 中文字体渲染确实是有瑕疵的。若非,便不会那么多人有动力一次次换字体。「换字体」这个 Topic 网上已经有一大把教程了,遗憾的是相当一部分都过于傻瓜化——直接丢过来一个需要 Root 的 App 也好、扔一个 Magisk 一键换字体模块也罢。
都会 Root 了,动手能力总还是不会太差的。授人以鱼不如授人以渔,与其打包好一个模块,不如教他们如何制作适合自己的模块,将自由度最大程度还给折腾字体的用户。
本文目的便在此,记录下我从零开始了解 Android 字体渲染机制、制作一个 Magisk 字体优化模块的过程。希望能让你看到一些不一样的东西。
Android 中文字体之过
用 Android 手机浏览某些网页的时候,总感觉不是那么对劲。稍微对比便可发现:中文字体的加粗没能实现,而英文字体就没有这种问题。
通过一些调试工具很快发现,加粗的字重被设为 600
,而默认 bold 字重为 700
。这并非什么稀奇的事情,GitHub 就是这么干的。而且在 Windows 和 macOS 正常显示,甚至 Android 设备中的英文字体也能显示出加粗效果。这样看来,最有可能是在中文字体库出错了。
通过 阅读 Android 源码 阅读其他大佬的博客不难发现,Android 启动时会解析 /system/etc/fonts.xml
字体配置文件创建对应的 Typeface 对系统字体进行加载。
获取 Root 权限,祭出 Root Explorer 查看字体配置文件 /system/etc/fonts.xml
。默认的英文字体有 18 条配置,包括衬线、无衬线、等宽三种各 6 款字体对应 100 - 900
范围内 6 种不同字重。再看中文字体配置就显得有点寒酸了,全局搜索 zh
关键字总共就一条:
<family lang="zh-Hans">
<font weight="400" style="normal" index="2">NotoSansCJK-Regular.ttc</font>
<font weight="400" style="normal" index="2" fallbackFor="serif">NotoSerifCJK-Regular.ttc</font>
</family>
其实也没什么好多言的。毕竟简体中文使用最频繁的地区,中国大陆,Google 在这甚至还不受待见。那人家还优化个什么劲啊,能用就省省吧!
对应的,Apple 在 iOS 的中文苹方字体就也有 6 个字重覆盖绝大多数使用场景。
那要显示粗体怎么办呢?答案是系统机器地加粗,也被称为「伪粗体」。然而没有经过人工处理的机器加粗,在一些笔画比较密集的场景很容易出差错(如 笔画粘连)。显示效果差就算了,伪粗体字重为 700
,字重在 700 以下直接回退至 normal
。这也就出现了字重设置为 600
时中文不显示粗体、而英文能正常显示粗体的糟糕体验。
明白问题所在后,便可以自己补充不同字重文件,完善多字重的字体库。大概就能挽救下不尽人意的 Android 字体渲染效果了。
并且这样一来,选取何种字体完全自由,你甚至可以为不同字重设置不同字体(然而非常不建议这么做),不受限于 Android 默认的思源黑体(Noto Sans CJK)。
准备工作
首先自然是要下载你所需要的字体库,可以前往 Google Fonts 获取各式各样的开源中文字体,例如 Android 默认的「思源黑体」就能在这里获得完整的、多字重的字体库。如果你有其他渠道也可以,但请注意版权问题。
以 Noto Sans SC 为例,在 Google Fonts 下载解压后将得到 6 个 .otf
文件,而不同命名版本对应的字重分别是:
- Thin:
100
- Light:
300
- Regular:
400
- Medium:
500
- Bold:
700
- Black:
900
此仅为 Noto Sans SC 配置,不同字体可能有所不同。
然后修改字体配置文件中的中文相关部分:
<family lang="zh-Hans">
<font weight="100" style="normal">NotoSansSC-Thin.otf</font>
<font weight="300" style="normal">NotoSansSC-Light.otf</font>
<font weight="400" style="normal">NotoSansSC-Regular.otf</font>
<font weight="500" style="normal">NotoSansSC-Medium.otf</font>
<font weight="700" style="normal">NotoSansSC-Bold.otf</font>
<font weight="900" style="normal">NotoSansSC-Black.otf</font>
</family>
为了照顾到老旧程序,最好再将原默认配置作为回退保险,总共就是:
<family lang="zh-Hans">
<font weight="100" style="normal">NotoSansSC-Thin.otf</font>
<font weight="300" style="normal">NotoSansSC-Light.otf</font>
<font weight="400" style="normal">NotoSansSC-Regular.otf</font>
<font weight="500" style="normal">NotoSansSC-Medium.otf</font>
<font weight="700" style="normal">NotoSansSC-Bold.otf</font>
<font weight="900" style="normal">NotoSansSC-Black.otf</font>
</family>
<family lang="zh-Hans">
<font weight="400" style="normal" index="2">NotoSansCJK-Regular.ttc</font>
</family>
如果你有兴趣,也可以将「中日韩(CJK
)字体」中的日文(ja
)字体和韩文(ko
)字体也一并优化了。
接下来只需要将字体文件移至字体目录 /system/fonts/
下,重启设备……你大概率是不会顺畅地完成的,最可能的情况是字体还没导入完全就莫名重启了。
回过头来看那几个字体文件。西文字体文件大小基本不超过 10MB,而中文、日文、韩文等东亚字体文件动辄不小于 120 MB。有些设备系统分区不会预留那么大空间,字体根本刷不进去。再说了,直接修改 System 文件是一件很危险的事情。
借助 Magisk
每一个 Android 玩机的小伙伴应该都不会对 Magisk 感到陌生。SuperSU 落幕后,一举成为众多 Root 玩家的利刃。Systemless、挂载特性使其在安全性、功能性、易用性上都可圈可点。Magisk 能在不修改系统分区的前提下,通过挂载另一个 Magisk 分区来加载自定义内容。
既然如此,我们可以借助 Magisk 挂载机制在不消耗系统分区空间的前提下达到修改系统文件一样的效果,让本来放不下的字体文件「好像放进去了」(对系统分区而言)。
你可以下载一个 Magisk 模块模版,或者在我修改的 [Noto Sans CJK 字体补全] 上二次创作。需要清楚的是,模块内的 /system/
下的目录将挂载至系统 System 目录下,所以我们只需要将修改后的 font.xml
放入模块内的 /system/etc/font.xml
,并将字体文件放在模块内的 /system/fonts/
目录下。
仔细一看我修改的 [Noto Sans CJK 字体补全] 模块会发现配置与上文提到的内容有所出入。事实上,该模块使用 Super OTC 将简体中文、繁体中文、日文、韩文 4 种语言各 7 种字重全部整合到一个文件,体积较单独 OTC 文件小了 10 MB。
后
这要说换字体的经历,于我而言可能要追溯到 6、7 年前,用某某主题商店,花个几块钱换一个花里胡哨的字体,没过几天又腻了。
慢慢的,对那些让人眼花缭乱的东西不感冒了。希望以简驭繁,当然这绝非放弃追求,否则也不会有这篇优化 Android 设备中文字体渲染的文章。
文章最后,我想再强调字体版权的重要性。对于在自己手机上使用的字体,就算来路不明也几乎无从追究,难免让人动点邪念。这绝非空穴来风,我曾看着同学在 PPT 上嵌入「某某免费字体下载站」下下来字体,完全不注重版权。相信抱着「只是在自己手机上用用,认为没必要浪费精力纠结正规渠道、正版字体」想法的人不在少数。然而一款字体的面世背后往往是一个设计团队数年的心血。他们对字体(Typeface)、字型(Font)、字形(Glyph)的独到把握,对不同字重每一处细节反复拿捏。从文件大小也能察觉到中文字体较西文字体设计难度更大。若中文字体使用者不在乎版权,中文字体设计者无法获得应有的回报,未来将很难再看到优秀的中文字体了。
参考链接:
- 《Magisk 模块字体为什么这么好用,因为它有这些优点!》:出自宁静之雨——折腾过 Android 字体总看过雨大的文章吧,从 Android 5 到 Android 10 一直坚持分享。
- Magisk Developer Guides:Magisk 官方开发指南
- Android 字体加载原理总结:Leslie 对 Android 字体加载流程的解读