2018 年 5 月,SuperSU 的作者 Chainfire 正式宣布停止开发所有 root 相关的应用,这个消息曾一度让许多人认为是 Android 玩机时代的落幕。

用尘封之泪大神的话来说,就是:

Chainfire 是真正的大神,能够做到像 Chainfire 这样的人并不多。

但谁也没想到,此前一直不温不火的 Magisk 却在这个时候站了出来并成功扛过了 root 工具的重担。现在,Magisk 不仅做到了 Android Q 一问世就立即解决了 root 问题,还为玩机的朋友提供了挂载各种优化模块,甚至结合一些开发者的作品在 Androd 9 上实现了需要依赖 Xposed 框架才能实现的功能(在 Xposed 的作者 rovo89 自己都还没搞定 Android 9 适配的前提下,这是一件非常 amazing 的事情)。

不过,今天主要讲通过 Magisk 换字体的话题,Magisk 模块字体为什么这么好用?

Android 是如何呈现字体的?

要回答上面这个问题,得先从 Android 手机的字体调用规则说起。

Android 5.0 之后,几乎整个手机的字体效果都由 fonts.xml 这个配置文件来掌控,它位于 system/etc 路径下,起到对 system/fonts 路径下的字体文件进行全局调配的中枢级作用。

之所以用上「几乎」这个词,是因为 Android 手机环境复杂,各大厂商喜欢魔改系统,也难免动到和字体有关的地方。因此通过修改 fonts.xml 这个配置文件以及 system/fonts 这些字体文件也许只能实现 95% 左右的字体替换覆盖效果,只有在原生安卓系统上才能 100% 全局生效。

为什么要进行这样的修改呢?在 fonts.xml 中,有一段很关键的代码:

<family name="sans-serif">    <font weight="100" style="normal">Roboto-Thin.ttf</font>    <font weight="100" style="italic">Roboto-ThinItalic.ttf</font>    <font weight="300" style="normal">Roboto-Light.ttf</font>    <font weight="300" style="italic">Roboto-LightItalic.ttf</font>    <font weight="400" style="normal">Roboto-Regular.ttf</font>    <font weight="400" style="italic">Roboto-Italic.ttf</font>    <font weight="500" style="normal">Roboto-Medium.ttf</font>    <font weight="500" style="italic">Roboto-MediumItalic.ttf</font>    <font weight="900" style="normal">Roboto-Black.ttf</font>    <font weight="900" style="italic">Roboto-BlackItalic.ttf</font>    <font weight="700" style="normal">Roboto-Bold.ttf</font>    <font weight="700" style="italic">Roboto-BoldItalic.ttf</font> </family>

即使没有什么程序员基础也不难看懂这段代码的意思,没错,font.xml 控制了 Android 操作系统在不同 UI 界面中的字体粗细,告诉系统该在哪些地方分别调用哪一款 Roboto 字体

Roboto 是 Google 为 Android 操作系统设计的一个无衬线字体家族,原本包含 Thin、Light、Regular 、Medium、Bold、Black 共计 6 套字重(即 weight),同时每个字重还有斜体版本(即 italic)。

此外,Roboto 字体家族的部分字重还有窄版的 Condensed 及对应的斜体,它们在 Android 操作系统中通常用作桌面启动器应用抽屉的图标标签,保证在有限的空间内显示更完整的应用名称。这种设定同样在 fonts.xml 这个文件里有所体现。

不难看出,fonts.xml 就像是一本字典,Android 操作系统在需要调用某一个字体效果时就会在这当中进行查询,然后按图索骥,找到并调用 system/fonts 下对应的字体文件,最后我们就在界面上看到了字体效果,是不是很简单?

你每天都在看的中文字体并不完美

我们继续用这个思路继续往下看 Android 操作系统中中文字体的调用机制。

在 Android 5.0 和 6.0 版本中,中文字体的控制代码如下:

<family lang="zh-Hans">    <font weight="400" style="normal">NotoSansSC-Regular.otf</font></family>

在 Android 7.0 到 8.1 版本中,中文字体的控制代码变成了这样:

<family lang="zh-Hans">    <font weight="400" style="normal" index="2">NotoSansCJK-Regular.ttc</font></family>

而在 Android 9 之后,中文字体的控制代码改成了这样:

<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>

这三套代码基本大同小异,早期版本调用思源黑体的 OpenType 文件,后来改成了思源黑体的 TrueType Collection 文件,并且用额外的 index 代码指定了集成字库中的文件序号,再后来又追加了缺字回退机制(即思源黑体里没有的符号用思源宋体来显示)。

不过仔细观察和对比后却不难发现,Google 在中文字体调用机制上偷了懒:和英文、数字字体中使用 6 套不同粗细字体并且还有斜体、窄版等做法不同,中文环境下 Google 仅用了 font weight=400 来敷衍了事

这种区别对待给我们带来了什么问题呢?

我们以谷歌原生系统默认的「思源黑体单字重」效果和我优化后的「思源黑体多字重」效果作比较:

默认样式(左)和优化样式(右),均为思源黑

上图中,我们可以看出哪怕中文只有一个「font weight=400」的配置,但界面上依然可以在标题部分显示粗体,但这个粗体似乎有些问题,我们从上图中截取两段,特写镜头对比一下:

伪粗体和真粗体对比

不难发现,系统默认方案下出来的粗体,首先粗度上就有问题——它显然不够粗啊!如果你的观察力不是很强,觉得两个标题粗度差不多,不妨再观察一下上图「大东山谷」和「普洱歌剧猫」这两个 ID 的字重,差别会更加明显。

事实上,默认字体的粗体不仅粗度不够,渲染效果也非常糟糕,这种无中生有冒出来粗体,是 Android 系统自动把字体机械加粗显示出来的效果,Windows 下也有相似的机制,我称之为「伪粗体」。「伪粗体」机制会带来一些很糟糕的体验,很多手机现在主题商店里都能换字体了,但只能达到单字重效果,比如这样:

行黑体,不好意思粗体糊了

阿丽达黑体,粗体笔画变形


上述这两个问题的锅,主要是安卓默认单字重的问题。粗体并不是独立的字体文件,而是系统自动机械加粗的「伪粗体」。而「伪粗体」由于没有经过人工较准,难免出现笔画粘连、字体变形等问题。

PS. 这里开发者也得背一部分锅,因为很多时候哪怕我们优化了多字重效果,这些问题还会或多或少遇到。而 App 开发者通常只会在系统自带的字体下测试,开发的时候也没严格调用多字重,非要用糟糕透顶的「伪粗体」渲染。

借 Magisk 用上多字重字体

解决方法则很简单——开启多字重效果,让粗体单独使用一个优质的字体文件来显示。

这不仅能够解决我们在上面展示的大部分字体问题,除了常规和粗体,还有丰富的其他粗细档位显示效果(例如微信聊天界面左上角就应该是 Medium 中粗体)。

微信标题字体

这里插句题外话。我从 Android  5.0 就开始制作多字重的 Android 字体,那时候只需简单仿写谷歌对英文字体的控制方法,然后手动扩展一下缺失的几个字重文件即可。而 Android  7.0 之后,包括现在的 Android Q,虽然类似的方法可以继续用,但是得额外多一些 ttc 格式字体的制作成本,不然无法做到真正全局替换,某些顽固的 App 和系统界面还会强制调用 ttc。在这个过程中,我们还不能全都改用 TrueType Collection,因为旧系统只支持 OpenType 和 TrueType。

为了两全其美,我就继续在高版本系统用 otf,同时插一段 ttc 的代码做字体回退。

最终完美兼容 Android 5.0 到 Android Q 通用中文字体解决方案如下:

<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></family><family lang="zh-Hans">    <font weight="400" style="normal" index="2">NotoSansCJK-Regular.ttc</font></family>

除了系统原有的 Regular,这套字体调用机制增加了包含 Thin、Light、Medium、Bold 在内的共计 5 套字重,没有加入 Black 的原因是中文环境基本没有用到那么粗的情况。事实上,大多数情况下我们连 Thin 也用不到,只不过我同时还要做 iOS 的字体,苹果系统的通知中心等 UI 界面需要这个Thin,就一起用了。

早些时候在我公众号以及极限、知乎等社区有分享过这个方案,现在看很多人的 Magisk 字体模块也都有我方案的影子。毕竟再怎么像,文件的命名规则、Android 7.0 之后仍然在用 otf 的奇怪思路、还有中文有 Thin 却没有 Bold 的诡异设置,别人不可能和我想得一模一样。

关于 ttc 格式字体的制作,限于篇幅我就不赘述了。需要涉及到 ttf name 和 Adobe Font Development Kit 及一定的脚本编写基础,而补字库和修改字型则可以用 font creator、font forge、font lab studio 等工具完成。

到这里,我们算是完成了 Android 字体调用规则的科普,回顾一下,要想实现完美的中文字体效果,我们至少需要考虑这样几个问题:

  1. 想要全局替换多字重的中英文,是不是要手动一个个换进去?
  2. 中文字体的体积很大,手机的系统分区放不下那么多字重文件怎么办?
  3. 每次换字体都要重新换一遍吗,有没有一键操作?

OK,我们来看下这两个问题的优先程度。

下图是我修改完成的思源黑体中文五字重以及 GoogleSans 英文数字 6 字重后,全部 39 个需要替换的文件。他们合计达到了 100MB:

需要替换的素材合计达到了100MB

如果手动替换进系统,文件数量也忒多了一些,使用 TWRP 卡刷勉强能解决这个问题(直接开机状态用RE管理器覆盖的话,可能操作到一半系统发现原来的字体文件读取不到,可能就重启了,非常不方便)。

另一方面,也并不是所有机型都能额外塞下这 100M 大小的字体文件,我之前用一加 5T 的时候就发现,官方只留了大概 70~80M 的空间,不对系统组件进行精简的前提下很多字体都刷不进去。于是以上两个问题就引发了第三个问题:换字体太麻烦了

好在 Magisk 的模块挂载机制就能解决这所有的问题,而刷入字体模块几乎是一键完成,不占多余的系统分区空间,却能达到替换系统文件一样的效果

由于制作 Magisk 模块字体还会涉及到一些简单的代码,有兴趣的朋友可以自己研究。我在这里直接提供一个思源黑体模板,大家可以直接刷入实现多字重效果或按需进行手动修改——按照前文的教程修改 fonts.xml 这个配置并附上 system/fonts 的配套字体文件,即可换上自己想要的字体。

思源黑体5字重+GoogleSans英文6字重Magisk模块(V2.000)

提取码:njzy

另外在上面的 Magisk 模块模板中有一个 module.prop 文件,里面有两行关键代码:

id=201812012322name=思源黑体5字重+GoogleSans英文【宁静之雨模板】

第一行表示独一无二的模块 id,相同 id 的模块刷入是会覆盖的,记得修改一下;第二行当然就是模块显示的名字了,按需调整即可。

做好 Magisk 字体模块后,在 Magisk 里刷上字体模块,勾选重启即可。唯一的注意事项就是不要同时选多个字体模块,否则系统会随机选其中一个生效。另外,有一些模块的作者也会修改字体配置文件:比如在任意手机上开启 Google Pixel 手机某些特性的模块里,作者可能就会加上 Google Sans 字体家族的扩展,这里就会和我们的字体模块产生冲突。

安装 Magisk 字体模块后我们可以随意勾选切换,重启即可生效。完美的多字重扩展且不占系统空间,灵活方便,这就是选则 Magisk 替换字体的原因!

关联阅读:

> 下载少数派 客户端、关注 少数派公众号,发现更多 Android 玩机技巧 😃
> 特惠、好用的硬件产品,尽在 少数派sspai官方店铺 🛒