前言
之前在处理系统字体问题的时候,可借鉴的资料很少,遇到了很多坑,不得不了解Android字体加载原理,现抽空写一篇总结,来加深自己对这块的理解。
内容
概述
Android字体系统是由底层的Android 2D图形引擎Skia来实现的,Android3.0之后逐渐使用了新的硬件绘图模块hwui,在5.0之后正式取代了Skia,因此不同版本的系统其字体加载机制有些差异,按照Google的API Level来看,大体可以分为三个阶段:
- Android4.0以下的系统
- Android4.0到Android4.4的系统
- Android5.0以上的系统
当然这每个阶段中,可能也存在些许小差异,但大方向是没变化的,本文主要对Android5.0以上的系统的字体加载机制进行描述,围绕系统字体配置文件解析与字体加载相关内容,不涉及系统运行库的实现细节。
注:浏览器及webView中的字体有单独的字体系统
下面将从Java层面、Native层面、文件配置系统三个部分来阐述Android字体加载原理。
Java层面
有研究过Android的人大概都有了解,Android的Java层封装了构建应用程序时可能会用到的各种Api。而在字体这部分,起主要作用的是android.graphics.Typeface,其主要负责字体加载以及对上层提供创建字体功能的调用,下面将着重分析该类的调用过程。
首先,在Android启动的过程中,ZygoteInit类中的main()方法会调用加载方法preload(),对各种类、链接库、资源等进行初始化,具体代码如下:
|
|
其中preloadClasses()方法会加载并初始化一些系统常用的API类,这些类都是位于frameworks/base/preloaded-classes文件中,当然也包括Typeface类。
|
|
从上面的代码可以看到,Android通过反射机制Class.forName(“android.graphics.Typeface”)加载了Typeface类,在加载的同时,会调用类中的static方法块。如下:
|
|
在上面的static方法块中,最终通过调用Native层方法nativeCreateFromTypeface(),来初始化系统字体并且设置默认的系统字体以及字体样式,可以从上面的方法看出系统默认创建sans-serif(无衬线字体),serif(衬线字体),monospace(等宽字体)三种字体,并且通过create第一个参数为null,来创建默认字体的四种style:normal,bold,italic,bolditalic。
注:这里需要注意的是,Android4.x版本的系统与Android5.0以上的版本所调用的API基本一致,但是native层确有很大的变,这是由于5.0以上的系统添加了一个新的方法init(),其主要实现了解析系统字体配置文件,并据此加载系统字体。而Android4.x版本是在native层实现的。
因为现在Android阵营已经基本上都是5.0以上的系统了,所以5.0以下版本的加载不在解释。下面我们来看init()方法的具体逻辑:
|
|
通过以上代码,可以看出,系统解析过程中,一共有三种字体模式。一种的是系统默认字体;一种是系统字体,所有字体,包括自己添加的字体;一种是设置别名的字体,字体的衍生。而这三种字体都会在init()中被加载,而它们加载主要涉及以下方法。
|
|
|
|
从上面的代码可看到,系统通过解析/system/etc/fonts.xml(字体配置文件),然后接收Native层方法回调上来的值,来创建指定的Typeface即字体,保存在sSystemFontMap中。而相关native方法列表以及注册(在frameworks/base/core/jni/android/graphics/Typeface.cpp中注册)如下:
|
|
最终,通过这一层的关系,调用到Native层的方法。
到此,字体加载Java层面就结束了,下面将调用Native层的方法。
Native层面
Native层主要是skia图形引擎的Android移植版,项目源码位于external\skia目录下。
在Android4.X版本中主要是用skia来进行软件绘制,所以解析配置文件并加载字体是在skia中完成,这里不在描述过程,可以参看相关博客中的描述。而由于绘制性能等问题,Android5.0之后使用了新的硬件绘图模块hwui,hwui主要则是使用opengles来进行gpu硬件绘图,提升整个系统的绘制性能。
在上述Java层调用过程后,字体加载指向了Native层。在Native层调用首先进入jni/android/graphics/Typeface.cpp,调用对应的方法,然后进入hwui/Typeface.h和hwui/Typeface.cpp中定制的函数,从而解析配置文件并加载字体。
|
|
|
|
Native层的c/c++方法调用比较复杂,通过一系列的调用,返回值给Java层,这里就不在阐述,有兴趣的人可以自己下个源码深入理解下,到这里Android的字体加载原理基本完成了,不得不感叹Google工程师的丰功伟绩。
文件配置系统
前面介绍的是加载的原理,现在简单的描述下字体加载过程中所用到的字体加载文件。
在4.x版本的系统字体配置文件位于system/etc/system_fonts.xml,备用字体配置文件位于system/etc/fallback_fonts.xml和vendor/etc/fallback_fonts.xml。而5.0以上的版本的系统字体及备用字体配置均位于system/etc/fonts.xml文件中,下面展示部分fonts.xml内容。
|
|
如上所示,第一个family节点为系统默认字体。nameset节点的各个name子节点定义可用的字体名称,fileset节点的file子节点分别对应normal、bold、italic、bold-italic四种字体样式,如果file节点个数少于四个,相应字体样式会对应已有兄弟file节点的字体文件。family属性中lang代表国家的缩写,系统在切换语言的时候会从加载的字体中匹配国家的缩写,从而调出对于的系统字体、variant属性指的是字体的排列格式通常有compact(紧凑型)以及(简洁型)。
fallback_fonts配置了系统备用字体。只有在系统内置字体中找不到相应字符时,才会到备用字体中去寻找,family节点的顺序对应搜索顺序,搜索匹配规则采用BCP47的定义。
5.0以后的字体配置文件与之前版本的相比,最大的一个改进是将之前字体样式中的单一bold样式改为各种不同过的weight,这样可以更加细粒度的控制字重。
为系统添加新的字体
现在的手机产商都对Android系统进行了定制,当然也会加上属于自己的字体,下面简单描述下添加新字体的流程,以缅甸字体为例。
1.在frameworks/base/data/fonts/fonts.xml中添加字体节点
|
|
2.在frameworks/base/data/fonts/fonts.mk的最后加入新加的字体文件
|
|
3.在frameworks/base/data/fonts/Android.mk的font_src_files最后加入新加的字体文件
|
|
4.将下载的字体放入frameworks/base/data/fonts下
其中第2、第3步是为了让字体能够编译进入系统中。
参考博客
版权声明:本文为博主原创文章,转载请注明出处KidSea