Android替换系统字体

2020-09-06  本文已影响0人  过期的薯条

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>


字体匹配规则:

image.png

其实不是,我们把文件fonts中的字体都删除只保留Roboto的字体。发现中文字体集体乱码显示不出来。由此我们可以推断。当加载中文的时候,Roboto会主动去用NotoSerifCJK-Regular.ttc来加载中文。验证方法将NotoSerifCJK-Regular.ttc 添加到fonts文件中之后。中文就显示出来了。

知道了这些规则之后。如何更改系统中文字体呢

我们只需要替换如下的配置:


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

preload又会调用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 下

好了,今天的问题都弄明白了。下一期分享开机动画的知识。

上一篇下一篇

猜你喜欢

热点阅读