开机动画时序
本文面向已经读过 启动时间分析、延迟启动、BootPhase阶段 和 SystemServer.run 的读者。此前文章已经区分 boot_progress_enable_screen、PHASE_BOOT_COMPLETED 和 sys.boot_completed;本文继续追踪其中最容易误判的一段:开机动画何时启动、谁请求它退出、系统怎样确认进程真的结束,以及动画结束为什么反过来决定 AMS 能否完成 boot。
BootAnimation 不是 SystemServer 内的一段 UI。它是 init 管理的独立 native 进程,在 SurfaceFlinger 提供的高层 Surface 上绘制。退出也不是一次 property write 就结束:WMS 先发退出请求,再轮询 init service 状态;确认 bootanim 停止后通知 SurfaceFlinger,最后才启用输入、回调 AMS。读完后,你应能解释三个启动事件的生产者,定位动画卡住属于素材、渲染、属性、init 进程状态还是窗口就绪问题。
1. 进程所有权
这条链跨越五个 owner:
| owner | 持有的关键状态 | 消费者 |
|---|---|---|
| init | bootanim service 进程状态 | WMS 的 SystemService.isRunning() |
| BootAnimation | EGL/Surface、动画 part、exitPending() | 用户可见帧与进程退出 |
| WMS | mSystemBooted、mBootAnimationStopped、mDisplayEnabled | 屏幕与 input dispatch |
| SurfaceFlinger | mBootFinished、frame timeline、exit 属性 | 图形运行期与事件日志 |
| AMS | mBootAnimationComplete、mCallFinishBooting | phase 1000 与 boot completed 属性 |
service.bootanim.exit 是跨进程请求通道,但不是状态的唯一来源。WMS 用它请求退出;BootAnimation 轮询它;SurfaceFlinger 在 bootFinished 中再次写入以封口。进程是否已经停止则由 init 的 init.svc.bootanim 决定。
2. init服务
源码文件:frameworks/base/cmds/bootanimation/bootanim.rc
service bootanim /system/bin/bootanimation
class core animation
user graphics
group graphics audio
disabled
oneshot
ioprio rt 0
task_profiles MaxPerformancedisabled 表示 class 启动不会自动拉起它,必须由 ctl.start=bootanim 显式启动;oneshot 表示正常退出后进入 stopped,不自动重启。ioprio rt 0 与 MaxPerformance 让素材读取和绘制尽快完成,但也意味着动画素材过重可能与启动关键 I/O、CPU/GPU 竞争。
init 在进程退出时清除 RUNNING。oneshot 且不是手工 restart/reset 时会置为 disabled,并把 init.svc.bootanim 更新为 stopped。
源码文件:system/core/init/service.cpp
pid_ = 0;
flags_ &= (~SVC_RUNNING);
start_order_ = 0;
was_last_exit_ok_ =
siginfo.si_code == CLD_EXITED
&& siginfo.si_status == 0;
if ((flags_ & SVC_ONESHOT)
&& !(flags_ & SVC_RESTART)
&& !(flags_ & SVC_RESET)) {
flags_ |= SVC_DISABLED;
}
if (flags_ & (SVC_DISABLED | SVC_RESET)) {
NotifyStateChange("stopped");
return;
}WMS 等待的正是这个进程状态,而不是 BootAnimation 内部某个 Java/native callback。
WMS 的正常等待目标是 Running → Stopped;init 测试关闭 oneshot 后观察到的则是 Running → Restarting。
3. SF启动
SurfaceFlinger 初始化期间异步执行 initBootProperties()。原因是 property_set() 依赖 init property service,后者可能被 late mount 等工作拖慢;不应让 SurfaceFlinger 主线程在这里阻塞。
源码文件:frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
mInitBootPropsFuture.callOnce([this] {
return std::async(
std::launch::async,
&SurfaceFlinger::initBootProperties,
this);
});worker 先重置 exit/progress,再请求 init 启动 bootanim。
void SurfaceFlinger::initBootProperties() {
property_set("service.sf.present_timestamp",
mHasReliablePresentFences ? "1" : "0");
if (base::GetBoolProperty(
"debug.sf.boot_animation"s, true)
&& (base::GetIntProperty(
"debug.sf.nobootanimation"s, 0)
== 0)) {
property_set("service.bootanim.exit", "0");
property_set("service.bootanim.progress", "0");
property_set("ctl.start", "bootanim");
}
}状态顺序不能交换。旧启动留下的 exit=1 如果未清零,新进程第一轮 checkExit() 就会立即退出;先 start 再 reset 也存在同样竞态。mInitBootPropsFuture 后面还会成为 bootFinished 的同步屏障,防止迟到的 init worker 把 WMS 已写成 1 的 exit 又覆盖为 0。
启动条件有两层。SurfaceFlinger 通过 debug.sf.boot_animation 和 debug.sf.nobootanimation 决定是否请求 init;bootanimation 进程自身还检查 no-animation 与 quiescent boot 条件。
源码文件:frameworks/base/cmds/bootanimation/BootAnimationUtil.cpp
bool bootAnimationDisabled() {
char value[PROPERTY_VALUE_MAX];
property_get("debug.sf.nobootanimation",
value, "0");
if (atoi(value) > 0) {
return true;
}
property_get("ro.boot.quiescent", value, "0");
if (atoi(value) > 0) {
if (!property_get_bool(
"ro.bootanim.quiescent.enabled",
false)) {
return true;
}
}
return false;
}即使 init 成功 fork,进程也可能因为自身判断直接返回;WMS 最终只关心它是否仍在 running。
4. 进程入口
源码文件:frameworks/base/cmds/bootanimation/bootanimation_main.cpp
int main() {
setpriority(PRIO_PROCESS, 0,
ANDROID_PRIORITY_DISPLAY);
bool noBootAnimation = bootAnimationDisabled();
ALOGI_IF(noBootAnimation,
"boot animation disabled");
if (!noBootAnimation) {
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
sp<BootAnimation> boot =
new BootAnimation(
audioplay::createAnimationCallbacks());
waitForSurfaceFlinger();
boot->run("BootAnimation",
PRIORITY_DISPLAY);
IPCThreadState::self()->joinThreadPool();
}
return 0;
}构造 BootAnimation 后,onFirstRef() 会预加载动画 zip;代码刻意在 waitForSurfaceFlinger() 之前完成这部分,使素材解析与 SurfaceFlinger 发布 Binder service 的窗口重叠。
源码文件:frameworks/base/cmds/bootanimation/BootAnimation.cpp
void BootAnimation::onFirstRef() {
ATRACE_CALL();
status_t err =
mSession->linkToComposerDeath(this);
SLOGE_IF(err,
"linkToComposerDeath failed (%s) ",
strerror(-err));
if (err == NO_ERROR) {
ALOGD("%sAnimationPreloadTiming start time: %"
PRId64 "ms",
mShuttingDown ? "Shutdown" : "Boot",
elapsedRealtime());
preloadAnimation();
ALOGD("%sAnimationPreloadTiming stop time: %"
PRId64 "ms",
mShuttingDown ? "Shutdown" : "Boot",
elapsedRealtime());
}
}等待逻辑使用 checkService("SurfaceFlinger") 每 100ms 轮询,不会触发 lazy start。等待超过一次周期才打印总耗时。
源码文件:frameworks/base/cmds/bootanimation/BootAnimationUtil.cpp
void waitForSurfaceFlinger() {
int64_t waitStartTime = elapsedRealtime();
sp<IServiceManager> sm =
defaultServiceManager();
const String16 name("SurfaceFlinger");
const int SERVICE_WAIT_SLEEP_MS = 100;
const int LOG_PER_RETRIES = 10;
int retry = 0;
while (sm->checkService(name) == nullptr) {
retry++;
if ((retry % LOG_PER_RETRIES) == 0) {
ALOGW("Waiting for SurfaceFlinger, waited for %"
PRId64 " ms",
elapsedRealtime() - waitStartTime);
}
usleep(SERVICE_WAIT_SLEEP_MS * 1000);
}
}SurfaceFlinger 死亡是不可恢复边界。BootAnimation 的 Surface 操作可能永久卡在已死亡 composer,代码直接给自己发送 SIGKILL,而不是只依赖 requestExit()。
void BootAnimation::binderDied(
const wp<IBinder>&) {
SLOGD("SurfaceFlinger died, exiting...");
kill(getpid(), SIGKILL);
requestExit();
}动画素材在 onFirstRef() 中预加载。当前 tag 的正常开机搜索顺序是 APEX、product、OEM、system;product 文件名还可由 ro.product.bootanim.file 指定,默认根据 ro.boot.theme 选择普通或 dark 文件。
源码文件:frameworks/base/cmds/bootanimation/BootAnimation.cpp
void BootAnimation::findBootAnimationFile() {
ATRACE_CALL();
const bool playDarkAnim =
android::base::GetIntProperty(
"ro.boot.theme", 0) == 1;
const std::string productBootanimationFile =
PRODUCT_BOOTANIMATION_DIR
+ android::base::GetProperty(
"ro.product.bootanim.file",
playDarkAnim
? PRODUCT_BOOTANIMATION_DARK_FILE
: PRODUCT_BOOTANIMATION_FILE);
static const std::vector<std::string> bootFiles = {
APEX_BOOTANIMATION_FILE,
productBootanimationFile,
OEM_BOOTANIMATION_FILE,
SYSTEM_BOOTANIMATION_FILE
};
static const std::vector<std::string> shutdownFiles = {
PRODUCT_SHUTDOWNANIMATION_FILE,
OEM_SHUTDOWNANIMATION_FILE,
SYSTEM_SHUTDOWNANIMATION_FILE,
""
};
static const std::vector<std::string>
userspaceRebootFiles = {
PRODUCT_USERSPACE_REBOOT_ANIMATION_FILE,
OEM_USERSPACE_REBOOT_ANIMATION_FILE,
SYSTEM_USERSPACE_REBOOT_ANIMATION_FILE,
};
if (android::base::GetBoolProperty(
"sys.init.userspace_reboot.in_progress",
false)) {
findBootAnimationFileInternal(
userspaceRebootFiles);
} else if (mShuttingDown) {
findBootAnimationFileInternal(
shutdownFiles);
} else {
findBootAnimationFileInternal(bootFiles);
}
}第一个可读文件成为 mZipFileName;所有文件都不可用时退回 stock Android logo。优化素材时必须先确认设备实际命中了哪条路径,不能只替换 /system/media/bootanimation.zip。
5. Surface与帧循环
readyToRun() 获取物理 display token,为每个启用显示创建名为 BootAnimation 的 opaque RGB565 Surface,并放在高 layer。Surface owner 是 bootanim 进程中的 SurfaceControl/Surface,显示合成仍由 SurfaceFlinger 完成。
源码文件:frameworks/base/cmds/bootanimation/BootAnimation.cpp
status_t BootAnimation::readyToRun() {
ATRACE_CALL();
mAssets.addDefaultAssets();
return initDisplaysAndSurfaces();
}
status_t BootAnimation::initDisplaysAndSurfaces() {
std::vector<PhysicalDisplayId> displayIds =
SurfaceComposerClient::getPhysicalDisplayIds();
if (displayIds.empty()) {
SLOGE("Failed to get ID for any displays");
return NAME_NOT_FOUND;
}
if (!com::android::graphics::bootanimation::flags
::multidisplay()) {
displayIds.erase(
displayIds.begin() + 1,
displayIds.end());
}创建 Surface 的核心片段如下:
display.surfaceControl =
session()->createSurface(
String8("BootAnimation"),
resolution.width,
resolution.height,
PIXEL_FORMAT_RGB_565,
ISurfaceComposerClient::eOpaque);
configureDisplayAndLayerStack(
display,
ui::LayerStack::fromValue(displayIdx));
display.surface =
display.surfaceControl->getSurface();
display.eglSurface = eglCreateWindowSurface(
mEgl, config,
display.surface.get(), nullptr);threadLoop() 在没有 zip 时运行 stock Android logo,否则运行 movie;两条路径结束后统一关闭音频 callback、解绑 EGL、销毁各 display Surface、停止 Binder process。
bool BootAnimation::threadLoop() {
ATRACE_CALL();
bool result;
initShaders();
if (mZipFileName.empty()) {
ALOGD("No animation file");
result = android(mDisplays.front());
} else {
result = movie();
}
mCallbacks->shutdown();
eglMakeCurrent(mEgl, EGL_NO_SURFACE,
EGL_NO_SURFACE,
EGL_NO_CONTEXT);
eglDestroyContext(mEgl, mEglContext);
for (auto& display : mDisplays) {
eglDestroySurface(
mEgl, display.eglSurface);
display.surface.clear();
display.surfaceControl.clear();
}
eglTerminate(mEgl);
eglReleaseThread();
IPCThreadState::self()->stopProcess();
return result;
}stock 动画限制为约 12fps,每帧 swap 后调用 checkExit();zip movie 按 desc fps 播放,也在每帧及长 pause 中最多每 50ms 检查一次退出。
void BootAnimation::checkExit() {
ATRACE_CALL();
char value[PROPERTY_VALUE_MAX];
property_get(EXIT_PROP_NAME,
value, "0");
int exitnow = atoi(value);
if (exitnow) {
requestExit();
}
}property 非零只把 Thread 置为 exit pending;具体在哪一 part 退出由动画格式决定,而不是立即 kill。
6. 退出语义
desc 中 c part 会设置 playUntilComplete=true,收到退出请求后仍播放完;普通 p part 可以被中断;f part 还能按配置完成 fade。
源码文件:frameworks/base/cmds/bootanimation/BootAnimation.cpp
part.playUntilComplete = pathType == 'c';
part.framesToFadeCount = framesToFadeCount;
part.count = count;
part.pause = pause;
part.path = path;解析结果进入每个 Animation::Part;播放循环再根据 playUntilComplete、fade 进度与 exit pending 决定是否中断:
bool BootAnimation::shouldStopPlayingPart(
const Animation::Part& part,
const int fadedFramesCount,
const int lastDisplayedProgress) {
return exitPending()
&& !part.playUntilComplete
&& fadedFramesCount
>= part.framesToFadeCount
&& (lastDisplayedProgress == 0
|| lastDisplayedProgress == 100);
}这解释了为什么 exit 属性已经变成 1,bootanim 仍可能短暂 running:它可能正在完成 c part、fade、当前帧 swap 或资源清理。WMS 必须观察进程状态,而不能把 property write 当作完成通知。
平台源码在本 tag 中只看到 SurfaceFlinger 把 service.bootanim.progress 重置为 0,以及 BootAnimation 在退出时把未完成进度补到 100;没有 Framework/SystemServer 生产者持续写入中间进度。产品若启用 desc 的 progress 显示,需要自己的 producer,不能假设 PMS 扫描会自动更新它。
7. WMS开屏门槛
AMS 的 ensureBootCompleted() 会先尝试 finishBooting(),再调用 ATMS 的 enableScreenAfterBoot()。若动画尚未完成,finishBooting() 设置 mCallFinishBooting=true 后返回;后面要靠 WMS 的完成回调重新进入。
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
final void ensureBootCompleted() {
boolean booting;
boolean enableScreen;
synchronized (mGlobalLock) {
booting = mBooting;
mBooting = false;
enableScreen = !mBooted;
mBooted = true;
}
if (booting) {
finishBooting();
}
if (enableScreen) {
mAtmInternal.enableScreenAfterBoot(
mBooted);
}
}ATMS 先写 boot_progress_enable_screen,再进入 WMS。这个事件仅表示开始尝试开屏。
源码文件:frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@Override
public void enableScreenAfterBoot(
boolean booted) {
writeBootProgressEnableScreen(
SystemClock.uptimeMillis());
mWindowManager.enableScreenAfterBoot();
synchronized (mGlobalLock) {
updateEventDispatchingLocked(booted);
}
}WMS 将 mSystemBooted=true,隐藏 boot message,并启动 30 秒 timeout。然后 performEnableScreen() 逐项检查 policy、checkpoint 与所有 system decor windows 是否 drawn。
源码文件:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public void enableScreenAfterBoot() {
synchronized (mGlobalLock) {
if (mSystemBooted) {
return;
}
mSystemBooted = true;
hideBootMessagesLocked();
mH.sendEmptyMessageDelayed(
H.BOOT_TIMEOUT,
30 * 1000
* Build.HW_TIMEOUT_MULTIPLIER);
}
mPolicy.systemBooted();
performEnableScreen();
}真正的退出握手从这里开始:
if (!mBootAnimationStopped) {
Trace.asyncTraceBegin(
TRACE_TAG_WINDOW_MANAGER,
"Stop bootanim", 0);
SystemProperties.set(
"service.bootanim.exit", "1");
mBootAnimationStopped = true;
}
if (!mForceDisplayEnabled
&& !checkBootAnimationCompleteLocked()) {
return;
}
if (!SurfaceControl.bootFinished()) {
ProtoLog.w(WM_ERROR,
"performEnableScreen: bootFinished() failed.");
return;
}
EventLogTags.writeWmBootAnimationDone(
SystemClock.uptimeMillis());
Trace.asyncTraceEnd(
TRACE_TAG_WINDOW_MANAGER,
"Stop bootanim", 0);
mDisplayEnabled = true;
mInputManagerCallback.setEventDispatchingLw(
mEventDispatchingEnabled);mBootAnimationStopped 防止重复写 exit 和重复开启 async trace。正常路径在调用 SurfaceFlinger 前必须确认 init service 已不再 running。
private boolean checkBootAnimationCompleteLocked() {
if (SystemService.isRunning(
BOOT_ANIMATION_SERVICE)) {
mH.removeMessages(
H.CHECK_IF_BOOT_ANIMATION_FINISHED);
mH.sendEmptyMessageDelayed(
H.CHECK_IF_BOOT_ANIMATION_FINISHED,
BOOT_ANIMATION_POLL_INTERVAL);
return false;
}
return true;
}poll interval 是 50ms。Handler 收到检查消息后,如果已经停止就再次调用 performEnableScreen();这次通过进程状态门槛。
case CHECK_IF_BOOT_ANIMATION_FINISHED: {
final boolean bootAnimationComplete;
synchronized (mGlobalLock) {
bootAnimationComplete =
checkBootAnimationCompleteLocked();
}
if (bootAnimationComplete) {
performEnableScreen();
}
break;
}下面的流程图把首次开屏、50ms 进程轮询和 30 秒恢复路径放在同一决策链中:
8. SF封口
SurfaceControl.bootFinished() 经 JNI 和 SurfaceComposerClient 调用 SurfaceFlinger AIDL;服务端先执行 access permission check。
源码文件:frameworks/base/core/jni/android_view_SurfaceControl.cpp
static jboolean nativeBootFinished(
JNIEnv* env, jclass clazz) {
status_t error =
SurfaceComposerClient::bootFinished();
return error == OK
? JNI_TRUE : JNI_FALSE;
}源码文件:frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
binder::Status SurfaceComposerAIDL::bootFinished() {
status_t status = checkAccessPermission();
if (status != OK) {
return binderStatusFromStatusT(status);
}
mFlinger->bootFinished();
return binder::Status::ok();
}SurfaceFlinger 只接受第一次 bootFinished。它先等待异步 boot property 初始化和 RenderEngine cache prime 完成,再切换 frame tracing/timeline 到运行期状态。
void SurfaceFlinger::bootFinished() {
if (mBootFinished == true) {
ALOGE("Extra call to bootFinished");
return;
}
mBootFinished = true;
FlagManager::getMutableInstance()
.markBootCompleted();
mInitBootPropsFuture.wait();
mRenderEnginePrimeCacheFuture.wait();
const nsecs_t now = systemTime();
const nsecs_t duration = now - mBootTime;
ALOGI("Boot is finished (%ld ms)",
long(ns2ms(duration)));
mFrameTracer->initialize();
mFrameTimeline->onBootFinished();等待 mInitBootPropsFuture 是属性竞态屏障:确保 start worker 已经完成 exit=0/reset/start,随后再次写 exit=1 才不会被旧任务覆盖。
property_set("service.bootanim.exit", "1");
const int LOGTAG_SF_STOP_BOOTANIM = 60110;
LOG_EVENT_LONG(
LOGTAG_SF_STOP_BOOTANIM,
ns2ms(systemTime(
SYSTEM_TIME_MONOTONIC)));正常 WMS 路径中,bootanim 此时已经停止,所以 sf_stop_bootanim 的名字不能机械解释为“SurfaceFlinger 刚发出首次退出请求”。它记录的是 SurfaceFlinger 接收 bootFinished 并完成自身启动封口的时间点。WMS 随后写 wm_boot_animation_done,二者接近但 owner 和时钟 API不同。
9. AMS回调
WMS 在设置 mDisplayEnabled=true、启用 input dispatch 后,才跨 Binder 调用 AMS 的 bootAnimationComplete()。
源码文件:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
try {
mActivityManager.bootAnimationComplete();
} catch (RemoteException e) {
}
mPolicy.enableScreenAfterBoot();
updateRotationUnchecked(false, false);AMS 保存完成标志;如果之前 finishBooting() 因动画未完成而设置了 mCallFinishBooting,现在重新进入并推进 phase 1000、sys.boot_completed 与 boot broadcasts。
源码文件:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
@Override
public void bootAnimationComplete() {
final boolean callFinishBooting;
synchronized (mGlobalLock) {
callFinishBooting =
mCallFinishBooting;
mBootAnimationComplete = true;
}
if (callFinishBooting) {
finishBooting();
}
}所以动画完成不仅是视觉事件,还是 Framework boot completion 的一个条件屏障。屏幕启用请求可以早于动画结束;真正的 phase 1000 又可能晚于 wm_boot_animation_done。
10. 指标边界
三条常用事件的生产者如下:
| 事件 | 写入位置 | 实际含义 |
|---|---|---|
boot_progress_enable_screen | ATMS | 开始调用 WMS 尝试开屏 |
sf_stop_bootanim | SurfaceFlinger | 接收 bootFinished、完成 SF 启动封口 |
wm_boot_animation_done | WMS | SF bootFinished 成功,即将设置 display enabled |
官方 perfboot.py 默认将它们按该顺序列在末尾,并以最后一个 tag 作为默认结束条件。
源码文件:system/core/init/perfboot.py
'boot_progress_ams_ready',
'boot_progress_enable_screen',
'sf_stop_bootanim',
'wm_boot_animation_done',上面是 _DEFAULT_EVENT_TAGS 的末尾四项;完整列表还包含 Zygote、SystemServer 与 PMS 里程碑。
它从 events buffer 解析 payload,并以 (tag, pid) 保存。比较时不能使用 logcat 的 wall-clock 前缀。
tag = m.group('tag')
event_time = int(m.group('time'))
pid = m.group('pid')
record[(tag, pid)] = event_time
if tag == end_tag:
booted = True正常路径通常是 enable_screen < sf_stop ≈ wm_done < sys.boot_completed;但强制开屏、runtime restart、无动画配置或调用失败都可能改变关系。指标分析必须结合 init.svc.bootanim 和 WMS 状态。
11. 超时与失败
WMS 的 30 秒 BOOT_TIMEOUT 是恢复路径。超时后 mForceDisplayEnabled=true,再次进入 performEnableScreen() 时跳过 system decor drawn 和 bootanim stopped 的等待,但仍请求 exit、调用 SurfaceFlinger bootFinished、启用 display/input 并回调 AMS。
public void performBootTimeout() {
synchronized (mGlobalLock) {
if (mDisplayEnabled) {
return;
}
ProtoLog.w(WM_ERROR,
"***** BOOT TIMEOUT: forcing display enabled");
mForceDisplayEnabled = true;
}
performEnableScreen();
}主要失败边界可以按 owner 排查:
- SurfaceFlinger 没有请求 start:检查
debug.sf.boot_animation、debug.sf.nobootanimation和 property service; - bootanim 启动即退:检查 quiescent/no-animation 条件、素材加载、display token 与 EGL;
- exit=1 但长期 running:检查
cpart、帧 pause、音频 callback、EGL swap 和进程栈; - init 已 stopped 但屏幕未启用:检查 policy、checkpoint、system decor drawn、SurfaceControl.bootFinished 返回值;
- 30 秒后才启用:说明正常门槛至少一个未满足,不能只通过缩短动画 zip 掩盖;
- SurfaceFlinger 死亡:BootAnimation 自杀,等待 SF/init 重启链恢复。
强制路径可能在 bootanim 进程尚未退出时记录 SF/WMS 事件,因此事件名不等于进程状态。诊断必须同时采集属性和 init state。
12. 验证方法
init 的 oneshot_on_test 在已完成启动的设备上先断言 bootanim stopped;关闭 oneshot 后手工启动,进程快速退出并进入 restarting;重新开启 oneshot 再启动,最终进入 stopped。输入是同一个 bootanim service 的动态 oneshot flag,断言覆盖 init 对退出状态与自动重启策略的处理,不覆盖 EGL 帧内容。
源码文件:system/core/init/oneshot_on_test.cpp
ASSERT_EQ("stopped",
GetProperty("init.svc.bootanim", ""));
SetProperty("ctl.oneshot_off", "bootanim");
SetProperty("ctl.start", "bootanim");
EXPECT_TRUE(WaitForProperty(
"init.svc.bootanim", "restarting", 10s));
SetProperty("ctl.oneshot_on", "bootanim");
SetProperty("ctl.start", "bootanim");
EXPECT_TRUE(WaitForProperty(
"init.svc.bootanim", "stopped", 10s));当前 tag 没有覆盖 WMS exit property、50ms polling、SurfaceFlinger bootFinished 和 AMS callback 的单一端到端单元测试。AOSP 的 perfboot.py 提供运行实验,但它只记录事件时间,不断言进程间因果。修改握手逻辑时,应增加可控 init-state/property/SurfaceControl fake,验证正常、bootFinished 失败和 BOOT_TIMEOUT 三条路径。
设备上的只读诊断可以把请求、进程状态和事件分开采集:
# 输入:刚完成或正在进行的启动。输出:退出请求值、进程状态和关键完成属性。
adb shell getprop service.bootanim.exit
adb shell getprop init.svc.bootanim
adb shell getprop sys.boot_completed
# 输入:events buffer。输出:ATMS、SF、WMS三个不同owner写入的monotonic时间点。
adb logcat -b events -v threadtime -d \
| rg 'boot_progress_enable_screen|sf_stop_bootanim|wm_boot_animation_done'
# 输入:system/main日志。输出:WMS门槛、bootanim素材/EGL和SurfaceFlinger封口日志。
adb logcat -b all -v threadtime -d \
| rg 'BootAnimation|Waiting for anim complete|BOOT TIMEOUT|Boot is finished|bootFinished'需要复现实验时不要在生产设备随意修改启动属性。userdebug/eng 设备可以在重启前设置 debug.sf.nobootanimation=1 做对照,比较动画 I/O/GPU 成本;实验结束必须恢复为 0,并保持相同构建、温度和启动终点。
检查自己是否读通这条源码,可以回答两个问题:为什么 exit=1 后 WMS 仍每 50ms 等 init.svc.bootanim,以及为什么 sf_stop_bootanim 正常情况下反而发生在 bootanim 进程退出之后。能区分“退出请求、进程停止、SurfaceFlinger 封口、屏幕启用、AMS 完成”五个事件后,才不会用一条动画日志误判系统启动瓶颈。
