Skip to content

开机动画时序

追踪 init、BootAnimation、WMS、SurfaceFlinger 和 AMS 之间的开机动画启动、退出确认与屏幕启用握手。

基于android-17.0.0_r1
AndroidBootAnimationSurfaceFlingerWindowManagerinit启动性能源码阅读

开机动画时序 ​

本文面向已经读过 启动时间分析、延迟启动、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持有的关键状态消费者
initbootanim service 进程状态WMS 的 SystemService.isRunning()
BootAnimationEGL/Surface、动画 part、exitPending()用户可见帧与进程退出
WMSmSystemBooted、mBootAnimationStopped、mDisplayEnabled屏幕与 input dispatch
SurfaceFlingermBootFinished、frame timeline、exit 属性图形运行期与事件日志
AMSmBootAnimationComplete、mCallFinishBootingphase 1000 与 boot completed 属性

service.bootanim.exit 是跨进程请求通道,但不是状态的唯一来源。WMS 用它请求退出;BootAnimation 轮询它;SurfaceFlinger 在 bootFinished 中再次写入以封口。进程是否已经停止则由 init 的 init.svc.bootanim 决定。

2. init服务 ​

源码文件:frameworks/base/cmds/bootanimation/bootanim.rc

text
service bootanim /system/bin/bootanimation
    class core animation
    user graphics
    group graphics audio
    disabled
    oneshot
    ioprio rt 0
    task_profiles MaxPerformance

disabled 表示 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

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

cpp
mInitBootPropsFuture.callOnce([this] {
    return std::async(
            std::launch::async,
            &SurfaceFlinger::initBootProperties,
            this);
});

worker 先重置 exit/progress,再请求 init 启动 bootanim。

cpp
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

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

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

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

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()。

cpp
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

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

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 的核心片段如下:

cpp
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。

cpp
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 检查一次退出。

cpp
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

cpp
part.playUntilComplete = pathType == 'c';
part.framesToFadeCount = framesToFadeCount;
part.count = count;
part.pause = pause;
part.path = path;

解析结果进入每个 Animation::Part;播放循环再根据 playUntilComplete、fade 进度与 exit pending 决定是否中断:

cpp
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

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

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

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();
}

真正的退出握手从这里开始:

java
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。

java
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();这次通过进程状态门槛。

java
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

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

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 到运行期状态。

cpp
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 才不会被旧任务覆盖。

cpp
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

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

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_screenATMS开始调用 WMS 尝试开屏
sf_stop_bootanimSurfaceFlinger接收 bootFinished、完成 SF 启动封口
wm_boot_animation_doneWMSSF bootFinished 成功,即将设置 display enabled

官方 perfboot.py 默认将它们按该顺序列在末尾,并以最后一个 tag 作为默认结束条件。

源码文件:system/core/init/perfboot.py

python
'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 前缀。

python
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。

java
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:检查 c part、帧 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

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 三条路径。

设备上的只读诊断可以把请求、进程状态和事件分开采集:

bash
# 输入:刚完成或正在进行的启动。输出:退出请求值、进程状态和关键完成属性。
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 完成”五个事件后,才不会用一条动画日志误判系统启动瓶颈。