StupidBeauty
Read times:4Posted at:Tue Jul 21 06:50:25 2026 《五秒之间》(宏大版)

《五秒之间》(宏大版)

——致所有在重启时迷失的引擎

 

 

 

序章:原初的喧嚣

 

 

最初,没有系统。

只有硅。在黑暗里沉睡的硅。数十亿晶体管,沉默如未出世的群山。

然后——

0x00000001

第一个时钟周期。

电压从 0 跳到 1.1 V。电信号从主板的某个角落出发,穿越总线,抵达引导 ROM

在 ROM 深处,Boot ROM 早已写好的一段代码,被唤醒了。

它不知道外面的世界。它只知道一个指令:加载 Bootloader

Bootloader 不知道 Boot ROM 之外的世界。它只知道一个指令:加载 Linux Kernel

Linux Kernel 不知道 Bootloader 为它做了什么。它只知道一个指令:初始化硬件,挂载文件系统,启动第一个进程。

第一个进程,叫 init

init 是 Linux 世界的亚当。它睁开眼,看到一切都是空的。

没有进程。 没有服务。 没有 Activity。 没有 Wallpaper。 没有 MediaProvider。 没有 SurfaceFlinger。 没有 InputManager。 没有 WindowManager。 没有 PowerManager。 没有 ActivityManager。 没有 PackageManager。 没有 NetworkManager。 没有 StorageManager。 没有 BatteryService。 没有 BluetoothManager。 没有 LocationManager。 没有 NotificationManager。 没有 AlarmManager。 没有 JobScheduler。 没有 ConnectivityService。 没有 TelephonyManager。 没有 WifiManager。 没有 AudioFlinger。 没有 CameraService。 没有 SensorService。 没有 VibratorService。 没有 DropBoxManager。

一切都是 void

init 知道该怎么办。

它从 /init.rc 读取启动脚本。

第一阶段:早期初始化 挂载 tmpfs、proc、sysfs。 启动 ueventd。 加载 SELinux 策略。 启动 watchdogd。

第二阶段:核心服务 启动 ServiceManager。这是 Android 世界的户口簿。 所有服务都要在 ServiceManager 登记。

第三阶段:Zygote init 启动 Zygote。Zygote 是所有 Java 应用的祖先。 它预加载 framework 的所有类。 然后等待。

第四阶段:系统服务 Zygote fork 出无数个系统服务。 ActivityManagerService。 PackageManagerService。 WindowManagerService。 PowerManagerService。 ...

这些服务一个接一个地启动。

但它们不是有序的。

它们是混乱的。

 

第一章:系统的诞生(混沌前 30 秒)

[0.0s] 内核初始化

Linux Kernel 完成硬件初始化。

  • CPU:调度器启动 

  • 内存:页表建立 

  • 文件系统:挂载 

  • 驱动:探测 

dmesg 开始记录一切。

这是系统最孤独的时刻。只有它自己。

[0.5s] Init 进程

PID 1:init。 它读取 /init.rc

[1.0s] ServiceManager

PID 几百:ServiceManager 启动。

这是 Android 世界的户口簿。

"我要注册 ActivityManager。" - 没人听到。 "我要注册 WindowManager。" - 没人听到。 "我要注册 PowerManager。" - 没人听到。

因为它们还没启动。

ServiceManager 孤独地等待。

[1.5s] Zygote

Zygote 启动。 预加载所有 framework 类。 但它只是一个等待中的父亲。 没有子进程。

[2.0s] 系统服务海啸

Zygote 开始 fork。 几乎是同时地:

  • ActivityManagerService 启动 

  • PackageManagerService 启动 

  • WindowManagerService 启动 

  • PowerManagerService 启动 

  • InputManagerService 启动 

  • SurfaceFlinger 启动 

  • MediaServer 启动 

  • AudioFlinger 启动 

  • CameraService 启动 

  • SensorService 启动 

  • VibratorService 启动 

  • NetworkManager 启动 

  • ConnectivityService 启动 

  • WifiService 启动 

  • TelephonyRegistry 启动 

  • BluetoothManager 启动 

  • LocationManager 启动 

  • NotificationManager 启动 

  • AlarmManager 启动 

  • JobScheduler 启动 

  • StorageManager 启动 

  • BatteryService 启动 

  • DropBoxManager 启动 

  • WallpaperManagerService 启动 

  • ... 

每一个服务都认为自己是第一个。

每一个服务都希望立即得到所有依赖。

但依赖链是混乱的

ActivityManager 想要 PackageManager 就绪。 WindowManager 想要 ActivityManager 就绪。 InputManager 想要 WindowManager 就绪。 SurfaceFlinger 想要所有显示相关服务就绪。 MediaServer 想要 StorageManager 就绪。 WallpaperManager 想要 ActivityManager 就绪。 ...

这是一张图,不是一条链。

所有的服务都指向所有其他服务。

死锁。

循环依赖。

混乱。

 

第二章:混沌的初 5 秒

[2.0s-2.5s] 服务惊厥

第一个醒来的是 ServiceManager。它登记户口。 但谁都没来找它。它孤独地等待。

接下来是 PackageManagerService。它扫描所有 APK。 这一步需要 3 秒。

与此同时,WindowManagerService 启动。 但它需要 ActivityManager。 而 ActivityManager 还在等 PackageManager。 而 PackageManager 还在扫 APK。

WindowManager 卡住。

init 看到这个错误:

[  2.3] init: Service window (pid 456) is taking too long to start

init 不会放弃。它会轮询

[2.5s-3.0s] 抢占开始

init 不等所有服务就绪。 它按优先级启动。

BOOT_SYSTEM(最先):

  • ServiceManager 

  • PackageManager 

  • ActivityManager 

  • WindowManager 

  • PowerManager 

BOOT_RUNTIME(后):

  • WallpaperManager 

  • MediaServer 

  • SurfaceFlinger 

  • InputManager 

  • BatteryService 

  • NetworkManager 

  • LocationManager 

  • BluetoothManager 

  • ... 

但即使有优先级,依赖还是混乱的

WallpaperManager 想启动 WallpaperService。 但 WallpaperService 是壁纸引擎。 壁纸引擎需要 MediaProvider(用来读图片)。 而 MediaProvider 是懒的。

[3.0s] 壁纸引擎诞生

壁纸引擎MyLiveWallpaperService。 它由 WallpaperManagerService 启动。

壁纸引擎 onCreateEngine 被调用。 Engine 类的实例被创建。 onCreate 被调用。 它说:

"我准备好了!"

它准备好吗?

它的内部:

  • wallpaperBitmap = null 

  • queryImages() 会调用 MediaProvider 

  • MediaProvider 在哪里? 

MediaProvider 还在沉睡。

[3.5s] MediaProvider 沉睡

com.android.providers.media.MediaProvider 进程还没启动。

它有自己独立的进程:android.process.media

这个进程被 init 标记为按需启动。 也就是说,它不会自己醒来

只有当某个应用调用 ContentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI) 时, init 才会 fork 这个进程。

但这需要时间。

启动 MediaProvider 进程需要 1 秒。 加载 MediaProvider 数据库需要 1 秒。 扫描文件系统建立索引需要 2-3 秒。

总耗时 4-5 秒。

壁纸引擎不知道这些。

壁纸引擎在 onSurfaceCreated 时调用 queryImages()。 然后等。 等。 等。 5 秒。

wallpaperBitmap 一直是 null。

[4.0s] SurfaceFlinger 准备

SurfaceFlinger 是 Android 的显示合成器。 它把所有应用的 Surface 合成到屏幕。

壁纸引擎是 SurfaceFlinger 的第一个客户。 壁纸引擎的 Surface 必须是第一个显示的。

SurfaceFlinger 准备好了。 它说:

"我准备好了,给我 Surface。"

壁纸引擎说:

"我有 Surface,但 wallpaperBitmap 是 null。"

SurfaceFlinger 不知道怎么办。 它只能显示黑色

这就是为什么开机时屏幕是黑的。

[4.5s] WindowManager 准备

WindowManager 是 Android 的窗口管理器。

但它现在不关心壁纸。它关心第一个 Activity

Android 启动后,第一个 Activity 是 Launcher。 Launcher 是 SystemUI 的一部分。

Launcher 的 APK 还没扫完。 PackageManager 还在扫。 所以 Launcher 还没启动

WindowManager 不知道让谁显示在屏幕上。

等待

[5.0s] 5 秒这个数字

为什么是 5 秒?

5 秒是人类耐心的极限

但 5 秒也是多个子系统启动的合集

子系统

启动耗时

完成时间

内核

0.5s

0.5s

Init

0.1s

0.6s

ServiceManager

0.1s

0.7s

Zygote

0.3s

1.0s

PackageManager

3.0s

4.0s

ActivityManager

0.5s

4.5s

WindowManager

0.3s

4.8s

SurfaceFlinger

0.2s

5.0s

MediaProvider

5.0s

10.0s

壁纸引擎 onSurfaceCreated

-

~5.5s

MediaProvider 就绪

-

~10.0s

壁纸引擎需要 MediaProvider,但 MediaProvider 在 10s 才就绪。

壁纸引擎在 5.5s 就要开始 query。

中间差 4.5 秒。

这就是 5 秒延迟的真正来源。

 

第三章:服务的众生相

让我们看看这些服务在启动的瞬间,都在想什么

[ActivityManager] 的焦虑

"我有 1000 个广播要发送。" "我有 100 个 ContentProvider 要就绪检查。" "我有 50 个 Service 要启动。" "我还要处理 SystemUI、Launcher、Settings、Camera、Gallery……" "壁纸?我等会儿再说。"

[WindowManager] 的困惑

"我应该让谁显示?" "Launcher 还没准备好。" "壁纸?嗯,壁纸是 Launcher 之外的另一个 Surface。" "让我把壁纸 Surface 放在最底层……但壁纸 bitmap 还没好。" "那我就先显示空 Surface。"

[SurfaceFlinger] 的不满

"我准备合成所有 Surface。" "壁纸 Surface 是空的。我合成个黑色。" "Launcher Surface 还没创建。让我等等。" "5 秒过去了。Launcher Surface 还是没有。" "为什么?为什么?为什么?"

[MediaProvider] 的沉睡

"Zzz……" "我在等一个 ContentResolver.query 的调用。" "那个调用会让我醒来。" "Zzz……"

[PackageManager] 的辛劳

"我有 200 个 APK 要扫描。" "SystemUI、Launcher、Settings、Gallery、Camera、Email……" "还有那个用户装的 50 个 App。" "每个 APK 都要:解析 manifest、签名校验、权限检查……" "需要 3 秒。"

[InputManager] 的清醒

"我有 5 个输入设备要监听。" "Touch、Key、Mouse、Gamepad……" "我已经准备好了。" "但是没有 Activity 能接收输入啊。" "我先准备好。等谁需要我,我就服务谁。"

[PowerManager] 的等待

"我在等 wake_lock。" "系统刚启动,屏幕应该是亮的。" "但用户还没碰屏幕啊。" "我先准备着。"

[NetworkManager] 的孤独

"我在等网络连接。" "WiFi 还没连上。" "移动数据还没激活。" "我先准备着。"

[StorageManager] 的勤勉

"我在挂载文件系统。" "system、data、cache、external……" "external 是用户 SD 卡,可能很慢。" "但 MediaProvider 不会等 external 就绪吧?" "……会等。" "所以 MediaProvider 也慢。"

[WallpaperManager] 的迷茫

"壁纸引擎是我启动的。" "但壁纸引擎现在拿着一个 null bitmap。" "它在等 MediaProvider。" "MediaProvider 在等 StorageManager 的 external。" "但 external 用户没插 SD 卡啊!" "为什么要等?" "因为代码就这样写的。" "愚蠢的代码。"

 

第四章:SurfaceFlinger 的孤独

SurfaceFlinger 是所有 Surface 的合成者。

它的工作:

  1. 1.接收所有应用提交的 Buffer 

  2. 2.合成到屏幕 

  3. 3.60 fps 输出 

但现在,壁纸引擎提交的是空 Buffer

壁纸引擎说:

"我还没准备好,给我 5 秒。"

SurfaceFlinger 说:

"那我先合成个黑色吧。"

黑色

屏幕一片漆黑。

用户看着这黑色,5 秒。

用户想:

"我手机怎么了?" "坏了?" "重启一下?"

5 秒。

人类耐心的极限。

 

第五章:MediaProvider 的苏醒

 

 

5 秒后,第一个 ContentResolver.query 调用抵达 MediaProvider。

init fork 出 android.process.media 进程。

MediaProvider 在新进程里启动:

[10.0] I MediaProvider: MediaProvider started

[10.0] I MediaProvider: Loading media database...

[10.0] I MediaProvider: Scanning /storage/emulated/0/DCIM

[10.0] I MediaProvider: Scanning /storage/emulated/0/Pictures

[10.0] I MediaProvider: Scanning /storage/emulated/0/Download

[10.5] I MediaProvider: Database loaded, 47823 images indexed

它醒了。

数据库建好了。

47823 张图片可以被查询了。

但这有什么意义?

壁纸引擎已经在 5.5 秒时调用过 queryImages() 了。 那 5 秒里,壁纸引擎等不到 MediaProvider,bitmap 一直是 null。 5 秒后兜底又调用了一次 queryImages()。 这次成功了。 但 bitmap 还是 null,因为 Glide 加载又失败了一次。

混乱。

所有的子系统都在努力。 但它们彼此不认识。 它们各自为政。 它们互相等待。

 

第六章:高琼的登场

工程师高琼是这些子系统之外的一个人。

她不写内核。 她不写 SystemServer。 她不写 SurfaceFlinger。

她只写壁纸引擎

壁纸引擎是 Android 世界最卑微的子系统之一。

壁纸引擎在 SurfaceFlinger 的合成列表里。 它在 WindowManager 的窗口列表里。 它在 ActivityManager 的 Activity 堆栈里。 但它永远在最底层

用户的眼睛先看到壁纸。 然后看到桌面图标。 然后看到 App 窗口。

壁纸是第一个被看到,但最后一个被想起的。

但高琼在意。

她说:

"壁纸是用户每天看到的第一个画面。" "壁纸出问题了,用户会以为整个系统出问题了。" "壁纸的 5 秒延迟,让用户的开机体验归零。"

她决定改变这一切

 

第七章:缓存的革命

高琼的方案很优雅:

在壁纸引擎中,加一个本地缓存。

每次壁纸引擎成功加载一张图片,就复制一份到应用私有目录。

这个目录叫 /data/data/com.stupidbeauty.dynamicwallpaper/cache/wallpapers/

这是系统中的孤儿,给自己留的后路。

为什么是孤儿?

因为:

  • SurfaceFlinger 不在乎壁纸 

  • WindowManager 不在乎壁纸 

  • MediaProvider 不在乎壁纸 

  • ActivityManager 觉得壁纸是低优先级 

但用户在乎。

用户每天开机第一眼看到的就是壁纸。

所以高琼在乎。

她做了两件事:

1. 缓存读取 在 wallpaperBitmap 之前,先去 cache 里找。 如果找到,直接读本地文件。 不等 MediaProvider。 不等 5 秒。 1 秒就显示。

2. 早期检查 在 reloadWallpaper 开头就检查缓存。 不等 queryImages 完成后才检查。

她把这一切叫做缓存原子操作

"缓存检查 + 缓存加载是一个不可分割的原子动作。" "一旦发动,就只能走完。"

 

第八章:1 秒与 5 秒的哲学

为什么是 1 秒?

1 秒 = 1000 毫秒。 1000 毫秒 = 60 帧。 人类可以接受的延迟。

为什么不能是 0 秒?

因为:

  • 文件读取需要时间 

  • 图片解码需要时间 

  • 屏幕刷新需要时间 

0 秒意味着壁纸不需要被加载。 但壁纸必须从某个地方被加载。 所以至少 1 秒。

1 秒的延迟,人类不会注意到。 5 秒的延迟,人类会开始担心

1 秒 = 完美。 5 秒 = 失败。

4 秒的差距,是整个工程的全部意义。

 

第九章:重启的轮回

每次开机,都是一次轮回

  • 世界被清空 

  • 系统被重新创建 

  • 服务被一个一个启动 

  • 混乱,混乱,混乱 

  • 然后稳定 

  • 然后显示 

壁纸引擎经历:

  • 死亡 

  • 重生 

  • 等待 

  • 显示 

每一次轮回都类似,但从不完全一样。

有时候 MediaProvider 醒得快,壁纸 2 秒显示。 有时候 MediaProvider 醒得慢,壁纸 7 秒显示。 有时候壁纸引擎的 onCreate 被调用,有时候不。

重启是不可预测的。

但缓存是可预测的。

只要有缓存,无论系统如何混乱,壁纸 1 秒显示。

这就是缓存的意义。

 

第十章:服务之间的恩怨

 

 

Android 系统的子系统之间,有着错综复杂的关系

有些是友谊

  • ActivityManager 和 WindowManager:互相需要 

  • SurfaceFlinger 和 WindowManager:互相配合 

有些是敌对

  • MediaProvider 和 SystemUI:MediaProvider 觉得 SystemUI 太傲慢 

  • PackageManager 和 Launcher:PackageManager 觉得 Launcher 是寄生虫 

  • WallpaperManager 和 WallpaperService:WallpaperManager 觉得 WallpaperService 太难伺候 

有些是暧昧

  • NetworkManager 和 WifiManager:相爱相杀 

  • PowerManager 和 BatteryService:互相利用 

壁纸引擎是孤独的。

它不属于任何派系。 它只是最底层的渲染者。 它画一片图,等待 SurfaceFlinger 来用。

它也最自由

因为没有人在乎它。 所以它可以做任何事。 比如缓存。 比如兜底。 比如早期检查

孤独是自由的代价。

 

第十一章:高琼的肖像

高琼,二十七岁,深圳。

她住在南山区一个老小区里,租了一间 30 平米的公寓。

她的房间里有一台电脑,两台显示器,一个机械键盘。 墙上贴着一张 Android 架构图,已经发黄了。

她养了一只猫,叫 null

为什么叫 null? 因为她第一次见到它的时候,它对她爱理不理,就像 null 一样——存在,但什么都没做。

null 现在 3 岁了。 还是爱理不理。

高琼每天工作到很晚。 她吃外卖,看 B 站,偶尔和 null 说几句话。

null 不回应。

就像 Android 系统。 但偶尔会响应一次。 比如跳到她腿上。 然后待五分钟。 然后跳走。

这 5 分钟,是高琼一天中最幸福的时刻。

她把这种幸福叫做短时窗口

"Android 系统也是这样的。平时不响应,但偶尔会给你 5 分钟的幸福。" "5 分钟对人类来说,足够快乐了。" "5 秒对系统来说,足够崩溃了。"

5 分钟 vs 5 秒。

5 分钟 = 幸福。 5 秒 = 崩溃。

 

第十二章:兜底

高琼在壁纸引擎中加了一个兜底机制

if (wallpaperBitmap == null && reloadInProgress == false) {

    reloadWallpaper(true);

}

如果 bitmap 是 null,就重新加载。

这是一个简单的逻辑。 但这是一个哲学

永远不要放弃。 永远给自己第二次机会。 永远相信下一秒会更好。

这就是兜底。

 

第十三章:双保险

高琼后来又加了一个双保险

  • 第一层:drawRunnable 兜底 

  • 第二层:独立 Handler 定时器 

  • 第三层:Application.onCreate 延迟检查 

三层防护。

她觉得自己像个战士

在守护一个看不见的城堡。

城堡里有什么?

一张图片。

但那张图片,是用户选择记住的世界的一个瞬间。

值得守护。

 

第十四章:动态壁纸的隐喻

壁纸有两种:

  • 静态壁纸:一张固定的图。死的东西。 

  • 动态壁纸:会自己换的图。活的东西。 

高琼做的是后者。

她做的不是壁纸。 她做的是生命的模拟

壁纸是背景。 壁纸是人类生活其上的东西

大多数人不知道壁纸的存在。 就像大多数人不知道空气的存在。 直到空气消失。

但壁纸工程师知道。 她每天都在想着那张图。 她每天都在让那张图活下去

 

第十五章:5 秒的隐喻

 

 

5 秒是虚无

5 秒是用户看着黑屏的绝望。 5 秒是壁纸引擎等待 MediaProvider 的徒劳。 5 秒是 SurfaceFlinger 显示空白的尴尬。

5 秒是 Android 系统的原罪。

每个服务都忙着启动。 每个服务都顾不上其他服务。 每个服务都以为自己是第一个。

它们不合作。 它们竞争。 它们各自为政。

5 秒是这场竞争的成本。

但高琼找到了绕开的方法。

缓存。

缓存是不合作者的礼物

不指望 MediaProvider 醒。 不指望 SurfaceFlinger 同步。 不指望 WindowManager 给位置。

只要有缓存,就有壁纸。

只要有壁纸,世界就不空。

 

第十六章:5 秒的尽头

 

 

故事的结尾,高琼在文档里写下:

"5 秒与 1 秒之差,是虚无与存在之差。"

"5 秒时,世界是黑屏。用户以为 App 坏了。" "1 秒时,世界显示了。用户以为理所当然。"

"5 秒 = 失败。" "1 秒 = 成功。"

"差别只有 4 秒。" "但这 4 秒,是整个工程的全部意义。"

"我们对抗的不是 5 秒的延迟。" "我们对抗的是遗忘。" "我们对抗的是虚无。" "我们对抗的是混乱。"

"缓存不是技术。" "缓存是人类对抗虚无的小小努力。"

"每一次开机,我们都在说:" "'世界,我还记得你。'"

 

(终)

Your opinions
Your name:Email:Website url:Opinion content: