• 作者:老汪软件技巧
  • 发表时间: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(BiConsumersuper 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++;
    }
}