Android字库介绍

Andoridの既存Font

    Android4.4 既存的Font保存在frameworks/base/data/fonts目录下,除了包含一些字库外,还包含一些Font配置文件


    system_fonts.xml是Android默认Font的配置文件


    


    Android4.4 既存的Font如下:


    AndroidClock_Highlight.ttf


    AndroidClock_Solid.ttf


    AndroidClock.ttf


    AndroidEmoji.ttf


    Clockopia.ttf


    DroidKufi-Bold.ttf


    DroidKufi-Regular.ttf


    DroidNaskh-Bold.ttf


    DroidNaskh-Regular.ttf


    DroidNaskhUI-Regular.ttf


    DroidSansArabic.ttf


    DroidSansArmenian.ttf


    DroidSans-Bold.ttf


    DroidSansEthiopic-Bold.ttf


    DroidSansEthiopic-Regular.ttf


    DroidSansFallbackFull.ttf


    DroidSansFallbackLegacy.ttf


    DroidSansFallback.ttf


    DroidSansGeorgian.ttf


    DroidSansHebrew-Bold.ttf


    DroidSansHebrew-Regular.ttf


    DroidSansJapanese.ttf


    DroidSansMono.ttf


    DroidSans.ttf


    DroidSerif-BoldItalic.ttf


    DroidSerif-Bold.ttf


    DroidSerif-Italic.ttf


    DroidSerif-Regular.ttf


    MTLc3m.ttf


    MTLmr3m.ttf


    Roboto-BoldItalic.ttf


    Roboto-Bold.ttf


    RobotoCondensed-BoldItalic.ttf


    RobotoCondensed-Bold.ttf


    RobotoCondensed-Italic.ttf


    RobotoCondensed-Regular.ttf


    Roboto-Italic.ttf


    Roboto-LightItalic.ttf


    Roboto-Light.ttf


    Roboto-Regular.ttf


    Roboto-ThinItalic.ttf


    Roboto-Thin.ttf


    


    注: Clock表示时钟所用的字体



         Emoji表示绘文字所用的字体


        DroidSans表示无衬线字体


         DroidSansMono.ttf 等宽的无衬线字体


         DroidSerif表示衬线字体


         Condensed表示宽度比较窄的字体


         Thin表示字体线比较细的字体


         Roboto是Android4.x后,Google提供的全新的字体,相比原来的DroidSans多了两个斜体。主要用于替换原来的DroidSans。


        对于具体字体的信息,可以通过打开ttf文件,查看字体的显示效果。


    


    Android将Font分为多个Family,每个Family包含4种Style,分别为正常字体,粗体,斜体,粗体斜体。这4种Style不都是必须的。


    在Font中指定的字体大小主要有12,18,24,36,48,60,72

Android系统字库加载

android字体由android2D图形引擎skia实现,并在Zygote的Preloading classes中对系统字体进行load。

相关涉及到:

android的启动过程

frameworks/base/core/java/com/android/internal/os/ZygoteInit.java中的preloadClasses方法,对/framework/base/preloaded-classes文件里面的类一一加载Class.forName("android.graphics.Typeface");

Class.forName()会加载类到DVM(JVM),同时加载static代码块。

 

*****************************************************************************************

java中class.forName和classLoader都可用来对类进行加载。前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。而classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象。

*****************************************************************************************

android.graphics.Typeface Static代码:

static {

       DEFAULT         = create((String)null,0);

       DEFAULT_BOLD    = create((String)null, Typeface.BOLD);

       SANS_SERIF      = create("sans-serif", 0);

       SERIF           =create("serif", 0);

        MONOSPACE      = create("monospace", 0);

 

        sDefaults = newTypeface[] {

           DEFAULT,

           DEFAULT_BOLD,

           create((String)null, Typeface.ITALIC),

           create((String)null, Typeface.BOLD_ITALIC),

        };

 

public static Typeface create(String familyName, int style) {

        return newTypeface(nativeCreate(familyName, style));

}

 

nativeCreate()是jni方法,其实现在Typeface.cpp和skFontHost_android.cpp,其中后者是skia针对android平台字体实现的port。以下是Typeface.cpp native方法的注册。

由此可以知道,在JAVA层默认会创建sans-serif,serif,monospace三种字体,并且通过create第一个参数为null,来创建默认字体的四种style:normal,bold,italic,bolditalic。

Typeface_create又进一步调用face = SkTypeface::CreateFromName(str.c_str(),style);来完成。

SkTypeface*SkTypeface::CreateFromName(const char name[], Style style) {

    return SkFontHost::CreateTypeface(NULL,name, style);

}

 最终会调用到SkFontHost_android.cpp中的createTypeface。该函数做了两件事:

1.      首先调用load_system_fonts()加载系统字库,该函数会首先判断相关的系统字库变量没有被初始化,没有则加载,否则什么都不做,load_system_fonts()加载的字库是由/system/etc/system_fonts.xml来进行配置的。

2.       在当前的系统字库里面查找与所要求最接近的字体,并返回

 

由此JAVA层与C层联系起来,而创建系统字库,对与JAVA层来说也只是返回C层的一个ref。可以用图2来表示android系统字库加载过程:

 

Zygote preloadClasses() -> Class.forName("android.graphics.Typeface")–> 执行Typeface.javastatic块 -> create创建默认字体 -> Typeface.cpp Typeface_create–> SkTypeface.cpp CreateFromName() -> SkFontHost.cpp load_system_fonts()加载系统字体

 

 SkFontHost_android.cpp重要结构体及方法

重要结构体介绍:

struct FontFamily {

    SkTDArray<constchar*>  fNames;

   SkTDArray<const char*> fFileNames;

    int order;

};

解析system_fonts.xml得到的结构体,fNames保存文件名称信息,fFileNames保存相应的文件名。

struct FontInitRec {

    constchar*         fFileName;

    const char*const*  fNames;     // null-terminated list

};

FontFamily转换后的结构体,同一个fontfamily第一个ttf文件保存所有的名称,其余的fNames为NULL。

struct FamilyRec {

    FamilyRec*  fNext;

    SkTypeface* fFaces[4];

 

    FamilyRec()

    {

        fNext = gFamilyHead;

        memset(fFaces, 0,sizeof(fFaces));

        gFamilyHead = this;

    }

};

保存同一个font family的节点,每个font family分配了四个face,分别对应为normal,bold,italic,bolditalic。

struct NameFamilyPair {

    const char* fName;      // we own this

    FamilyRec*  fFamily;   // we don't own this, we just reference it

 

    void construct(const charname[], FamilyRec* family) {

        fName = strdup(name);

        fFamily = family;   // we don't own this, so just record thereferene

    }

 

    void destruct() {

        free((char*)fName);

        // we don't own family, sojust ignore our reference

    }

};

记录name跟font family的对应关系。

重要方法流程:

SkFontHost::CreateTypeface(constSkTypeface* familyFace,                                      constchar familyName[], SkTypeface::Style style)流程:

1.       调用init_system_fonts初始化系统字体

2.       在当前全局列表中查找最接近的typeface并返回

 

init_system_fonts流程:

1.       调用load_font_info初始化相关变量

2.       调用get_name_and_style获取字体的属性,name style

3.       通过这些属性创建FileTypeface,并把这些字体信息保存到全局列表中

4.       将family 及 name信息添加进NameFamilyPairList中

其中步骤2 3 4是循环执行,直到所有的信息都加入进去。

 

load_font_info流程:

1.             调用getFontFamilies(fontFamilies);解析/system/etc/system_fonts.xml、/system/etc/fallback_fonts.xml、/vendor/etc/fallback_fonts.xml文件,并把相应信息保存在fontFamilies。fallback_fonts.xml是当相应的字库找不到时,会继续找的字体,vendor一般为第三方厂商提供。fontFamilies保存了ttf文件的文件名字及名称。

2.             将fontFamily结构转换成FontInitRec结构,主要作用是:同一个fontfamily第一个出现的字体保存所有的名称,后续字体的名称均设置为NULL,以标致是同一个font family。

3.             将转换后的结果保存在gSystemFonts,并用gNumSystemFonts记录当前系统字体个数。

   

由此可知,通过名称或者familyface来创建Typeface的API,一般用于系统字体的创建,因为系统字体是一定会在列表中的,自定义字体则不一定会在列表中,主要看该字体之前是否被打开过,且没有被删除,而如果没有在列表中,则会选一个跟所需要的字体比较接近的字体来返回。

 

SkFontHost::CreateTypefaceFromFile(const char path[])流程:

1.       调用SkMMAPStream函数将指定文件映射到内存空间,以进程共享读的方式

2.       调用SkFontHost::CreateTypefaceFromStream(stream);创建字体

 

SkFontHost::CreateTypefaceFromStream(SkStream* stream)流程:

1.       调用find_name_and_attributes获取字体style

2.       调用init_system_fonts初始化系统字体

3.       调用StreamTypeface构造函数创建Typeface,并把信息保存到相应的链表中。

    getFontFamilies

        从/system/etc/system_fonts.xml读取字体信息,并加载字体信息,保存到 SkTDArray<FontFamily*> fontFamilies;

        然后遍历 fontFamilies,将数据保存到gSystemFonts。


    gDefaultNames保存里第一个<family>标签里的<nameset>。

 

    SkNEW_ARGS:创建每种字体FileTypeface

    addTypefaceLocked:把创建的字体保存到gFamilyHead链表,gFamilyHead每个节点保存了一个字体family,包含常规、粗体、斜体、粗斜体4种类型。

    gdefaultFamily 保存里第一个<family>标签里的字体。

    gdefaultNormal 默认字库normal类型