美文网首页
Android替换系统字体

Android替换系统字体

作者: 过期的薯条 | 来源:发表于2020-09-06 15:18 被阅读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>
    
    
    • 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”支持中文斜体呢

    image.png

    其实不是,我们把文件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.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 下

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

    相关文章

      网友评论

          本文标题:Android替换系统字体

          本文链接:https://www.haomeiwen.com/subject/lyqcektx.html