Android Framework源码解析(七):BootAnimation 启动流程解析——开机动画是如何显示出来的?

上一篇我们完整梳理了 Launcher3 桌面启动全流程,理解系统如何拉起桌面应用并渲染 UI。 但有一个直观问题容易被忽略:从按下电源键到 Launcher 完整显示,中间长达数秒的时间屏幕并非黑屏,而是持续播放厂商定制开机动画,完整链路为: Google Logo 静态帧 → BootAnimation 逐帧动画 → Launcher 桌面

本篇围绕 4 个核心问题,结合完整 Native+Java 层源码拆解全链路:

  1. 开机动画进程由谁启动?
  2. 为何 Framework 服务未完全就绪时,动画就能正常渲染输出?
  3. SurfaceFlinger 与 BootAnimation 的依赖、协作关系是什么?
  4. BootAnimation 如何精准感知退出时机,动画结束后 Launcher 如何接管屏幕?

完整系统启动骨架链路: init 进程 → SurfaceFlinger → BootAnimation → WindowManagerService → Launcher

一、BootAnimation 进程启动源头:init 配置与启动规则

1.1 启动配置文件 bootanim.rc

路径:frameworks/base/cmds/bootanimation/bootanim.rc

bash 复制代码
service bootanim /system/bin/bootanimation
    class core animation
    user graphics
    group graphics audio
    disabled
    oneshot
    ioprio rt 0
    task_profiles MaxPerformance

关键配置释义:

  1. disabled:默认不会由 init 自动拉起,必须由 SurfaceFlinger 初始化完成后通过属性 ctl.start bootanim 主动触发启动
  2. class core animation:归属核心动画进程组,系统关机时统一回收;
  3. user/group graphics:赋予图形、音频硬件访问权限,保证 OpenGL 渲染、动画音效可用;
  4. oneshot:进程执行完成后自动销毁,不会反复重启;
  5. ioprio rt 0:实时 IO 优先级,避免动画加载、解码卡顿。

1.2 启动时序关键:SurfaceFlinger 是前置依赖

内核启动后第一个用户态进程 init,会优先解析 rc 脚本启动 surfaceflinger 图形合成服务。 SurfaceFlinger 完成 EGL、显示硬件、BufferQueue 初始化后,会通过属性线程执行: property_set("ctl.start", "bootanim") 通知 init 拉起 /system/bin/bootanimation 二进制程序。 依赖逻辑:BootAnimation 所有渲染、图层创建都依赖 SurfaceFlinger,因此必须等 SF 就绪后才能启动动画进程。

1.3 程序入口 bootanimation_main.cpp

路径: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) {
        // 初始化Binder线程池,后续与SF、WMS通信依赖Binder
        sp<ProcessState> proc(ProcessState::self());
        ProcessState::self()->startThreadPool();

        // 实例化开机动画核心类,加载/system/media/bootanimation.zip,初始化音频回调
        sp<BootAnimation> boot = new BootAnimation(audioplay::createAnimationCallbacks());
        
        // 阻塞等待SurfaceFlinger服务Binder通道可用
        waitForSurfaceFlinger();
        
        // 启动动画子线程,主线程阻塞等待播放完成
        boot->run("BootAnimation", PRIORITY_DISPLAY);

        ALOGV("Boot animation set up. Joining binder thread pool.");
        // 动画播放结束,主线程进入Binder循环,维持进程不立即销毁
        IPCThreadState::self()->joinThreadPool();
    }
    return 0;
}
辅助判断函数 bootAnimationDisabled ()
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;
}

二、BootAnimation 线程执行时序:readyToRun → threadLoop 完整流程

BootAnimation 继承系统 Thread 基类,boot->run() 内部会创建独立 pthread 子线程,底层静态入口 _threadLoop 执行固定两步逻辑:

  1. 优先执行 readyToRun():一次性初始化 EGL、多屏幕 Surface、着色器、渲染环境;
  2. 初始化无异常才进入 do-while 循环,反复执行 threadLoop() 播放动画。

完整时序图

bash 复制代码
boot->run("BootAnimation", PRIORITY_DISPLAY);
    Thread::run() → pthread_create 创建独立动画子线程
        底层静态入口 _threadLoop()
            第一步:BootAnimation::readyToRun()
                mAssets.addDefaultAssets();
                initDisplaysAndSurfaces() // 初始化多屏幕、EGL、图层Surface
                返回 NO_ERROR 代表图形环境就绪
            第二步:进入 do-while 循环,循环调用 threadLoop()
                threadLoop()
                    movie() // 加载zip动画资源
                        playAnimation() // 逐帧渲染主逻辑
                            每帧渲染末尾调用 checkExit() 检测退出属性

2.1 readyToRun ():动画播放前一次性图形环境初始化

路径:frameworks/base/cmds/bootanimation/BootAnimation.cpp

cpp 复制代码
status_t BootAnimation::readyToRun() {
    ATRACE_CALL();
    // 注册系统资源加载路径,用于读取zip内图片、字体
    mAssets.addDefaultAssets();
    // 核心:初始化屏幕、EGL渲染上下文、Surface图层
    return initDisplaysAndSurfaces();
}
核心初始化 initDisplaysAndSurfaces ()
cpp 复制代码
status_t BootAnimation::initDisplaysAndSurfaces() {
    // 1. 通过SurfaceComposerClient获取本机所有物理屏幕ID
    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());
    }

    // 遍历所有有效屏幕,获取屏幕唯一Token保存
    for (const auto id : displayIds) {
        const auto token = SurfaceComposerClient::getPhysicalDisplayToken(id);
        if (!token.isValid()) {
            SLOGE("Failed to get display token for display %" PRIu64, id.value);
            return NAME_NOT_FOUND;
        }
        mDisplays.push_back({.displayToken = token});
    }

    // 全局EGL初始化,创建GLES2.0渲染上下文(所有屏幕共享一套GL上下文)
    mEgl = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    eglInitialize(mEgl, nullptr, nullptr);
    EGLConfig config = getEglConfig(mEgl);
    EGLint contextAttributes[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
    mEglContext = eglCreateContext(mEgl, config, nullptr, contextAttributes);

    // 读取系统属性限制图层最大分辨率,防止超大折叠屏显存溢出
    mMaxWidth = android::base::GetIntProperty("ro.surface_flinger.max_graphics_width", 0);
    mMaxHeight = android::base::GetIntProperty("ro.surface_flinger.max_graphics_height", 0);

    // 为每一块物理屏幕创建独立Surface图层与EGL渲染窗口
    for (size_t displayIdx = 0; displayIdx < mDisplays.size(); displayIdx++) {
        auto& display = mDisplays[displayIdx];
        DisplayMode displayMode;
        // 查询屏幕当前生效分辨率、刷新率
        status_t error = SurfaceComposerClient::getActiveDisplayMode(display.displayToken, &displayMode);
        if (error != NO_ERROR) return error;
        ui::Size resolution = displayMode.resolution;
        // 根据全局限制裁剪分辨率
        resolution = limitSurfaceSize(resolution.width, resolution.height);

        // 向SurfaceFlinger创建专属图层BootAnimation,像素格式RGB565不透明,节省显存
        display.surfaceControl = session()->createSurface(
                String8("BootAnimation"), resolution.width, resolution.height,
                PIXEL_FORMAT_RGB_565, ISurfaceComposerClient::eOpaque);
        // 将图层绑定到对应屏幕的LayerStack,输出画面至当前物理屏
        configureDisplayAndLayerStack(display, ui::LayerStack::fromValue(displayIdx));
        display.surface = display.surfaceControl->getSurface();
        // 基于Native窗口创建EGL渲染表面,作为OpenGL绘制目标
        display.eglSurface = eglCreateWindowSurface(mEgl, config, display.surface.get(), nullptr);

        // 查询EGL表面实际宽高
        EGLint w, h;
        eglQuerySurface(mEgl, display.eglSurface, EGL_WIDTH, &w);
        eglQuerySurface(mEgl, display.eglSurface, EGL_HEIGHT, &h);
        // 绑定当前屏幕EGL上下文,后续GL绘制全部输出到此屏幕
        if (eglMakeCurrent(mEgl, display.eglSurface, display.eglSurface, mEglContext) == EGL_FALSE) {
            return NO_INIT;
        }
        display.initWidth = display.width = w;
        display.initHeight = display.height = h;
        mTargetInset = -1;

        // 根据系统属性 ro.bootanim.set_orientation_<display_id> 旋转动画画面,适配平板横屏开机
        rotateAwayFromNaturalOrientationIfNeeded(display);
        // 初始化投影矩阵,用于动画居中、缩放、旋转变换
        projectSceneToWindow(display);
    }

    // 注册屏幕热插拔、分辨率变化监听,动态适配画面
    mDisplayEventReceiver = std::make_unique<DisplayEventReceiver>();
    status_t status = mDisplayEventReceiver->initCheck();
    SLOGE_IF(status != NO_ERROR, "DisplayEventReceiver init failed: %d", status);
    // 将事件fd注册到主线程Looper,异步接收屏幕变更
    mLooper->addFd(mDisplayEventReceiver->getFd(), 0, Looper::EVENT_INPUT,
                   new DisplayEventCallback(this), nullptr);

    return NO_ERROR;
}
本段核心价值

Framework(system_server、WMS、AMS)尚未启动时,仅依赖 SurfaceFlinger 即可完成图层、EGL 初始化,这也是开机动画能提前渲染的核心原因: BootAnimation 与 SF 均为 Native 独立进程,不依赖 Java 层系统服务。

2.2 threadLoop ():动画主循环入口

cpp 复制代码
bool BootAnimation::threadLoop() {
    ATRACE_CALL();
    bool result;
    // 预编译图片渲染着色器
    initShaders();

    // 判断是否存在bootanimation.zip资源
    if (mZipFileName.empty()) {
        ALOGD("No animation zip file, render default Android logo");
        // 无自定义动画,渲染原生静态Android文字Logo
        result = android(mDisplays.front());
    } else {
        // 标准流程:加载并播放zip逐帧动画
        result = movie();
    }

    // 动画播放完毕,统一销毁全部图形资源
    mCallbacks->shutdown();
    // 解绑EGL上下文
    eglMakeCurrent(mEgl, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroyContext(mEgl, mEglContext);
    // 释放所有屏幕EGL窗口与Surface图层
    for (auto& display : mDisplays) {
        eglDestroySurface(mEgl, display.eglSurface);
        display.surface.clear();
        display.surfaceControl.clear();
    }
    eglTerminate(mEgl);
    eglReleaseThread();
    // 终止Binder通信
    IPCThreadState::self()->stopProcess();
    return result;
}

2.3 movie ():解析 zip 动画资源,配置渲染参数

cpp 复制代码
bool BootAnimation::movie() {
    ATRACE_CALL();
    // 解析/system/media/bootanimation.zip,加载desc.txt分段配置、所有帧图片
    if (mAnimation == nullptr) {
        mAnimation = loadAnimation(mZipFileName);
    }
    if (mAnimation == nullptr)
        return false;

    // 递归初始化各分段音频播放回调,处理嵌套子动画
    for (const Animation::Part& part : mAnimation->parts) {
        if (part.animation != nullptr) {
            mCallbacks->init(part.animation->parts);
        }
    }
    mCallbacks->init(mAnimation->parts);

    // 检测动画分段是否配置右上角时钟,初始化时钟渲染参数
    bool anyPartHasClock = false;
    for (size_t i = 0; i < mAnimation->parts.size(); i++) {
        if (validClock(mAnimation->parts[i])) {
            anyPartHasClock = true;
            break;
        }
    }
    if (!anyPartHasClock) {
        mClockEnabled = false;
    } else {
        // 读取开关属性,控制时钟是否显示
        mClockEnabled = android::base::GetBoolProperty(CLOCK_ENABLED_PROP, false);
    }

    // 检测GPU是否支持非2次幂纹理,适配各类图片素材
    mUseNpotTextures = false;
    String8 glExtensions((const char*)glGetString(GL_EXTENSIONS));
    if (glExtensions.contains("GL_OES_texture_npot")) {
        mUseNpotTextures = true;
    }

    // 配置全局OpenGL混合、抗锯齿基础参数
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glDisable(GL_DITHER);
    glDisable(GL_SCISSOR_TEST);
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, 0);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // 加载时钟、开机进度条字体纹理
    bool clockFontOk = false;
    if (mClockEnabled) {
        clockFontOk = (initFont(&mClockFont, "clockfont") == NO_ERROR);
        if (!clockFontOk) mClockEnabled = false;
    }
    initFont(&mProgressFont, "progressfont");

    // 时钟校准子线程,实时更新系统时间用于渲染
    if (mClockEnabled && !mTimeIsAccurate) {
        mTimeCheckThread = new TimeCheckThread(this);
        mTimeCheckThread->run("BootAnimTime", PRIORITY_LOW);
    }

    // 初始化动态主题渐变着色器
    if (mAnimation->dynamicColoringEnabled) {
        initDynamicColorShaders();
    }

    // 核心:阻塞播放完整动画,逐帧渲染逻辑
    playAnimation(*mAnimation);

    // 动画播放完成,回收时钟校准子线程
    if (mTimeCheckThread != nullptr) {
        mTimeCheckThread->requestExit();
        mTimeCheckThread->join();
        mTimeCheckThread = nullptr;
    }

    // 释放时钟纹理显存
    if (clockFontOk) {
        glDeleteTextures(1, &mClockFont.textureId);
    }
    glDeleteTextures(1, &mProgressFont.textureId);
    // 释放全部动画帧资源
    releaseAnimation(mAnimation);
    mAnimation = nullptr;

    return false;
}

2.4 playAnimation ():逐帧渲染核心逻辑(最关键循环)

cpp 复制代码
bool BootAnimation::playAnimation(const Animation& animation) {
    ATRACE_CALL();
    const size_t totalParts = animation.parts.size();
    // 计算单帧标准间隔(纳秒),锁定动画FPS
    nsecs_t frameIntervalNs = s2ns(1) / animation.fps;

    SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms",
          mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime());

    int fadeFrameCount = 0; // 关机淡出帧计数
    int lastProgress = 0;  // 开机进度条缓存值
    int colorTransStart = animation.colorTransitionStart;
    int colorTransEnd = animation.colorTransitionEnd;

    // 第一层循环:遍历desc.txt定义的所有动画分段Part
    for (size_t partIdx = 0; partIdx < totalParts; partIdx++) {
        const Animation::Part& part = animation.parts[partIdx];
        const size_t frameCountInPart = part.frames.size();
        glBindTexture(GL_TEXTURE_2D, 0);

        // 支持嵌套子zip动画,递归播放
        if (part.animation != nullptr) {
            playAnimation(*part.animation);
            if (exitPending()) break;
            continue;
        }

        // 第二层循环:控制分段循环次数(无限循环/指定次数/淡出收尾)
        for (int loopIdx = 0;
             part.loopCount == 0 || loopIdx < part.loopCount || fadeFrameCount > 0;
             loopIdx++) {
            // 检测是否需要终止当前分段
            if (shouldStopPart(part, fadeFrameCount, lastProgress)) break;

            // 动态主题属性延迟加载重试逻辑
            if (animation.dynamicColoringEnabled && !mDynamicColorLoaded
                && (part.useDynamicColor || part.postDynamicColor)) {
                SLOGD("Reload dynamic color system props");
                loadDynamicColors();
                if (mDynamicColorLoaded) {
                    int transLen = colorTransEnd - colorTransStart;
                    if (part.postDynamicColor) {
                        colorTransStart = 0;
                        colorTransEnd = std::min(transLen, (int)frameCountInPart - 1);
                    }
                }
            }

            // 触发当前分段配套音频播放
            mCallbacks->playPart(partIdx, part, loopIdx);
            // 设置分段背景清屏色
            glClearColor(part.bgColor[0], part.bgColor[1], part.bgColor[2], 1.0f);

            bool drawProgressBar = animation.progressEnabled
                    && partIdx == totalParts - 1
                    && android::base::GetIntProperty(PROGRESS_PROP, 0) != 0;

            // 第三层循环:遍历当前分段内所有图片帧
            for (size_t frameIdx = 0; frameIdx < frameCountInPart; frameIdx++) {
                if (shouldStopPart(part, fadeFrameCount, lastProgress)) break;
                // 计算动态主题渐变系数 [0 ~ 1]
                float colorProgress = part.useDynamicColor
                    ? std::clamp(
                        ((float)frameIdx - colorTransStart) / std::max(colorTransEnd - colorTransStart, 1.0f),
                        0.0f, 1.0f)
                    : (part.postDynamicColor ? 1.0f : 0.0f);
                // 处理屏幕分辨率、热插拔事件
                processDisplayEvents();

                const Animation::Frame& frame = part.frames[frameIdx];
                nsecs_t frameBeginTime = systemTime();

                // 纹理复用逻辑:首次循环创建纹理,后续直接复用显存,避免重复解码
                if (loopIdx > 0) {
                    glBindTexture(GL_TEXTURE_2D, frame.textureId);
                } else {
                    if (part.loopCount != 1) {
                        glGenTextures(1, &frame.textureId);
                        glBindTexture(GL_TEXTURE_2D, frame.textureId);
                    }
                    int w, h;
                    // 解码图片至纹理,保留原始Alpha通道不预乘
                    loadTexture(frame.buffer, &w, &h, false);
                }

                // 收到退出信号且分段配置淡出动画,计算透明度衰减值
                float fadeAlpha = 0.0f;
                if (exitPending() && part.enableFadeOut) {
                    fadeAlpha = (float)(++fadeFrameCount) / part.fadeTotalFrames;
                    if (fadeFrameCount >= part.fadeTotalFrames) {
                        fadeFrameCount = MAX_FADE_FRAME;
                    }
                }

                // 多屏幕循环:逐屏绘制当前帧
                for (const auto& display : mDisplays) {
                    // 切换EGL渲染目标至当前屏幕
                    eglMakeCurrent(mEgl, display.eglSurface, display.eglSurface, mEglContext);
                    // 计算画面缩放比例,实现居中适配屏幕
                    float scaleW = (float)display.width / display.originalWidth;
                    float scaleH = (float)display.height / display.originalHeight;
                    int animBaseX = (display.width - animation.width * scaleW) / 2;
                    int animBaseY = (display.height - animation.height * scaleH) / 2;
                    // 帧裁剪坐标缩放换算
                    int cropW = frame.cropW * scaleW;
                    int cropH = frame.cropH * scaleH;
                    int cropX = frame.cropX * scaleW;
                    int cropY = frame.cropY * scaleH;
                    int drawX = animBaseX + cropX;
                    // OpenGL Y轴倒置,修正绘制纵坐标
                    int drawY = display.height - (animBaseY + cropY + cropH);

                    // 初始化正交投影矩阵
                    projectSceneToWindow(display);
                    glClear(GL_COLOR_BUFFER_BIT);

                    // 绑定图片着色器,传入渐变、淡出透明度参数
                    glUseProgram(mImageShaderProgram);
                    glUniform1i(mTexLoc, 0);
                    glUniform1f(mFadeLoc, fadeAlpha);
                    if (animation.dynamicColoringEnabled) {
                        glUniform1f(mColorProgressLoc, colorProgress);
                    }
                    glEnable(GL_BLEND);
                    // 绘制带纹理矩形(核心图片渲染)
                    drawTexturedRect(drawX, drawY, cropW, cropH, display);
                    glDisable(GL_BLEND);

                    // 叠加绘制右上角时钟文字
                    if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
                        drawClockText(mClockFont, part.clockX, part.clockY, display);
                    }

                    // 叠加绘制底部开机进度百分比
                    if (drawProgressBar) {
                        int newProgress = android::base::GetIntProperty(PROGRESS_PROP, 0);
                        if (lastProgress != 100) {
                            usleep(100000); // 放慢进度条刷新速度,避免跳动过快
                            if (lastProgress < newProgress) lastProgress++;
                        }
                        drawProgressText(lastProgress, mProgressFont, TEXT_CENTER, animation.height / 3, display);
                    }

                    // 交换前后双缓冲,画面输出到物理屏幕
                    eglSwapBuffers(mEgl, display.eglSurface);
                }

                // 帧间隔休眠,锁定FPS,防止动画加速播放
                nsecs_t now = systemTime();
                nsecs_t sleepNs = frameIntervalNs - (now - frameBeginTime);
                if (sleepNs > 0) {
                    struct timespec sleepSpec;
                    sleepSpec.tv_sec = (now + sleepNs) / 1000000000;
                    sleepSpec.tv_nsec = (now + sleepNs) % 1000000000;
                    int ret;
                    // 绝对单调时钟休眠,被信号中断自动重试
                    do {
                        ret = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &sleepSpec, nullptr);
                    } while (ret == EINTR);
                }

                // 每帧渲染完成,检测是否收到退出指令(核心退出检测入口)
                checkExit();
            }

            // 当前分段所有帧播放完毕,执行分段暂停pause
            int pauseUs = part.pauseFrameCount * ns2us(frameIntervalNs);
            while (pauseUs > 0 && !exitPending()) {
                if (pauseUs > MAX_CHECK_INTERVAL_US) {
                    usleep(MAX_CHECK_INTERVAL_US);
                    pauseUs -= MAX_CHECK_INTERVAL_US;
                } else {
                    usleep(pauseUs);
                    break;
                }
                // 休眠中途持续检测退出信号
                checkExit();
            }

            // 收到退出信号,无限循环分段强制置进度100并跳出
            if (exitPending() && part.loopCount == 0 && mCurrentInset >= mTargetInset && !part.enableFadeOut) {
                if (lastProgress != 0 && lastProgress != 100) {
                    android::base::SetProperty(PROGRESS_PROP, "100");
                    continue;
                }
                break;
            }
        }
    }

    // 动画全部播放完成,批量释放循环帧纹理,释放GPU显存
    for (const Animation::Part& part : animation.parts) {
        if (part.loopCount != 1) {
            for (const auto& frame : part.frames) {
                glDeleteTextures(1, &frame.textureId);
            }
        }
    }

    SLOGD("%sAnimationShownTiming end time: %" PRId64 "ms",
          mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime());
    return true;
}

2.5 退出检测核心函数 checkExit ()

cpp 复制代码
void BootAnimation::checkExit() {
    ATRACE_CALL();
    char propValue[PROPERTY_VALUE_MAX];
    // 读取全局退出标记属性 service.bootanim.exit
    property_get("service.bootanim.exit", propValue, "0");
    int exitFlag = atoi(propValue);
    if (exitFlag != 0) {
        // 标记线程需要退出,下一轮循环执行淡出收尾
        requestExit();
    }
}

核心逻辑:每一帧渲染结束都会同步读取共享内存中的系统属性,一旦值为 1,标记线程退出,后续执行淡出过渡动画,不会直接强制杀进程,保证画面过渡顺滑。

三、开机动画退出完整链路:Launcher 就绪 → 设置退出属性

3.1 起点:Launcher 主线程 Idle 回调上报 activityIdle

Launcher 作为系统首个前台 Activity,完成 onResume、布局渲染后,ActivityThread 消息队列无待处理 UI 任务,触发 Idler 空闲处理器。

文件路径:frameworks/base/core/java/android/app/ActivityThread.java

java 复制代码
private class Idler implements MessageQueue.IdleHandler {
    @Override
    public final boolean queueIdle() {
        boolean stopProfiling = false;
        if (mBoundApplication != null && mProfiler.profileFd != null
                && mProfiler.autoStopProfiler) {
            stopProfiling = true;
        }
        final ActivityClient ac = ActivityClient.getInstance();
        // 批量上报所有Resume完成的页面空闲状态
        while (mNewActivities.size() > 0) {
            final ActivityClientRecord record = mNewActivities.remove(0);
            if (localLOGV) {
                Slog.v(TAG, "Report idle activity " + record
                        + " finished=" + (record.activity != null && record.activity.mFinished));
            }
            // 页面有效则跨Binder通知ATMS页面进入空闲
            if (record.activity != null && !record.activity.mFinished) {
                ac.activityIdle(record.token, record.createdConfig, stopProfiling);
                record.createdConfig = null;
            }
        }
        if (stopProfiling) {
            mProfiler.stopProfiling();
        }
        // 返回false,执行一次后销毁,不重复触发
        return false;
    }
}

触发时机:handleResumeActivity() 末尾通过 Looper.myQueue().addIdleHandler(new Idler()) 注册。

3.2 客户端 Binder 转发 ActivityClient.java

java 复制代码
public void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
    try {
        // 调用IActivityTaskManager Binder接口,转发至系统端ActivityClientController
        getActivityTaskManager().activityIdle(token, config, stopProfiling);
    } catch (RemoteException e) {
        e.rethrowFromSystemServer();
    }
}

3.3 系统端接收入口 ActivityClientController.java

路径:frameworks/base/services/core/java/com/android/server/wm/ActivityClientController.java

java 复制代码
public void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
    final long origCallingId = Binder.clearCallingIdentity();
    try {
        synchronized (mGlobalLock) {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityIdle");
            // 根据页面Token查询服务端Activity记录
            final ActivityRecord r = ActivityRecord.forTokenLocked(token);
            if (r == null) return;
            // 转发至任务栈调度核心处理空闲逻辑
            mTaskSupervisor.activityIdleInternal(r, false, false, config);
            // 按需关闭性能采样器
            if (stopProfiling && r.hasProcess()) {
                r.app.clearProfilerIfNeeded();
            }
        }
    } finally {
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        Binder.restoreCallingIdentity(origCallingId);
    }
}

3.4 开机完成判定 ActivityTaskSupervisor.java

java 复制代码
void activityIdleInternal(ActivityRecord r, boolean fromTimeout,
        boolean processPausingActivities, Configuration config) {
    // 省略页面暂停、内存回收、栈调度逻辑
    // 核心判定:系统仍在开机 且 所有前台Resume页面全部空闲
    if ((mService.isBooting() && mRootWindowContainer.allResumedActivitiesIdle())
            || fromTimeout) {
        checkFinishBootingLocked();
    }
    // 清除页面重启标记
    r.mRelaunchReason = RELAUNCH_REASON_NONE;
}

private void checkFinishBootingLocked() {
    boolean isBooting = mService.isBooting();
    boolean enableScreen = false;
    mService.setBooting(false);
    // 首次完整开机,标记开机完成
    if (!mService.isBooted()) {
        mService.setBooted(true);
        enableScreen = true;
    }
    if (isBooting || enableScreen) {
        // 投递开机收尾任务至ATMS主线程Handler
        mService.postFinishBooting(isBooting, enableScreen);
    }
}

关键判定 mRootWindowContainer.allResumedActivitiesIdle():遍历全局所有前台 Activity,必须全部完成渲染、进入空闲状态,才判定整机开机流程结束,避免动画提前消失导致黑屏。

3.5 ATMS 主线程分发任务 ActivityTaskManagerService.java

java 复制代码
void postFinishBooting(boolean finishBooting, boolean enableScreen) {
    mH.post(() -> {
        if (finishBooting) {
            // 通知AMS完成开机,发送BOOT_COMPLETED广播、调度后台进程
            mAmInternal.finishBooting();
        }
        if (enableScreen) {
            // 联动WMS执行屏幕启用、关闭开机动画
            mInternal.enableScreenAfterBoot(isBooted());
        }
    });
}

public void enableScreenAfterBoot(boolean booted) {
    writeBootProgressEnableScreen(SystemClock.uptimeMillis());
    // 调用WindowManagerService核心方法
    mWindowManager.enableScreenAfterBoot();
    synchronized (mGlobalLock) {
        updateEventDispatchingLocked(booted);
    }
}

3.6 WMS 设置退出属性 WindowManagerService.java

java 复制代码
public void enableScreenAfterBoot() {
    synchronized (mGlobalLock) {
        if (mSystemBooted) return;
        mSystemBooted = true;
        hideBootMessagesLocked();
        mPolicy.systemBooted();
        // 执行关闭开机动画核心逻辑
        performEnableScreen();
    }
}

private void performEnableScreen() {
    synchronized (mGlobalLock) {
        // 省略屏幕亮度、输入设备初始化代码
        if (!mBootAnimationStopped) {
            // 核心:写入系统属性,通知BootAnimation退出
            SystemProperties.set("service.bootanim.exit", "1");
            mBootAnimationStopped = true;
        }
    }
}

3.7 SystemProperties 底层机制:Java → JNI → 属性共享内存

Java 层接口 SystemProperties.java
java 复制代码
public static void set(@NonNull String key, @Nullable String val) {
    if (val != null && !key.startsWith("ro.") &&
            val.getBytes(StandardCharsets.UTF_8).length > PROP_VALUE_MAX) {
        throw new IllegalArgumentException("Property value too long");
    }
    if (TRACK_KEY_ACCESS) onKeyAccess(key);
    // 调用native底层方法
    native_set(key, val);
}
JNI 层实现 android_os_SystemProperties.cpp
cpp 复制代码
void SystemProperties_set(JNIEnv *env, jobject clazz, jstring keyJ, jstring valJ)
{
    ScopedUtfChars key(env, keyJ);
    if (!key.c_str()) return;
    std::optional<ScopedUtfChars> value;
    if (valJ != nullptr) {
        value.emplace(env, valJ);
        if (!value->c_str()) return;
    }
    const char* valueStr = value ? value->c_str() : "";
    errno = 0;
    // 调用bionic库底层属性设置函数
    bool success = !__system_property_set(key.c_str(), valueStr);
    if (!success) {
        if (errno != 0) {
            jniThrowExceptionFmt(env, "java/lang/RuntimeException",
                    "set prop %s=%s failed: %m", key.c_str(), valueStr);
        } else {
            jniThrowExceptionFmt(env, "java/lang/RuntimeException",
                    "set prop %s=%s failed, check logcat", key.c_str(), valueStr);
        }
    }
}
底层属性系统原理
  1. __system_property_set 是 Bionic C 库提供接口,通过 Unix 域 Socket 向 PID=1 的 init 进程发送修改请求;
  2. init 进程校验权限后,修改全局共享内存 /dev/__properties__ 内属性值;
  3. 所有进程(包含独立的 bootanimation 进程)都会 mmap 这块共享内存,property_get() 直接内存读取,无需跨 Binder,读取速度极快,适合每帧高频轮询;
  4. BootAnimation 在 checkExit() 直接读取共享内存,实时感知 service.bootanim.exit 变更。

四、四大核心问题完整解答

1. 开机动画是谁启动的?

init 进程启动 SurfaceFlinger,SF 图形服务初始化完成后,通过 ctl.start bootanim 属性触发 init 拉起 /system/bin/bootanimation 独立 Native 进程。 进程配置在 bootanim.rc,默认 disabled,无法自启动,完全依赖 SurfaceFlinger 触发。

2. 为什么 Framework 未完全启动,动画就能显示?

BootAnimation、SurfaceFlinger 均为独立 Native 进程,运行在 Linux 用户态,不依赖 system_server、AMS、WMS 等 Java 层 Framework 服务。 仅需依赖底层图形硬件、EGL、BufferQueue 即可创建图层、渲染画面,因此在 Zygote、system_server 启动前就能播放动画。

3. SurfaceFlinger 和 BootAnimation 是什么关系?

  1. 依赖关系:BootAnimation 创建 Surface 图层、EGL 窗口、分配 GraphicBuffer 全部依赖 SurfaceFlinger,无 SF 则无法输出画面;
  2. 合成关系:BootAnimation 向 SF 提交专属不透明图层 Layer,SF 按最高 Z 序合成至屏幕;
  3. 启动时序:SF 先启动并初始化硬件,再主动拉起 BootAnimation;
  4. 进程隔离:两者是独立进程,通过 Binder 完成图层管理通信。

4. BootAnimation 如何知道什么时候退出?

  1. Launcher 全部加载、主线程空闲后,通过 activityIdle 上报 ATMS;
  2. ATMS 校验所有前台页面全部空闲,判定开机完成;
  3. WMS 调用 SystemProperties.set("service.bootanim.exit", "1") 修改全局共享内存属性;
  4. BootAnimation 每帧渲染末尾调用 checkExit() 读取该属性,检测到 1 后开启淡出动画,播放完毕释放全部图形资源,进程退出。

5. 动画结束后 Launcher 如何接管屏幕?

  1. BootAnimation 进程销毁,对应的 BootAnimation 专属 Layer 从 SurfaceFlinger 移除;
  2. Launcher 的 Window 由 WMS 管理,图层 Z 序低于动画图层,动画销毁后 SF 自动合成 Launcher 窗口、状态栏、导航栏图层;
  3. 多图层硬件合成(HWC)直接输出桌面画面至显示屏,无黑屏断层。

五、完整全链路时序总表

  1. Kernel 启动 → init 进程初始化属性共享内存;
  2. init 启动 SurfaceFlinger 图形服务;
  3. SF 初始化 EGL、显示硬件完成,设置 ctl.start bootanim
  4. init 拉起 bootanimation 二进制进程;
  5. bootanimation_main 初始化 Binder 线程池,阻塞等待 SF 就绪;
  6. BootAnimation::run 创建子线程,先执行 readyToRun 初始化多屏幕、EGL、Surface;
  7. 进入 threadLoop → movie → playAnimation 逐帧循环渲染,每帧调用 checkExit;
  8. init 启动 Zygote,fork system_server,启动 AMS/WMS/ATMS;
  9. AMS 拉起 Launcher 桌面 Activity;
  10. Launcher onResume 完成,主线程 Idle 上报 activityIdle;
  11. ATMS 判定所有前台页面空闲,投递任务至 WMS;
  12. WMS 设置 service.bootanim.exit=1
  13. BootAnimation 检测到退出标记,执行淡出动画,播放完毕释放 EGL/Surface 资源;
  14. bootanimation 进程销毁,SF 移除动画图层,Launcher 窗口图层正常合成显示桌面。
相关推荐
达达尼昂1 小时前
在 Claude Cowork 中用好 Claude Fable 5
android·人工智能·后端
sTone873752 小时前
写时复制COW的第一性理解
android·c++·flutter
sTone873752 小时前
Zygote的第一性理解
android
sTone873752 小时前
类RN框架通过Service暴露卡片渲染能力给AI Chat
android·react native
灯火不休2 小时前
Android Studio & Flutter 构建命令完全指南
android
杉氧2 小时前
Ktor 全栈之路 (5):JWT 认证全流程实战 —— 打造安全通信闭环
android·架构·kotlin
️学习的小王3 小时前
Python + MySQL 学生信息管理系统实战教程
android·python·mysql
帅次3 小时前
Android 高级工程师面试:Flutter 路由导航 近1年高频追问 18 题
android·flutter·面试
alexhilton11 小时前
MVI模式的完整历史、误解和现代Android范式
android·kotlin·android jetpack