Skip to content

preloadResources

深入 Resources.preloadResources 与 ResourcesImpl 的预加载标记、Drawable/ColorStateList 缓存、配置校验和异常清理。

基于android-17.0.0_r1
AndroidZygoteResources源码阅读

preloadResources ​

Zygote 预加载资源的目标不是把任意资源永久塞进一个全局 Map,而是让系统 Resources 在 fork 前建立可共享的 ConstantState 缓存。Android 17 的真实路径是:ZygoteInit.preload() 调用 Resources.preloadResources(),后者让 ResourcesImpl 进入 preloading 状态,读取 framework 资源数组,加载 drawable 和 color state list,按配置变化规则写入预加载缓存,最后退出该状态并清理 XML layout cache。

1. 入口与输入清单 ​

源码文件:frameworks/base/core/java/android/content/res/Resources.java、frameworks/base/core/res/res/values/arrays.xml

java
public static void preloadResources() {
    try {
        final Resources sysRes = Resources.getSystem();
        sysRes.startPreloading();
        if (PRELOAD_RESOURCES) {
            TypedArray ar = sysRes.obtainTypedArray(
                    com.android.internal.R.array.preloaded_drawables);
            int numberOfEntries = preloadDrawables(sysRes, ar);
            ar.recycle();

            ar = sysRes.obtainTypedArray(
                    com.android.internal.R.array.preloaded_color_state_lists);
            numberOfEntries = preloadColorStateLists(sysRes, ar);
            ar.recycle();
        }
        sysRes.finishPreloading();
    } catch (RuntimeException e) {
        Log.w(TAG, "Failure preloading resources", e);
    }
}

输入清单不是 Java 代码中的硬编码资源 ID,而是 com.android.internal.R.array.preloaded_drawables 和 preloaded_color_state_lists。Resources.getSystem() 是系统资源 owner,TypedArray 是遍历期间的临时 owner;每次遍历后 recycle(),避免预加载阶段把 TypedArray 本身长期带入 fork 状态。

2. 预加载状态 ​

源码文件:frameworks/base/core/java/android/content/res/ResourcesImpl.java

java
public final void startPreloading() {
    synchronized (sSync) {
        if (sPreloaded) {
            throw new IllegalStateException("Resources already preloaded");
        }
        sPreloaded = true;
        mPreloading = true;
        mConfiguration.densityDpi = DisplayMetrics.DENSITY_DEVICE;
        updateConfiguration(null, null, null);
    }
}

void finishPreloading() {
    if (mPreloading) {
        mPreloading = false;
        flushLayoutCache();
    }
}

sPreloaded 是进程级一次性门闩,防止重复建立全局预加载状态;mPreloading 是当前 ResourcesImpl 的工作标记。开始时强制使用设备密度并更新配置,保证资源选择依据稳定;结束时关闭预加载标记并清空 layout cache,因为 XML layout cache 不应把预加载期间的临时解析对象继续带入普通资源访问。

3. 资源遍历 ​

源码文件:frameworks/base/core/java/android/content/res/Resources.java

java
private static int preloadDrawables(Resources resources, TypedArray ar) {
    final int numberOfEntries = ar.length();
    for (int i = 0; i < numberOfEntries; i++) {
        int id = ar.getResourceId(i, 0);
        if (id != 0) {
            if (resources.getDrawable(id, null) == null) {
                throw new IllegalArgumentException(
                        "Unable to find preloaded drawable resource #0x"
                                + Integer.toHexString(id)
                                + " (" + ar.getString(i) + ")");
            }
        }
    }
    return numberOfEntries;
}

private static int preloadColorStateLists(Resources resources, TypedArray ar) {
    final int numberOfEntries = ar.length();
    for (int i = 0; i < numberOfEntries; i++) {
        int id = ar.getResourceId(i, 0);
        if (id != 0) {
            if (resources.getColorStateList(id, null) == null) {
                throw new IllegalArgumentException(
                        "Unable to find preloaded color resource #0x"
                                + Integer.toHexString(id)
                                + " (" + ar.getString(i) + ")");
            }
        }
    }
    return numberOfEntries;
}

两个 helper 都把资源 ID 交给正常的 getDrawable/getColorStateList,因此真正的 XML inflate、颜色解析和缓存写入仍由 ResourcesImpl 完成。ID 为 0 的数组项被跳过;非零 ID 找不到资源会抛 IllegalArgumentException,由外层 preloadResources() 捕获并记录 warning。

4. 缓存与配置 ​

源码文件:frameworks/base/core/java/android/content/res/ResourcesImpl.java

java
if (mPreloading) {
    final int changingConfigs = cs.getChangingConfigurations();
    if (isColorDrawable) {
        if (verifyPreloadConfig(changingConfigs, 0,
                value.resourceId, "drawable")) {
            sPreloadedColorDrawables.put(key, cs);
        }
    } else if (verifyPreloadConfig(changingConfigs,
            ActivityInfo.CONFIG_LAYOUT_DIRECTION,
            value.resourceId, "drawable")) {
        if ((changingConfigs & ActivityInfo.CONFIG_LAYOUT_DIRECTION) == 0) {
            sPreloadedDrawables[0].put(key, cs);
            sPreloadedDrawables[1].put(key, cs);
        } else {
            sPreloadedDrawables[
                    mConfiguration.getLayoutDirection()].put(key, cs);
        }
    }
}

预加载缓存保存的是 Drawable.ConstantState,不是同一个 Drawable 实例;读取时再由 state 创建新实例,避免不同调用者共享可变对象。颜色 drawable 使用独立 map;普通 drawable 按 layout direction 分两个 map。verifyPreloadConfig() 拒绝会随不支持配置变化的资源进入预加载缓存,只允许 density/font scale 等已特殊处理的变化,防止子进程复用错误配置。

5. 失败与清理 ​

资源文件缺失、资源 ID 无法解析、inflate 失败或配置不允许预加载,路径不同:缺失/解析异常会在 preloadResources() 外层变成 warning;配置不允许时只跳过写入 preload cache;TypedArray.recycle() 和 finishPreloading() 负责临时状态清理。由于 finishPreloading() 位于 try 内部,若前面的 helper 抛出异常,源码会直接进入 catch,预加载标记可能由后续进程生命周期结束而不再使用,不能把 warning 误写成“所有资源已成功缓存”。

6. 导航与后续 ​

bash
# 输入预加载入口和资源数组,输出清单与 helper。
rg -n "preloadResources|preloaded_drawables|preloaded_color_state_lists|preloadDrawables|preloadColorStateLists" \
  frameworks/base/core/java/android/content/res/Resources.java \
  frameworks/base/core/res/res/values/arrays.xml

# 输入 ResourcesImpl 缓存实现,输出 preloading 标记、配置校验与 ConstantState 写入。
rg -n "startPreloading|finishPreloading|mPreloading|verifyPreloadConfig|sPreloadedDrawables|sPreloadedColorDrawables" \
  frameworks/base/core/java/android/content/res/ResourcesImpl.java

下一篇将只分析 preloadSharedLibraries() 与图形驱动预加载,解释 System.loadLibrary、feature flag 和 native HAL 初始化;不会重复资源数组、TypedArray 或 ConstantState 缓存。