上一篇我们完整梳理了 Launcher3 桌面启动全流程,理解系统如何拉起桌面应用并渲染 UI。 但有一个直观问题容易被忽略:从按下电源键到 Launcher 完整显示,中间长达数秒的时间屏幕并非黑屏,而是持续播放厂商定制开机动画,完整链路为: Google Logo 静态帧 → BootAnimation 逐帧动画 → Launcher 桌面
本篇围绕 4 个核心问题,结合完整 Native+Java 层源码拆解全链路:
开机动画进程由谁启动? 为何 Framework 服务未完全就绪时,动画就能正常渲染输出? SurfaceFlinger 与 BootAnimation 的依赖、协作关系是什么? BootAnimation 如何精准感知退出时机,动画结束后 Launcher 如何接管屏幕?
完整系统启动骨架链路: init 进程 → SurfaceFlinger → BootAnimation → WindowManagerService → Launcher
一、BootAnimation 进程启动源头:init 配置与启动规则
1.1 启动配置文件 bootanim.rc
路径:frameworks/base/cmds/bootanimation/bootanim.rc
service bootanim /system/bin/bootanimationclass core animationuser graphicsgroup graphics audiodisabledoneshotioprio rt 0task_profiles MaxPerformance
关键配置释义:
disabled:默认不会由 init 自动拉起,必须由 SurfaceFlinger 初始化完成后通过属性 ctl.start bootanim主动触发启动;class core animation:归属核心动画进程组,系统关机时统一回收; user/group graphics:赋予图形、音频硬件访问权限,保证 OpenGL 渲染、动画音效可用; oneshot:进程执行完成后自动销毁,不会反复重启; 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
int main(){// 提升进程调度优先级为显示级,保障动画流畅setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);// 读取系统属性,判断是否禁用开机动画bool noBootAnimation = bootAnimationDisabled();ALOGI_IF(noBootAnimation, "boot animation disabled");if(!noBootAnimation) {// 初始化Binder线程池,后续与SF、WMS通信依赖Bindersp<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 ()
boolbootAnimationDisabled() {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 执行固定两步逻辑:
优先执行 readyToRun():一次性初始化 EGL、多屏幕 Surface、着色器、渲染环境;初始化无异常才进入 do-while循环,反复执行threadLoop()播放动画。
完整时序图
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
status_t BootAnimation::readyToRun() {ATRACE_CALL();// 注册系统资源加载路径,用于读取zip内图片、字体mAssets.addDefaultAssets();// 核心:初始化屏幕、EGL渲染上下文、Surface图层return initDisplaysAndSurfaces();}
核心初始化 initDisplaysAndSurfaces ()
status_tBootAnimation::initDisplaysAndSurfaces(){// 1. 通过SurfaceComposerClient获取本机所有物理屏幕IDstd::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 ():动画主循环入口
bool BootAnimation::threadLoop() {ATRACE_CALL();bool result;// 预编译图片渲染着色器initShaders();// 判断是否存在bootanimation.zip资源if(mZipFileName.empty()) {ALOGD("No animation zip file, render default Android logo");// 无自定义动画,渲染原生静态Android文字Logoresult = 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 动画资源,配置渲染参数
boolBootAnimation::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((constchar*)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 ():逐帧渲染核心逻辑(最关键循环)
boolBootAnimation::playAnimation(const Animation& animation){ATRACE_CALL();const size_t totalParts = animation.parts.size();// 计算单帧标准间隔(纳秒),锁定动画FPSnsecs_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定义的所有动画分段Partfor (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();}// 当前分段所有帧播放完毕,执行分段暂停pauseint 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 ()
voidBootAnimation::checkExit(){ATRACE_CALL();char propValue[PROPERTY_VALUE_MAX];// 读取全局退出标记属性 service.bootanim.exitproperty_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
private class Idler implements MessageQueue.IdleHandler {@Overridepublic 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
public void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {try {// 调用IActivityTaskManager Binder接口,转发至系统端ActivityClientControllergetActivityTaskManager().activityIdle(token, config, stopProfiling);} catch (RemoteException e) {e.rethrowFromSystemServer();}}
3.3 系统端接收入口 ActivityClientController.java
路径:frameworks/base/services/core/java/com/android/server/wm/ActivityClientController.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
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主线程HandlermService.postFinishBooting(isBooting, enableScreen);}}
关键判定 mRootWindowContainer.allResumedActivitiesIdle():遍历全局所有前台 Activity,必须全部完成渲染、进入空闲状态,才判定整机开机流程结束,避免动画提前消失导致黑屏。
3.5 ATMS 主线程分发任务 ActivityTaskManagerService.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
publicvoidenableScreenAfterBoot() {synchronized (mGlobalLock) {if (mSystemBooted) return;mSystemBooted = true;hideBootMessagesLocked();mPolicy.systemBooted();// 执行关闭开机动画核心逻辑performEnableScreen();}}privatevoidperformEnableScreen() {synchronized (mGlobalLock) {// 省略屏幕亮度、输入设备初始化代码if (!mBootAnimationStopped) {// 核心:写入系统属性,通知BootAnimation退出SystemProperties.set("service.bootanim.exit", "1");mBootAnimationStopped = true;}}}
3.7 SystemProperties 底层机制:Java → JNI → 属性共享内存
Java 层接口 SystemProperties.java
public static void set(@NonNullString key, @NullableString 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
voidSystemProperties_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);}}}
底层属性系统原理
__system_property_set是 Bionic C 库提供接口,通过 Unix 域 Socket 向 PID=1 的 init 进程发送修改请求; init 进程校验权限后,修改全局共享内存 /dev/__properties__内属性值;所有进程(包含独立的 bootanimation 进程)都会 mmap 这块共享内存, property_get()直接内存读取,无需跨 Binder,读取速度极快,适合每帧高频轮询;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 是什么关系?
- 依赖关系
:BootAnimation 创建 Surface 图层、EGL 窗口、分配 GraphicBuffer 全部依赖 SurfaceFlinger,无 SF 则无法输出画面; - 合成关系
:BootAnimation 向 SF 提交专属不透明图层 Layer,SF 按最高 Z 序合成至屏幕; - 启动时序
:SF 先启动并初始化硬件,再主动拉起 BootAnimation; - 进程隔离
:两者是独立进程,通过 Binder 完成图层管理通信。
4. BootAnimation 如何知道什么时候退出?
Launcher 全部加载、主线程空闲后,通过 activityIdle上报 ATMS;ATMS 校验所有前台页面全部空闲,判定开机完成; WMS 调用 SystemProperties.set("service.bootanim.exit", "1")修改全局共享内存属性;BootAnimation 每帧渲染末尾调用 checkExit()读取该属性,检测到 1 后开启淡出动画,播放完毕释放全部图形资源,进程退出。
5. 动画结束后 Launcher 如何接管屏幕?
BootAnimation 进程销毁,对应的 BootAnimation 专属 Layer 从 SurfaceFlinger 移除; Launcher 的 Window 由 WMS 管理,图层 Z 序低于动画图层,动画销毁后 SF 自动合成 Launcher 窗口、状态栏、导航栏图层; 多图层硬件合成(HWC)直接输出桌面画面至显示屏,无黑屏断层。
五、完整全链路时序总表
Kernel 启动 → init 进程初始化属性共享内存; init 启动 SurfaceFlinger 图形服务; SF 初始化 EGL、显示硬件完成,设置 ctl.start bootanim;init 拉起 bootanimation 二进制进程; bootanimation_main 初始化 Binder 线程池,阻塞等待 SF 就绪; BootAnimation::run 创建子线程,先执行 readyToRun 初始化多屏幕、EGL、Surface; 进入 threadLoop → movie → playAnimation 逐帧循环渲染,每帧调用 checkExit; init 启动 Zygote,fork system_server,启动 AMS/WMS/ATMS; AMS 拉起 Launcher 桌面 Activity; Launcher onResume 完成,主线程 Idle 上报 activityIdle; ATMS 判定所有前台页面空闲,投递任务至 WMS; WMS 设置 service.bootanim.exit=1;BootAnimation 检测到退出标记,执行淡出动画,播放完毕释放 EGL/Surface 资源; bootanimation 进程销毁,SF 移除动画图层,Launcher 窗口图层正常合成显示桌面。
夜雨聆风