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
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
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
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
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. 导航与后续
# 输入预加载入口和资源数组,输出清单与 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 缓存。
