浅析Android字体加载原理

前言

之前在处理系统字体问题的时候,可借鉴的资料很少,遇到了很多坑,不得不了解Android字体加载原理,现抽空写一篇总结,来加深自己对这块的理解。

内容

概述

Android字体系统是由底层的Android 2D图形引擎Skia来实现的,Android3.0之后逐渐使用了新的硬件绘图模块hwui,在5.0之后正式取代了Skia,因此不同版本的系统其字体加载机制有些差异,按照Google的API Level来看,大体可以分为三个阶段:

  1. Android4.0以下的系统
  2. Android4.0到Android4.4的系统
  3. Android5.0以上的系统

当然这每个阶段中,可能也存在些许小差异,但大方向是没变化的,本文主要对Android5.0以上的系统的字体加载机制进行描述,围绕系统字体配置文件解析与字体加载相关内容,不涉及系统运行库的实现细节。

注:浏览器及webView中的字体有单独的字体系统

下面将从Java层面、Native层面、文件配置系统三个部分来阐述Android字体加载原理。

Java层面

有研究过Android的人大概都有了解,Android的Java层封装了构建应用程序时可能会用到的各种Api。而在字体这部分,起主要作用的是android.graphics.Typeface,其主要负责字体加载以及对上层提供创建字体功能的调用,下面将着重分析该类的调用过程。

首先,在Android启动的过程中,ZygoteInit类中的main()方法会调用加载方法preload(),对各种类、链接库、资源等进行初始化,具体代码如下:


public static void main(String argv[]) {...registerZygoteSocket(socketName);Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ZygotePreload");EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,SystemClock.uptimeMillis());//调用加载方法preload();EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,SystemClock.uptimeMillis());Trace.traceEnd(Trace.TRACE_TAG_DALVIK);...}//主要用于加载并初始化各种类、链接库、资源等。
static void preload() {Log.d(TAG, "begin preload");//Systrace开始tagTrace.traceBegin(Trace.TRACE_TAG_DALVIK, "BeginIcuCachePinning");//开始Icu缓存开销beginIcuCachePinning();//Systrace结束tagTrace.traceEnd(Trace.TRACE_TAG_DALVIK);Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadClasses");//预加载ClassespreloadClasses();Trace.traceEnd(Trace.TRACE_TAG_DALVIK);Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadResources");//预加载resourcespreloadResources();Trace.traceEnd(Trace.TRACE_TAG_DALVIK);Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");//预加载openGLpreloadOpenGL();Trace.traceEnd(Trace.TRACE_TAG_DALVIK);//加载分享库preloadSharedLibraries();//加载文本资源preloadTextResources();// Ask the WebViewFactory to do any initialization that must run in the zygote process,// for memory sharing purposes.、WebViewFactory.prepareWebViewInZygote();endIcuCachePinning();warmUpJcaProviders();Log.d(TAG, "end preload");
}

其中preloadClasses()方法会加载并初始化一些系统常用的API类,这些类都是位于frameworks/base/preloaded-classes文件中,当然也包括Typeface类。

/*** Performs Zygote process initialization. Loads and initializes* commonly used classes.** Most classes only cause a few hundred bytes to be allocated, but* a few will allocate a dozen Kbytes (in one case, 500+K).*/
private static void preloadClasses() {...    InputStream is;try {is = new FileInputStream(PRELOADED_CLASSES);} catch (FileNotFoundException e) {Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");return;}...try {BufferedReader br= new BufferedReader(new InputStreamReader(is), 256);int count = 0;String line;while ((line = br.readLine()) != null) {// Skip comments and blank lines.line = line.trim();if (line.startsWith("#") || line.equals("")) {continue;}Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadClass " + line);try {if (false) {Log.v(TAG, "Preloading " + line + "...");}// Load and explicitly initialize the given class. Use// Class.forName(String, boolean, ClassLoader) to avoid repeated stack lookups// (to derive the caller's class-loader). Use true to force initialization, and// null for the boot classpath class-loader (could as well cache the// class-loader of this class in a variable).Class.forName(line, true, null);count++;...}

从上面的代码可以看到,Android通过反射机制Class.forName(“android.graphics.Typeface”)加载了Typeface类,在加载的同时,会调用类中的static方法块。如下:


static {//初始化系统字体init();// Set up defaults and typefaces exposed in public APIDEFAULT         = 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 = new Typeface[] {DEFAULT,DEFAULT_BOLD,create((String) null, Typeface.ITALIC),create((String) null, Typeface.BOLD_ITALIC),};
}public static Typeface create(String familyName, int style) {if (sSystemFontMap != null) {return create(sSystemFontMap.get(familyName), style);}return null;
}public static Typeface create(Typeface family, int style) {...typeface = new Typeface(nativeCreateFromTypeface(ni, style));...return typeface;
}

在上面的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()方法的具体逻辑:


/** (non-Javadoc)** This should only be called once, from the static class initializer block.*/
private static void init() {// Load font config and initialize Minikin state//获取系统字体配置文件位置放置于system/etc目录下File systemFontConfigLocation = getSystemFontConfigLocation();//获取配置文件fonts.xmlFile configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);//以下代码是对fonts.xml的解析,即是对系统字体的解析try {FileInputStream fontsIn = new FileInputStream(configFilename);FontListParser.Config fontConfig = FontListParser.parse(fontsIn);Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>();//用来承载fonts.xml中的每个family节点List<FontFamily> familyList = new ArrayList<FontFamily>();// Note that the default typeface is always present in the fallback list;// this is an enhancement from pre-Minikin behavior.//从每个family节点中解析字体样式,这里解析系统默认字体for (int i = 0; i < fontConfig.families.size(); i++) {FontListParser.Family f = fontConfig.families.get(i);if (i == 0 || f.name == null) {familyList.add(makeFamilyFromParsed(f, bufferForPath));}}//系统默认字体集合sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]);//设置默认系统字体setDefault(Typeface.createFromFamilies(sFallbackFonts));//这里加载系统字体,包括默认字体Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();for (int i = 0; i < fontConfig.families.size(); i++) {Typeface typeface;FontListParser.Family f = fontConfig.families.get(i);if (f.name != null) {if (i == 0) {// The first entry is the default typeface; no sense in// duplicating the corresponding FontFamily.typeface = sDefaultTypeface;} else {//从每个family节点中解析字体FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath);FontFamily[] families = { fontFamily };typeface = Typeface.createFromFamiliesWithDefault(families);}//解析的字体添加到系统字体中systemFonts.put(f.name, typeface);}}//通过权重别号解析字体,别名必须与字体对应for (FontListParser.Alias alias : fontConfig.aliases) {Typeface base = systemFonts.get(alias.toName);Typeface newFace = base;int weight = alias.weight;if (weight != 400) {newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));}systemFonts.put(alias.name, newFace);}//系统字体集合sSystemFontMap = systemFonts;} catch (RuntimeException e) {Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);// TODO: normal in non-Minikin case, remove or make error when Minikin-only} catch (FileNotFoundException e) {Log.e(TAG, "Error opening " + configFilename, e);} catch (IOException e) {Log.e(TAG, "Error reading " + configFilename, e);} catch (XmlPullParserException e) {Log.e(TAG, "XML parse exception for " + configFilename, e);}
}

通过以上代码,可以看出,系统解析过程中,一共有三种字体模式。一种的是系统默认字体;一种是系统字体,所有字体,包括自己添加的字体;一种是设置别名的字体,字体的衍生。而这三种字体都会在init()中被加载,而它们加载主要涉及以下方法。


//通过family节点解析FontFamily
private static FontFamily makeFamilyFromParsed(FontListParser.Family family,Map<String, ByteBuffer> bufferForPath) {//这里的lang表示国家缩写,variant表示字体的排列格式一般有compact与elegant两种FontFamily fontFamily = new FontFamily(family.lang, family.variant);for (FontListParser.Font font : family.fonts) {ByteBuffer fontBuffer = bufferForPath.get(font.fontName);if (fontBuffer == null) {try (FileInputStream file = new FileInputStream(font.fontName)) {FileChannel fileChannel = file.getChannel();long fontSize = fileChannel.size();fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);bufferForPath.put(font.fontName, fontBuffer);} catch (IOException e) {Log.e(TAG, "Error mapping font file " + font.fontName);continue;}}if (!fontFamily.addFontWeightStyle(fontBuffer, font.ttcIndex, font.axes,font.weight, font.isItalic)) {Log.e(TAG, "Error creating font " + font.fontName + "#" + font.ttcIndex);}}return fontFamily;
}
/*
以下是通过不同的格式解析出不同的family
*/
public FontFamily() {mNativePtr = nCreateFamily(null, 0);if (mNativePtr == 0) {throw new IllegalStateException("error creating native FontFamily");}
}public FontFamily(String lang, String variant) {int varEnum = 0;if ("compact".equals(variant)) {varEnum = 1;} else if ("elegant".equals(variant)) {varEnum = 2;}mNativePtr = nCreateFamily(lang, varEnum);if (mNativePtr == 0) {throw new IllegalStateException("error creating native FontFamily");}
}@Override
protected void finalize() throws Throwable {try {nUnrefFamily(mNativePtr);} finally {super.finalize();}
}public boolean addFont(String path, int ttcIndex) {try (FileInputStream file = new FileInputStream(path)) {FileChannel fileChannel = file.getChannel();long fontSize = fileChannel.size();ByteBuffer fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);return nAddFont(mNativePtr, fontBuffer, ttcIndex);} catch (IOException e) {Log.e(TAG, "Error mapping font file " + path);return false;}
}public boolean addFontWeightStyle(ByteBuffer font, int ttcIndex, List<FontListParser.Axis> axes,int weight, boolean style) {return nAddFontWeightStyle(mNativePtr, font, ttcIndex, axes, weight, style);
}public boolean addFontFromAsset(AssetManager mgr, String path) {return nAddFontFromAsset(mNativePtr, mgr, path);
}private static native long nCreateFamily(String lang, int variant);
private static native void nUnrefFamily(long nativePtr);
private static native boolean nAddFont(long nativeFamily, ByteBuffer font, int ttcIndex);
private static native boolean nAddFontWeightStyle(long nativeFamily, ByteBuffer font,int ttcIndex, List<FontListParser.Axis> listOfAxis,int weight, boolean isItalic);
private static native boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr,String path);

/*** Create a new typeface from an array of font families.** @param families array of font families* @hide*/
//通过FontFamily解析创建字体
public static Typeface createFromFamilies(FontFamily[] families) {long[] ptrArray = new long[families.length];for (int i = 0; i < families.length; i++) {ptrArray[i] = families[i].mNativePtr;}return new Typeface(nativeCreateFromArray(ptrArray));
}/*** Create a new typeface from an array of font families, including* also the font families in the fallback list.** @param families array of font families* @hide*/
//通过FontFamily解析创建字体
public static Typeface createFromFamiliesWithDefault(FontFamily[] families) {long[] ptrArray = new long[families.length + sFallbackFonts.length];for (int i = 0; i < families.length; i++) {ptrArray[i] = families[i].mNativePtr;}for (int i = 0; i < sFallbackFonts.length; i++) {ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr;}return new Typeface(nativeCreateFromArray(ptrArray));
}

从上面的代码可看到,系统通过解析/system/etc/fonts.xml(字体配置文件),然后接收Native层方法回调上来的值,来创建指定的Typeface即字体,保存在sSystemFontMap中。而相关native方法列表以及注册(在frameworks/base/core/jni/android/graphics/Typeface.cpp中注册)如下:


private static native long nativeCreateFromTypeface(long native_instance, int style);
private static native long nativeCreateWeightAlias(long native_instance, int weight);
private static native void nativeUnref(long native_instance);
private static native int  nativeGetStyle(long native_instance);
private static native long nativeCreateFromArray(long[] familyArray);
private static native void nativeSetDefault(long native_instance);///static const JNINativeMethod gTypefaceMethods[] = {{ "nativeCreateFromTypeface", "(JI)J", (void*)Typeface_createFromTypeface },{ "nativeCreateWeightAlias",  "(JI)J", (void*)Typeface_createWeightAlias },{ "nativeUnref",              "(J)V",  (void*)Typeface_unref },{ "nativeGetStyle",           "(J)I",  (void*)Typeface_getStyle },{ "nativeCreateFromArray",    "([J)J",(void*)Typeface_createFromArray },{ "nativeSetDefault",         "(J)V",   (void*)Typeface_setDefault },
};int register_android_graphics_Typeface(JNIEnv* env)
{return RegisterMethodsOrDie(env, "android/graphics/Typeface", gTypefaceMethods,NELEM(gTypefaceMethods));
}

最终,通过这一层的关系,调用到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中定制的函数,从而解析配置文件并加载字体。

//jni/android/graphics/Typeface.cpp#include "jni.h"
#include "core_jni_helpers.h"#include "GraphicsJNI.h"
#include "ScopedPrimitiveArray.h"
#include "SkTypeface.h"
#include <android_runtime/android_util_AssetManager.h>
#include <androidfw/AssetManager.h>
#include <hwui/Typeface.h>using namespace android;static jlong Typeface_createFromTypeface(JNIEnv* env, jobject, jlong familyHandle, jint style) {Typeface* family = reinterpret_cast<Typeface*>(familyHandle);Typeface* face = Typeface::createFromTypeface(family, (SkTypeface::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 heuristicif (NULL == face) {face = Typeface::createFromTypeface(family, (SkTypeface::Style)(style ^ SkTypeface::kItalic));}for (int i = 0; NULL == face && i < 4; i++) {face = Typeface::createFromTypeface(family, (SkTypeface::Style)i);}return reinterpret_cast<jlong>(face);
}static jlong Typeface_createWeightAlias(JNIEnv* env, jobject, jlong familyHandle, jint weight) {Typeface* family = reinterpret_cast<Typeface*>(familyHandle);Typeface* face = Typeface::createWeightAlias(family, weight);return reinterpret_cast<jlong>(face);
}static void Typeface_unref(JNIEnv* env, jobject obj, jlong faceHandle) {Typeface* face = reinterpret_cast<Typeface*>(faceHandle);if (face != NULL) {face->unref();}
}static jint Typeface_getStyle(JNIEnv* env, jobject obj, jlong faceHandle) {Typeface* face = reinterpret_cast<Typeface*>(faceHandle);return face->fSkiaStyle;
}static jlong Typeface_createFromArray(JNIEnv *env, jobject, jlongArray familyArray) {ScopedLongArrayRO families(env, familyArray);std::vector<FontFamily*> familyVec;for (size_t i = 0; i < families.size(); i++) {FontFamily* family = reinterpret_cast<FontFamily*>(families[i]);familyVec.push_back(family);}return reinterpret_cast<jlong>(Typeface::createFromFamilies(familyVec));
}static void Typeface_setDefault(JNIEnv *env, jobject, jlong faceHandle) {Typeface* face = reinterpret_cast<Typeface*>(faceHandle);return Typeface::setDefault(face);
}
//hwui/Typeface.h#ifndef _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_
#define _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_#include "SkTypeface.h"#include <cutils/compiler.h>
#include <minikin/FontCollection.h>
#include <vector>namespace android {struct ANDROID_API Typeface {FontCollection *fFontCollection;// style used for constructing and querying Typeface objectsSkTypeface::Style fSkiaStyle;// base weight in CSS-style units, 100..900int fBaseWeight;// resolved style actually used for renderingFontStyle fStyle;void unref();static Typeface* resolveDefault(Typeface* src);static Typeface* createFromTypeface(Typeface* src, SkTypeface::Style style);static Typeface* createWeightAlias(Typeface* src, int baseweight);static Typeface* createFromFamilies(const std::vector<FontFamily*>& families);static void setDefault(Typeface* face);
};
}
#endif  // _ANDROID_GRAPHICS_TYPEFACE_IMPL_H_

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内容。


<familyset version="22"><!-- first font is default --><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><!-- Note that aliases must come after the fonts they reference. --><alias name="sans-serif-thin" to="sans-serif" weight="100" /><alias name="sans-serif-light" to="sans-serif" weight="300" /><alias name="sans-serif-medium" to="sans-serif" weight="500" /><alias name="sans-serif-black" to="sans-serif" weight="900" /><alias name="arial" to="sans-serif" /><alias name="helvetica" to="sans-serif" /><alias name="tahoma" to="sans-serif" /><alias name="verdana" to="sans-serif" />...<!-- 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节点为系统默认字体。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中添加字体节点

<family lang="my"><font weight="400" style="normal">ZawgyiOne.ttf</font>
</family>

   2.在frameworks/base/data/fonts/fonts.mk的最后加入新加的字体文件

PRODUCT_COPY_FILES := \frameworks/base/data/fonts/fonts.xml:$(TARGET_COPY_OUT_SYSTEM)/etc/fonts.xmlPRODUCT_PACKAGES := \DroidSansFallback.ttf \DroidSansMono.ttf \AndroidClock.ttf \DINPro-Black.otf \DINPro-Bold.otf \DINPro-Light.otf \DINPro-Medium.otf \DINPro-Regular.otf \Flyme-Light.ttf \ZawgyiOne.ttf

   3.在frameworks/base/data/fonts/Android.mk的font_src_files最后加入新加的字体文件


font_src_files := \
    AndroidClock.ttf \
    Flyme-Light.ttf \
    ZawgyiOne.ttf

   4.将下载的字体放入frameworks/base/data/fonts下

其中第2、第3步是为了让字体能够编译进入系统中。

参考博客

   knight

   flyeek

浅析Android字体加载原理相关推荐

  1. Android系统字体加载流程

    一.背景 视觉同学提了一个需求,要求手机中显示的字体可以支持medium字体,经过分析,android原生的字体库中并没有中文的medium字体,如果使用bold,显示又太粗,为满足需求,需要分析an ...

  2. Android Webview使用自定义字体加载网页

    前言 有时,当我们使用Webview加载一个网页的时候,需要使用特定的字体来显示,这时就需要我们对页面做下处理! 方法 ①首先需要我们获得目标网页的HTML源码: URL url = new URL( ...

  3. Android 进阶——MultiDex分包与动态加载原理剖析

    一.为什么要进行Dex分包 Android下单个Dex文件存在65535函数数量的限制,而一个功能稍微复杂点的APP很容易超过这个限制,为此,我们引入了Dex分包,将一个App所有class分别打包为 ...

  4. Android MultiDex 分包及加载原理

    彻底了解 65536 方法数超限的问题根源,与官方的 MultiDex 方案原理. Problem 日常开发中,一旦项目变的庞大起来,很容易遇到如下的编译错误: trouble writing out ...

  5. Android动态加载技术初探

    一.前言: 现在,已经有实力强大的公司用这个技术开发应用了,比如淘宝,大众点评,百度地图等,之所以采用这个技术,实际上,就是方便更新功能,当然,前提是新旧功能的接口一致,不然会报Not Found等错 ...

  6. Android动态加载黑科技 动态创建Activity模式

    基本信息 Author:kaedea GitHub:android-dynamical-loading 代理Activity模式的限制 还记得我们在代理Activity模式里谈到启动插件APK里的Ac ...

  7. android glide的历史,Android 图片加载的那些事:为什么你的Glide 缓存没有起作用?...

    前言Glide,该功能非常强大 Android  图片加载开源框架 相信大家并不陌生 正由于他的功能强大,所以它的源码非常复杂,这导致很多人望而却步 本人尝试将 Glide 的功能进行分解,并单独针对 ...

  8. Android布局加载慢,Android布局优化(四)X2C — 提升布局加载速度200%

    系列文章 前言 在Android布局优化(一)从布局加载原理说起中我们说到了布局加载的两大性能瓶颈,通过IO操作将XML加载到内存中并进行解析和通过反射创建View.这里介绍一种避免运行时通过IO操作 ...

  9. Web 性能优化:使用 CSS font-display 控制字体加载和替换

    作者 | 张旭乾       责编 | 欧阳姝黎 出品 | 峰华前端工程师 在编写网站的时候,或多或少都会用到一些网络上的字体,CSS 3 中虽然加入了对 Web Fonts(网络字体)的支持,但是浏 ...

最新文章

  1. Docker(一):Docker的安装与常用命令
  2. JS中遍历数组的两种方式
  3. 10-Python-mapfilter
  4. 条形码简介_条形码基本常识_条形码基本原理
  5. linux中gnu作用和功能,GNU是什么
  6. 2014TI杯(D题)带啸叫检测与抑制的音频功率放大器
  7. linux tac命令,Linux tac 命令 command not found tac 命令详解 tac 命令未找到 tac 命令安装 - CommandNotFound ⚡️ 坑否...
  8. 程序员转行做什么工作比较好?
  9. 关爱的艺术 - 致敬疫情前线奋战的医护人员!
  10. JavaScript中函数的length属性
  11. ssh配置公钥解决免密登录
  12. 【 react】react实现页面后退按钮(goBack())
  13. html2canvas的使用以及跨域问题
  14. 最值得一看的几条简单的谷歌 Google 搜索技巧,瞬间提升你的网络搜索能力!
  15. 代谢组学通路富集分析
  16. 强化学习及Python代码示例
  17. 2016年十大黑客工具
  18. locust之安装(3)
  19. 无器械健身和器械健身比较 1
  20. WIFI模块的STA模式和AP模式有什么区别?

热门文章

  1. 数据分析神器Alteryx
  2. 积分-钉钉考勤-告警
  3. 基于Java Swing编写的简易运费计算工具
  4. 日语语法(二):名词
  5. Mathmatica可视化
  6. bandizip右键选项设置方法步骤
  7. vue中缓存当前路由的实现
  8. jee6 学习笔记 5 - Struggling with JSF2 binding GET params
  9. 金工计算机测试题,金工考试题精选.doc
  10. 基于项目的协同过滤推荐算法单机版代码实现(包含输出电影-用户评分矩阵模型、项目相似度、推荐结果、平均绝对误差MAE)