Android字体系列 (二):Typeface完全解析,Android高级工程师进阶学习】

//如果当前 Typeface 的 mStyle 属性和传入的 style 相同,直接返回 Typeface 对象
if (family.mStyle == style) {
return family;
}

final long ni = family.native_instance;

Typeface typeface;
//使用 sStyledCacheLock 保证线程安全
synchronized (sStyledCacheLock) {
//从缓存中获取存放 Typeface 的 SparseArray
SparseArray styles = sStyledTypefaceCache.get(ni);
if (styles == null) {
//存放 Typeface 的 SparseArray 为空,新创建一个,容量为 4
styles = new SparseArray(4);
//将当前 存放 Typeface 的 SparseArray 放入缓存中
sStyledTypefaceCache.put(ni, styles);
} else {
//存放 Typeface 的 SparseArray 不为空,直接获取 Typeface 并返回
typeface = styles.get(style);
if (typeface != null) {
return typeface;
}
}

//通过 native 层构建创建 Typeface 的参数并创建 Typeface 对象
typeface = new Typeface(nativeCreateFromTypeface(ni, style));
//将新创建的 Typeface 对象放入 SparseArray 中缓存起来
styles.put(style, typeface);
}
return typeface;
}

从上述代码我们可以知道:

1、当你设置的 Typeface 和 Style 为 null 和 0 时,会给它们设置一个默认值

注意:这里的 Style ,对应上一篇中讲的 android:textStyle 属性传递的值,用于设定字体的粗体、斜体等参数

2、如果当前设置的 Typeface 的 mStyle 属性和传入的 Style 相同,直接将 Typeface 给返回

3、从缓存中获取存放 Typeface 的容器,如果缓存中存在,则从容器中取出该 Typeface 并返回

4、如果不存在,则创建新的容器并加入缓存,然后通过 native 层创建 Typeface,并把当前 Typeface 放入到容器中

因此我们在使用的时候无需担心效率问题,它会把我们传入的字体进行一个缓存,后续都是从缓存中去拿的

3、通过字体名称和 Style 获取字体

对应上面截图的第二个 API:

public static Typeface create(String familyName, @Style int style) {
//调用截图的第一个 API
return create(getSystemDefaultTypeface(familyName), style);
}

//获取系统提供的一些默认字体,如果获取不到则返回系统的默认字体
private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
Typeface tf = sSystemFontMap.get(familyName);
return tf == null ? Typeface.DEFAULT : tf;
}

1、这个创建 Typeface 的 API 很简单,就是调用它的一个重载方法,我们已经分析过

2、getSystemDefaultTypeface 主要是通过 sSystemFontMap 获取字体,而这个 sSystemFontMap 在 Typeface 初始化的时候会存放系统提供的一些默认字体,因此这里直接取就可以了

4、通过 Typeface 、weight(粗体) 和 italic(斜体) 获取新的 Typeface

对应上面截图的第三个 API

public static @NonNull Typeface create(@Nullable Typeface family,
@IntRange(from = 1, to = 1000) int weight, boolean italic) {
//校验传入的 weight 属性是否在范围内
Preconditions.checkArgumentInRange(weight, 0, 1000, “weight”);
if (family == null) {
//如果当前传入的 Typeface 为 null, 则置为默认值
family = sDefaultTypeface;
}
//调用 createWeightStyle 方法创建 Typeface
return createWeightStyle(family, weight, italic);
}

private static @NonNull Typeface createWeightStyle(@NonNull Typeface base,
@IntRange(from = 1, to = 1000) int weight, boolean italic) {
final int key = (weight << 1) | (italic ? 1 : 0);

Typeface typeface;
//使用 sWeightCacheLock 保证线程安全
synchronized(sWeightCacheLock) {
SparseArray innerCache = sWeightTypefaceCache.get(base.native_instance);
if (innerCache == null) {
//缓存 Typeface 的 SparseArray 为 null, 新建并缓存
innerCache = new SparseArray<>(4);
sWeightTypefaceCache.put(base.native_instance, innerCache);
} else {
//从缓存中拿取 typeface 并返回
typeface = innerCache.get(key);
if (typeface != null) {
return typeface;
}
}
//通过 native 创建 Typeface 对象
typeface = new Typeface(
nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic));
//将 Typeface 加入缓存
innerCache.put(key, typeface);
}
return typeface;
}

通过上述代码可以知道,他与截图一 API 的源码很类似,无非就是将之前需要设置的 Style 换成了 weight 和 italic,里面的实现机制是类似的

5、通过 AssetManager 和对应字体路径获取字体

对应上面截图的第四个 API

public static Typeface createFromAsset(AssetManager mgr, String path) {
//参数检查
Preconditions.checkNotNull(path); // for backward compatibility
Preconditions.checkNotNull(mgr);

//通过 Typeface 的 Builder 模式构建 typeface
Typeface typeface = new Builder(mgr, path).build();
//如果构建的 typeface 不为空则返回
if (typeface != null) return typeface;
// check if the file exists, and throw an exception for backward compatibility
//看当前字体路径是否存在,不存在直接抛异常
try (InputStream inputStream = mgr.open(path)) {
} catch (IOException e) {
throw new RuntimeException("Font asset not found " + path);
}
//如果构建的字体为 null 则返回默认字体
return Typeface.DEFAULT;
}

//接着看 Typeface 的 Builder 模式构建 typeface
//Builder 构造方法 主要就是初始化 mFontBuilder 和一些参数
public Builder(@NonNull AssetManager assetManager, @NonNull String path, boolean isAsset,
int cookie) {
mFontBuilder = new Font.Builder(assetManager, path, isAsset, cookie);
mAssetManager = assetManager;
mPath = path;
}

//build 方法
public Typeface build() {
//如果 mFontBuilder 为 null,则会调用 resolveFallbackTypeface 方法
//resolveFallbackTypeface 内部会调用 createWeightStyle 创建 Typeface 并返回
if (mFontBuilder == null) {
return resolveFallbackTypeface();
}
try {
//通过 mFontBuilder 构建 Font
final Font font = mFontBuilder.build();
//使用 createAssetUid 方法获取到这个字体的唯一 key
final String key = mAssetManager == null ? null : createAssetUid(
mAssetManager, mPath, font.getTtcIndex(), font.getAxes(),
mWeight, mItalic,
mFallbackFamilyName == null ? DEFAULT_FAMILY : mFallbackFamilyName);
if (key != null) {
// Dynamic cache lookup is only for assets.
//使用 sDynamicCacheLock 保证线程安全
synchronized (sDynamicCacheLock) {
//通过 key 从缓存中拿字体
final Typeface typeface = sDynamicTypefaceCache.get(key);
//如果当前字体不为 null 直接返回
if (typeface != null) {
return typeface;
}
}
}
//如果当前字体不存在,通过 Builder 模式构建 FontFamily 对象
//通过 FontFamily 构建 CustomFallbackBuilder 对象
//最终通过 CustomFallbackBuilder 构建 Typeface 对象
final FontFamily family = new FontFamily.Builder(font).build();
final int weight = mWeight == RESOLVE_BY_FONT_TABLE
? font.getStyle().getWeight() : mWeight;
final int slant = mItalic == RESOLVE_BY_FONT_TABLE
? font.getStyle().getSlant() : mItalic;
final CustomFallbackBuilder builder = new CustomFallbackBuilder(family)
.setStyle(new FontStyle(weight, slant));
if (mFallbackFamilyName != null) {
builder.setSystemFallback(mFallbackFamilyName);
}
//builder.build 方法内部最终会通过调用 native 层创建 Typeface 对象
final Typeface typeface = builder.build();
//缓存 Typeface 对象并返回
if (key != null) {
synchronized (sDynamicCacheLock) {
sDynamicTypefaceCache.put(key, typeface);
}
}
return typeface;
} catch (IOException | IllegalArgumentException e) {
//如果流程有任何异常,则内部会调用 createWeightStyle 创建 Typeface 并返回
return resolveFallbackTypeface();
}
}

上述代码步骤:

1、大量运用了 Builder 模式去构建相关对象

2、具体逻辑就是使用 createAssetUid 方法获取到当前字体的唯一 key ,通过这个唯一 key ,从缓存中获取已经被加载过的字体,如果没有,则创建一个 FontFamily 对象,经过一系列 Builder 模式,最终调用 native 层创建 Typeface 对象,并将这个 Typeface 对象加入缓存并返回

3、如果流程有任何异常,内部会调用 createWeightStyle 创建 Typeface 并返回

6、通过字体文件获取字体

对应上面截图的第五个 API

public static Typeface createFromFile(@Nullable File file) {
// For the compatibility reasons, leaving possible NPE here.
// See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
//通过 Typeface 的 Builder 模式构建 typeface
Typeface typeface = new Builder(file).build();
if (typeface != null) return typeface;

// check if the file exists, and throw an exception for backward compatibility
//文件不存在,抛异常
if (!file.exists()) {
throw new RuntimeException("Font asset not found " + file.getAbsolutePath());
}
//如果构建的字体为 null 则返回默认字体
return Typeface.DEFAULT;
}

//Builder 另外一个构造方法 主要是初始化 mFontBuilder
public Builder(@NonNull File path) {
mFontBuilder = new Font.Builder(path);
mAssetManager = null;
mPath = null;
}

从上述代码可以知道,这种方式主要也是通过 Builder 模式去构建 Typeface 对象,具体逻辑我们刚才已经分析过

7、通过字体路径获取字体

对应上面截图的第六个 API

public static Typeface createFromFile(@Nullable String path) {
Preconditions.checkNotNull(path); // for backward compatibility
return createFromFile(new File(path));
}

这个就更简单了,主要就是创建文件对象然后调用另外一个重载方法

8、Typeface 相关 Native 方法

在 Typeface 中,所有最终操作到加载字体的部分,全部都是 native 的方法。而 native 方法就是以效率著称的,这里只需要保证不频繁的调用(Typeface 已经做好了缓存,不会频繁的调用),基本上也不会存在效率的问题。

private static native long nativeCreateFromTypeface(long native_instance, int style);
private static native long nativeCreateFromTypefaceWithExactStyle(
long native_instance, int weight, boolean italic);
// TODO: clean up: change List to FontVariationAxis[]
private static native long nativeCreateFromTypefaceWithVariation(
long native_instance, List axes);
@UnsupportedAppUsage
private static native long nativeCreateWeightAlias(long native_instance, int weight);
@UnsupportedAppUsage
private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic);
private static native int[] nativeGetSupportedAxes(long native_instance);

@CriticalNative
private static native void nativeSetDefault(long nativePtr);

@CriticalNative
private static native int nativeGetStyle(long nativePtr);

@CriticalNative
private static native int nativeGetWeight(long nativePtr);

@CriticalNative
private static native long nativeGetReleaseFunc();

private static native void nativeRegisterGenericFamily(String str, long nativePtr);

到这里,关于 Typeface 源码部分我们就介绍完了,下面看下它的一些其他细节

三、Typeface 其它细节

1、默认使用

在初始化那部分,Typeface 对字体和 Style 有一些默认实现

如果我们只想用系统默认的字体,直接拿上面的常量用就 ok 了,如:

Typeface.DEFAULT
Typeface.DEFAULT_BOLD
Typeface.SANS_SERIF
Typeface.SERIF
Typeface.MONOSPACE

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

说一千道一万,不如自己去行动。要想在移动互联网的下半场是自己占有一席之地,那就得从现在开始,从今天开始,马上严格要求自己,既重视业务实现能力,也重视基础和原理。基础夯实好了,高楼才能够平地而起,稳如泰山。

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2020-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节

还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

一线互联网面试专题

379页的Android进阶知识大全

379页的Android进阶知识大全

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

一起学习。

[外链图片转存中…(img-x6nxkk2m-1712391174694)]

[外链图片转存中…(img-nQqjIc9i-1712391174694)]

[外链图片转存中…(img-ocAvffad-1712391174694)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在推上fo了个toefl单词机器人,没想到很多单词的音标显示成了小方块,那就是android默认的英文字体对某些英语国际音标不支持了,当然我的Galaxy S刷了第三方ROM的,不过从网上的帖子来看,官方的字体存在一样的问题,总而言之是字体的问题了。 也搜了一些帖子,但没有很好的解决方案,默认的英文字体还是挺美观的,只是某些英语音标的编码位上缺失了相应的字符而已,Anroid使用Java作为默认开发环境,那默认的字体也应该是使用UNICODE UCS编码的,事实证明的却如此。那就很简单了,强大的字体编辑工具FontForge派上用场了。 Ubuntu仓库里面的版本太低了,可以使用GetDeb仓库来安装最新版本的FontForge。用FontForge打开Android默认的英文字体DroidSans.ttf,果然国际音标编码区域空空如也,英语音标现实不完全也就一点儿也不奇怪了。从国际音标wiki上查找到了这写符号的UNICODE编码,从DejaVuSerif.ttf和Gothic.ttf这两个字体里面提取了对应的字形(glyph)插入到DroidSans.ttf相应的BMP(Basic Multilingual Plane)编码位上。当然只补充了英语音标会用到的字符,包括ɑ,ɒ,ɔ,ə,ɛ,ɜ,ɪ,ɵ,ʃ,ʌ,ʒ,ʤ,ʦ,ʧ这几个常用音标字符。默认的字体竟然连重音(primary stress)和次重音(Secondary stress)这两个符号都没有,一并补齐了。 将制作好的字体覆盖Android默认英文字体/system/fonts/DroidSans.ttf,当然需要root权限,再看英语音标,显示的相当完美了,google dictionary里面的音标也完全没有问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值