1.引言
最近老大安排一个任务,让我看看android 字体这块,将我们产品中的字体替换下。花了1.2天看懂,还得写篇文章,教程在组内进行分享。这次算是我进军Android系统的第一步。这篇文章基于Android 8.0
参考链接:https://kidsea.github.io/2017/08/12/Android字体加载原理总结/
2.正题
2.1 font.xml字段描述
系统字体相关的文件、文件夹:fonts文件 和font.xml配置文件
系统字体存放在: System/fonts/文件夹下
系统字体配置文件 font.xml 存放在:System/etc目录下。font.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>
<!-- fallback fonts -->
<family lang="und-Arab" variant="elegant">
<font weight="400" style="normal">NotoNaskhArabic-Regular.ttf</font>
<font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
</family>
<family lang="und-Arab" variant="compact">
<font weight="400" style="normal">NotoNaskhArabicUI-Regular.ttf</font>
<font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
</family>
<family lang="und-Ethi">
<font weight="400" style="normal">NotoSansEthiopic-Regular.ttf</font>
<font weight="700" style="normal">NotoSansEthiopic-Bold.ttf</font>
</family>
<!-- 简体中文字体 -->
<family lang="zh-Hans">
<font weight="400" style="normal">NotoSansSC-Regular.otf</font>
</family>
<!-- 繁体中文字体 -->
<family lang="zh-Hant">
<font weight="400" style="normal">NotoSansTC-Regular.otf</font>
</family>
-
family
name:源码中通过name去找到font族
lang: 国家语言 -
font
weight:字体的粗细style: 字体类型 普通、加粗,斜体
index:从0开始,表示备用字体的序号,0优先加载
字体匹配规则:
-
当是中文的时候会寻找中文也就是zh的配置,其中
zh-hans
是简体,zh-hant
是繁体。原生系统指定的备用字体是NotoSerifCJK-Regular.ttc
。ttc是多种字体的组合字体,上述的字体是中日韩三国的综合字体。 -
Roboto是不支持加载中文字体的。我把下图标注的字体换成Roboto-Italic.ttf之后,发现字体还真的是能显示出斜体来。这是不是说明“ Roboto”支持中文斜体呢?
其实不是,我们把文件fonts中的字体都删除只保留Roboto的字体。发现中文字体集体乱码显示不出来。由此我们可以推断。当加载中文的时候,Roboto会主动去用NotoSerifCJK-Regular.ttc来加载中文。验证方法将NotoSerifCJK-Regular.ttc 添加到fonts文件中之后。中文就显示出来了。
- 删除font.xml的 zh-hans/zh-hant配置之后,发现依旧能正常显示中文。说明加载中文时,系统会自动加载NotoSerifCJK-Regular.ttc字体。
知道了这些规则之后。如何更改系统中文字体呢?
我们只需要替换如下的配置:
<family lang="zh-Hans">
<font weight="400" style="normal" index="0">自己的ttf</font>
//字体加粗的中文字体,不加这句,会以NotoSerifCJK来显示加粗字体
<font weight="700" style="normal" index="0">自己的ttf</font>
</family>
<!-- 繁体中文字体 -->
<family lang="zh-Hant">
<font weight="400" style="normal">NotoSansTC-Regular.otf</font>
<font weight="700" style="normal" index="0">自己的ttf</font>
</family>
到此为止,替换系统字体就会生效。
上文说到:weight==400是android系统中正常字体的粗细,weight==700表示是加粗的字体。系统是在哪里加载的字体呢??是不是我们更改下代码,就能达到weight==500的时候显示正常字体,weight==100的时候字体加粗呢?
TypeFace.java
static {
final HashMap<String, Typeface> systemFontMap = new HashMap<>();
initSystemDefaultTypefaces(systemFontMap, SystemFonts.getRawSystemFallbackMap(),
SystemFonts.getAliases());
sSystemFontMap = Collections.unmodifiableMap(systemFontMap);
// We can't assume DEFAULT_FAMILY available on Roboletric.
if (sSystemFontMap.containsKey(DEFAULT_FAMILY)) {
setDefault(sSystemFontMap.get(DEFAULT_FAMILY));//创建默认“sans-serif”的TypeFace
}
// Set up defaults and typefaces exposed in public API
DEFAULT = create((String) null, 0);//正常粗细的“sans-serif”字体
DEFAULT_BOLD = create((String) null, Typeface.BOLD);//粗体 “sans-serif”类型的字体
SANS_SERIF = create("sans-serif", 0);
SERIF = create("serif", 0);
MONOSPACE = create("monospace", 0);
sDefaults = new Typeface[] {
DEFAULT,
DEFAULT_BOLD,
create((String) null, Typeface.ITALIC),
create((String) null, Typeface.BOLD_ITALIC),
};
// A list of generic families to be registered in native.
// https://www.w3.org/TR/css-fonts-4/#generic-font-families
String[] genericFamilies = {
"serif", "sans-serif", "cursive", "fantasy", "monospace", "system-ui"
};
for (String genericFamily : genericFamilies) {
registerGenericFamilyNative(genericFamily, systemFontMap.get(genericFamily));
}
}
点进Create方法,nativeCreateFromTypeface native 方法来初始化系统字体并且设置默认的系统字体以及字体样式。
public static Typeface create(Typeface family, @Style int style) {
if ((style & ~STYLE_MASK) != 0) {
style = NORMAL;
}
if (family == null) {
family = sDefaultTypeface;
}
// Return early if we're asked for the same face/style
if (family.mStyle == style) {
return family;
}
final long ni = family.native_instance;
Typeface typeface;
synchronized (sStyledCacheLock) {
SparseArray<Typeface> styles = sStyledTypefaceCache.get(ni);
if (styles == null) {
styles = new SparseArray<Typeface>(4);
sStyledTypefaceCache.put(ni, styles);
} else {
typeface = styles.get(style);
if (typeface != null) {
return typeface;
}
}
typeface = new Typeface(nativeCreateFromTypeface(ni, style));
styles.put(style, typeface);
}
return typeface;
}
/framwork/base/core/jni/android/graphics/Typeface.cpp#nativeCreateFromTypeface
static jlong Typeface_createFromTypeface(JNIEnv* env, jobject, jlong familyHandle, jint style) {
Typeface* family = toTypeface(familyHandle);
Typeface* face = Typeface::createRelative(family, (Typeface::Style)style);
// TODO: the following logic shouldn't be necessary, the above should always succeed.
// Try to find the closest matching font, using the standard heuristic
if (NULL == face) {
face = Typeface::createRelative(family, (Typeface::Style)(style ^ Typeface::kItalic));
}
for (int i = 0; NULL == face && i < 4; i++) {
face = Typeface::createRelative(family, (Typeface::Style)i);
}
return toJLong(face);
}
进一步调用真正的创建类:
/framwork/base/libs/hwui/hwui/Typeface.cpp# createRelative
Typeface* Typeface::createFromFamilies(std::vector<std::shared_ptr<minikin::FontFamily>>&& families,
int weight, int italic) {
Typeface* result = new Typeface;
result->fFontCollection.reset(new minikin::FontCollection(families));
if (weight == RESOLVE_BY_FONT_TABLE || italic == RESOLVE_BY_FONT_TABLE) {
int weightFromFont;
bool italicFromFont;
const minikin::FontStyle defaultStyle;
const minikin::MinikinFont* mf =
families.empty()
? nullptr
: families[0]->getClosestMatch(defaultStyle).font->typeface().get();
if (mf != nullptr) {
SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(mf)->GetSkTypeface();
const SkFontStyle& style = skTypeface->fontStyle();
weightFromFont = style.weight();
italicFromFont = style.slant() != SkFontStyle::kUpright_Slant;
} else {
// We can't obtain any information from fonts. Just use default values.
weightFromFont = SkFontStyle::kNormal_Weight;//默认值400
italicFromFont = false;
}
if (weight == RESOLVE_BY_FONT_TABLE) {
weight = weightFromFont;
}
if (italic == RESOLVE_BY_FONT_TABLE) {
italic = italicFromFont ? 1 : 0;
}
}
由上面的流程可以看到,系统默认的确还是400,加粗是在base-weight上加300.
上面我们知道了,TypeFace是系统加载默认字体的地方,通过jni调用告诉底层字体样式。也知道了代码中正常字体粗细是400.加粗是700。又有一个问题:TypeFace是在哪里加载的呢?
android系统启动是通过解析init.rc文件。进一步得到Zygote进程,ZygoteInit进程会产生System_Server进程。且Zygote进程会通过反射进入ZygoteInit.java main()方法。这是底层进入到java层的第一个方法。
main方法中调用了preload()方法:
image.pngpreload又会调用preloadClass() 预加载基础类。
/**
* The path of a file that contains classes to preload.
*/
private static final String PRELOADED_CLASSES = "/system/etc/preloaded-classes";
preloaded-classes 是一个xml,里面写入了成千上万个全路径类名。存在于System/etc 下
好了,今天的问题都弄明白了。下一期分享开机动画的知识。
网友评论