- 作者:老汪软件技巧
- 发表时间:2024-08-22 00:03
- 浏览量:
字体中如何通过算法缓存字体资源,下面先看一下字体相关代码
从指定目录中创建字体
Typeface fontTypeFace = Typeface.createFromAsset(mContext.getAssets(), mFontTypeFace);
public static Typeface createFromAsset(AssetManager mgr, String path) {
Preconditions.checkNotNull(path); // for backward compatibility
Preconditions.checkNotNull(mgr);
Typeface typeface = new Builder(mgr, path).build();
if (typeface != null) return typeface;//这里用到缓存,不是每次都io操作
// 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);
}
return Typeface.DEFAULT;
}
缓存核心逻辑主要在*sDynamicTypefaceCache*这个类实现
private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
public Typeface build() {
if (mFontBuilder == null) {
return resolveFallbackTypeface();
}
try {
final Font font = mFontBuilder.build();
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.
synchronized (sDynamicCacheLock) {
final Typeface typeface = sDynamicTypefaceCache.get(key);
if (typeface != null) {
return 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);
}
final Typeface typeface = builder.build();
if (key != null) {
synchronized (sDynamicCacheLock) {
sDynamicTypefaceCache.put(key, typeface);
}
}
return typeface;
} catch (IOException | IllegalArgumentException e) {
return resolveFallbackTypeface();
}
}
}
LruCache主要是通过LinkedHashMap实现,LinkedHashMap通过链表结构存储。
每次读取新的数据之后,把新访问的数据,存储到尾端。
void afterNodeAccess(Node e) { // move node to last
LinkedHashMapEntry last;
if (accessOrder && (last = tail) != e) {
LinkedHashMapEntry p =
(LinkedHashMapEntry)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
这段逻辑意思就是,把新访问节点放到末尾,然后把上一个末尾放到新节点的before节点。
/**
* The head (eldest) of the doubly linked list.
*/
transient LinkedHashMapEntry<K,V> head;
/**
* The tail (youngest) of the doubly linked list.
*/
transient LinkedHashMapEntry<K,V> tail;
head表示节点头,tail表示节点尾;调整大小,容器如果满了需要移除头部节点数据。
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize) {
break;
}
Map.Entry toEvict = map.eldest();//头部节点
if (toEvict == null) {
break;
}
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);//头部节点
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
public Map.Entry eldest() {
return head;
}
前面讲到,LruCache主要是通过LinkedHashMap实现,LinkedHashMap相对于HashMap,简单理解,就是可以让HashMap的数据访问有一定的顺序了,数据之间有关联性。比如,如果需要迭代所有的数据,它提供了更快的方法。看一下forEach如何迭代。
public void forEach(BiConsumer super K, ? super V> action) {
int mc = modCount;
// Android-changed: Detect changes to modCount early.
for (LinkedHashMapEntry e = head; modCount == mc && e != null; e = e.after)
action.accept(e.key, e.value);
}
先从head头部节点开始,然后每个节点有一个after,也就是关联下一个节点。这样访问速度就快了。
关于字体源代码中,还有另外一个缓存,是缓存与子重相关的字体。
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;
synchronized(sWeightCacheLock) {
SparseArray innerCache = sWeightTypefaceCache.get(base.native_instance);
if (innerCache == null) {
innerCache = new SparseArray<>(4);
sWeightTypefaceCache.put(base.native_instance, innerCache);
} else {
typeface = innerCache.get(key);
if (typeface != null) {
return typeface;
}
}
typeface = new Typeface(
nativeCreateFromTypefaceWithExactStyle(base.native_instance, weight, italic));
innerCache.put(key, typeface);
}
return typeface;
}
SparseArray 简单理解就是一个映射表,高效地映射long类型的键到Object类型的值,但内部是通过两个数组实现。
private int[] mKeys;
private Object[] mValues;
Key理想状态是要唯一的,先看一下子重缓存如何生成Key
final int key = (weight << 1) | (italic ? 1 : 0);
这种做法很场景,通过两个字段组合,生成唯一的Key。不理解这段代码的,可以看一下我前面关于位运算的文章。再看一下SparseArray中读取get方法
public E get(int key, E valueIfKeyNotFound) {
//二分查找算法,先查找一下该key是否存在
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i < 0 || mValues[i] == DELETED) {
return valueIfKeyNotFound;
} else {
return (E) mValues[i];
}
}
继续看一下存储put方法
public void put(int key, E value) {
int i = ContainerHelpers.binarySearch(mKeys, mSize, key);
if (i >= 0) {
//如果key之前已经有的,直接覆盖
mValues[i] = value;
} else {
i = ~i;
if (i < mSize && mValues[i] == DELETED) {
mKeys[i] = key;
mValues[i] = value;
return;
}
if (mGarbage && mSize >= mKeys.length) {
gc();
// Search again because indices may have changed.
i = ~ContainerHelpers.binarySearch(mKeys, mSize, key);
}
//检查数据是否需要扩容,然后保存数据
mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key);
mValues = GrowingArrayUtils.insert(mValues, mSize, i, value);
mSize++;
}
}