3.修改开机动画
一、源码分析
Android开机动画实现方式目前实现Androtd开机动画的方式主要是逐帧动画和opencL直接编程绘制动画。 逐帧动画 逐帧动画是一种常见的动画形式((FraneByFrane),其原理是在“连续的关键帧“中分解动画动作,也就是在时间轴的每顿上逐帧绘制不同的内容,使其连续播放而成动画。因为遂帧动画的帧序列内容不一样,不但给制作增加了负担而且最终输出的文件量也很大,但它的优势也很明显:逐帧动画具有非常大的灵活性,几乎可以表现任何想表现的内容,而它类似与电影的播放模式,很适合于表演细腻的动画。
逐帧动画是广泛流传的一种实现方法。实现原理是将一系列图片打包成bootantnatton.zip放入system/media/录,系统将图片一顿一顿循环播放形成一个动画效果。理论上讲这种方法应该是可以实现一切动画需求的,但是实践后你会发现当bootantnatton.zip大于5M的时候,动画将有明显卡顿,文件越大动画越不流畅。所以细心的同学会发现手机上的开机动画大多都是只有中间一小部分在变化,而四周全是黑色,这样做是为了使得可以采用100*50(甚至更小)分辨率的图片,这样100帧也オ几M的大小
0penGL动画 opencL(英语:openGraphtcs_Ltbrary)是个定义了一个跨编程语言、跨平台的应用程序接口(Api)的规范,它用于生成二维、三维图像。这个接口由近三百五十个不同的函数调用组成,用来从简单的图形比特绘制复杂的三维景象
1.1 源码启动开机动画流程分析
1.源码路径介绍
bootanimation frameworks/base/cmds/bootanimation
surfaceflinger frameworks/native/services/surfaceflinger
init system/core/init
解释:
bootanimation是专门用于播放开机动画的独立进程,核心功能是加载动画资源并通过 SurfaceFlinger 将动画帧渲染到屏幕。
SurfaceFlinger 是 Android 系统的 核心图形合成服务,运行在 system_server 进程中,负责管理所有应用和系统的图形渲染、图层合成,并最终将画面输出到屏幕,是连接应用绘制与屏幕显示的 “桥梁”
init是Android 系统启动后第一个运行的用户态进程(PID=1),是所有其他系统进程和服务的 “父进程”。
2.启动流程详细分析
- 内核起来后会根据init.rc配置启动surfaceflinger进程
知识点拓展:init.rc与bootanim.rc
.rc 文件是 init 进程的 “指令清单”,初始化配置文件
init.rc 是系统启动的 “总指挥”,统筹所有初始化工作;bootanim.rc 是 “专项执行者”,只负责开机动画这一个具体功能,且依赖 init.rc 才能被执行。

surfaceflinger.rc如下

这时就有一个问题点,既然说init.rc启动了这两个rc,主要的动画不是bootanimation吗?那直接启动bootanimation不就好了,注意看第一张图片,有一个disabled的配置,虽然init.rc有会启动bootanim.rc,但是并不会跑起来,会注册有信息。如果bootianimation想跑起来,还是要通过surfaceflinger 这个服务去启动
- surfaceflinger 进程便启动了,跟着就会跑进程的main() 函数
首先创建一个SurfaceFlinger实例,然后init,然后run
int main(int, char**) {
.................................
// instantiate surfaceflinger
sp<SurfaceFlinger> flinger = surfaceflinger::createSurfaceFlinger();
..................................
flinger->init();
// publish surface flinger
sp<IServiceManager> sm(defaultServiceManager());
sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,
IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);
// publish gui::ISurfaceComposer, the new AIDL interface
sp<SurfaceComposerAIDL> composerAIDL = sp<SurfaceComposerAIDL>::make(flinger);
sm->addService(String16("SurfaceFlingerAIDL"), composerAIDL, false,
IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO); // 注册到service manager里
..................................
// run surface flinger in this thread
flinger->run(); //开跑
return 0;
}
启动的流程就是这些,那么慢慢刨析
先看SurfaceFlinger.cpp,里面有一个startBootAnim()方法,会启动一个线程
void SurfaceFlinger::startBootAnim() {
// Start boot animation service by setting a property mailbox
// if property setting thread is already running, Start() will be just a NOP
mStartPropertySetThread->Start();
// Wait until property was set
if (mStartPropertySetThread->join() != NO_ERROR) { //真正启动设置bootanimation的属性线程
ALOGE("Join StartPropertySetThread failed!");
}
}
.......................
那么我们来看这个线程做了什么,通过命名可以知道哈,这个mStartPropertySetThread,是由StartPropertySetThread.cpp里面得来的
那么我们来看StartPropertySetThread.cpp
#include <cutils/properties.h>
#include "StartPropertySetThread.h"
namespace android {
StartPropertySetThread::StartPropertySetThread(bool timestampPropertyValue):
Thread(false), mTimestampPropertyValue(timestampPropertyValue) {}
//1.start起来了直接run
status_t StartPropertySetThread::Start() {
return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL);
}
//2.threadLoop相当于java 里面的run方法
bool StartPropertySetThread::threadLoop() {
// Set property service.sf.present_timestamp, consumer need check its readiness
property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");
// Clear BootAnimation exit flag
property_set("service.bootanim.exit", "0"); // 关键属性
property_set("service.bootanim.progress", "0");
// Start BootAnimation if not started
// 3.设置属性启动,控制开启
property_set("ctl.start", "bootanim"); // 关键属性
// Exit immediately
return false;
}
} // namespace android
这样bootanim进程就会启动?凭什么设置了一个属性就会启动了,那么下面我们来看 /system/core/init/init.cpp,再看init进程的init.cpp的main函数,为什么知道是这个init.cpp,也不太理解。后续可以去查阅相关资料

在这里跟课程走的话,应该是要找到一个标志为start_property_service() 方法的,但是没找到,可能是版本不一样,名字变了,技术还不到位,只能先记录,但是引入了这个.h的头文件,这个方法也只是在init里面使用,让整个系统声明。有时间去到这个方法的出处,再研究一波为啥吧
property_service.cpp内容如下
....................
void StartPropertyService(int* epoll_socket) {
................................
//1.关键,启动这个服务创建了一个Socket,用来各个进程之间进行通信
if (auto result = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
/*passcred=*/false, /*should_listen=*/false, 0666, /*uid=*/0,
/*gid=*/0, /*socketcon=*/{});
result.ok()) {
property_set_fd = *result;
} else {
LOG(FATAL) << "start_property_service socket creation failed: " << result.error();
}
//2.创建了一个fd,用来监听
listen(property_set_fd, 8);
auto new_thread = std::thread{PropertyServiceThread};
property_service_thread.swap(new_thread);
auto async_persist_writes =
android::base::GetBoolProperty("ro.property_service.async_persist_writes", false);
if (async_persist_writes) {
#ifdef MTK_LOG
SetPersistPropServThrStart(1);
#endif
persist_write_thread = std::make_unique<PersistWriteThread>();
}
}
................................
注意,我用的是MTK平台的代码,正常来说,listen(property_set_fd, 8);后面只会有一句
register_epoll_hander(property_set_fd,handle_property_set_fd);
就结束了。如果是源码,property_set_fd会跳到handle_property_set_fd,查找这个方法的类,最终还是找到了init。后面我发现不管是MTK还是源码,都是回到了init.cpp中,都是启动一个service
这里我给出MTK的代码介绍,init.cpp
................
static const std::map<std::string, ControlMessageFunction, std::less<>>& GetControlMessageMap() {
// clang-format off
static const std::map<std::string, ControlMessageFunction, std::less<>> control_message_functions = {
{"sigstop_on", [](auto* service) { service->set_sigstop(true); return Result<void>{}; }},
{"sigstop_off", [](auto* service) { service->set_sigstop(false); return Result<void>{}; }},
{"oneshot_on", [](auto* service) { service->set_oneshot(true); return Result<void>{}; }},
{"oneshot_off", [](auto* service) { service->set_oneshot(false); return Result<void>{}; }},
{"start", DoControlStart},
{"stop", DoControlStop},
{"restart", DoControlRestart},
};
// clang-format on
return control_message_functions;
}
.......................
static Result<void> DoControlStart(Service* service) {
return service->Start();
}
这里的service启动的是什么?这里的service启动的就是开头中bootanim.rc的第一行service,因为一开始启动的时候,配置为disable,这次终于启动起来了,虽然还是在init中启动,但是初始化的时候被标志位disable,只是服务被记录下来了。
问题:
为什么要在init中启动,但是一开始初始化的时候又把他停了?因为bootanition要绘制图像,这个图像要依赖surfaceflinger来绘制,要等这个surfaceflinger起来后才可以绘制
总结流程
init启动 ->
surfaceflinger进程启动 ->
surfaceflinger的init启动 ->
StartPropertySetThread线程的执行 ->
property_set 通知 init 启动bootanimation程序 ->
bootanimation的main方法执行
1.2 开机动画播放从开始到结束流程分析
1.执行bootanimation_main.cpp的main方法
从上面的启动流程,我们得知init已经把我们的bootanimation拉起来了,拉起来后,肯定执行了main方法,我们来看main方法
frameworks\base\cmds\bootanimation\bootanimation_main.cpp
int main()
{
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
//1如果在这里打LOG,没有生效,去BootAnimation.cpp 把#define LOG_NDEBUG 0 的 0改成1,默认关闭
ALOGD("Boot animation main start");
bool noBootAnimation = bootAnimationDisabled();
ALOGI_IF(noBootAnimation, "boot animation disabled");
if (!noBootAnimation) {
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
// create the boot animation object (may take up to 200ms for 2MB zip)
sp<BootAnimation> boot = new BootAnimation(audioplay::createAnimationCallbacks());
waitForSurfaceFlinger();
boot->run("BootAnimation", PRIORITY_DISPLAY);
ALOGV("Boot animation set up. Joining pool.");
IPCThreadState::self()->joinThreadPool();
}
return 0;
}
这里的noBootAnimation变量比较关键,这个变量就是开机动画有没有被禁止,如果被禁止,那就是一直处于黑屏状态了,这个属性默认都是返回 false
2.创建一个BootAnimation对象,对象被sp智能指针引用,会执行一个onFirstRef()方法
这里有一点要说明,sp 是一个智能指针,用于管理对象的生命周期(内存自动回收)。看似只New了一个BootAnimation,去到BootAnimation 对象的创建方
法中,似乎除了赋值几个变量外,啥事也没干了,但是因为这个指针,会自动回调一个名字为 onFirstRef()的方法。看 BootAnimation.cpp的源码如下
.......................
//1.New方法
BootAnimation::BootAnimation(sp<Callbacks> callbacks)
: Thread(false), mLooper(new Looper(false)), mClockEnabled(true), mTimeIsAccurate(false),
mTimeFormat12Hour(false), mTimeCheckThread(nullptr), mCallbacks(callbacks) {
mSession = new SurfaceComposerClient();
std::string powerCtl = android::base::GetProperty("sys.powerctl", "");
if (powerCtl.empty()) {
mShuttingDown = false;
} else {
mShuttingDown = true;
}
ALOGD("%sAnimationStartTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
elapsedRealtime());
}
...................
//2.回调方法
void BootAnimation::onFirstRef() {
status_t err = mSession->linkToComposerDeath(this);
SLOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
if (err == NO_ERROR) {
// Load the animation content -- this can be slow (eg 200ms)
// called before waitForSurfaceFlinger() in main() to avoid wait
ALOGD("%sAnimationPreloadTiming start time: %" PRId64 "ms",
mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime());
preloadAnimation();
ALOGD("%sAnimationPreloadStopTiming start time: %" PRId64 "ms",
mShuttingDown ? "Shutdown" : "Boot", elapsedRealtime());
}
}
比较关键的就是preloadAnimation(),作用就是进行一个预加载动画
...............................
bool BootAnimation::preloadAnimation() {
findBootAnimationFile(); //1.查找动画文件
if (!mZipFileName.isEmpty()) {
mAnimation = loadAnimation(mZipFileName); //2.加载动画
return (mAnimation != nullptr);
}
return false;
}
..................................
void BootAnimation::findBootAnimationFile() {
const bool playDarkAnim = android::base::GetIntProperty("ro.boot.theme", 0) == 1;
//定义了 3 类动画文件路径列表:
//bootFiles:开机动画路径(优先从 APEX、产品目录、OEM 目录、系统目录查找)。
//shutdownFiles:关机动画路径。
//userspaceRebootFiles:用户空间重启(如部分服务重启)时的动画路径。
static const std::vector<std::string> bootFiles = {
APEX_BOOTANIMATION_FILE, playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE,
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);
}
}
........................................
bool BootAnimation::findBootAnimationFileInternal(const std::vector<std::string> &files) {
for (const std::string& f : files) {
if (access(f.c_str(), R_OK) == 0) { // 检查文件是否存在且可读
mZipFileName = f.c_str(); // 保存找到的文件路径
return true;
}
}
return false;
}
这些就是产品的一些目录路径

最后是加载动画,并且返回加载完成的动画对象
BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) {
// 1. 检查是否已加载该文件,避免循环引用
if (mLoadedFiles.indexOf(fn) >= 0) {
SLOGE("File \"%s\" is already loaded. Cyclic ref is not allowed", fn.string());
return nullptr;
}
// 2. 打开ZIP动画文件
ZipFileRO *zip = ZipFileRO::open(fn); // 只读方式打开ZIP包
if (zip == nullptr) { // 打开失败(如文件不存在、损坏)
SLOGE("Failed to open animation zip \"%s\": %s", fn.string(), strerror(errno));
return nullptr;
}
ALOGD("%s is loaded successfully", fn.string()); // 日志:ZIP文件打开成功
// 3. 创建Animation对象并初始化基础信息
Animation *animation = new Animation;
animation->fileName = fn; // 保存文件路径
animation->zip = zip; // 关联ZIP文件指针
animation->clockFont.map = nullptr; // 初始化时钟字体(若动画包含时钟)
mLoadedFiles.add(animation->fileName); // 将文件添加到“已加载列表”,避免重复加载
// 4. 解析动画描述文件(如desc.txt)
parseAnimationDesc(*animation); // 解析ZIP包中的描述文件,获取帧率、尺寸、播放次数等参数
// 5. 预加载ZIP包中的帧资源(图片)
if (!preloadZip(*animation)) { // 预加载失败(如资源损坏)
releaseAnimation(animation); // 释放已分配的资源
return nullptr;
}
// 6. 从“已加载列表”移除(加载完成,无需再限制)
mLoadedFiles.remove(fn);
return animation; // 返回加载完成的动画对象
}
这里通过onFirstRef()方法,返回了一个开机动画包,赋值给了New,那么继续查看bootanimation_main.cpp
waitForSurfaceFlinger();
boot->run("BootAnimation", PRIORITY_DISPLAY);
ALOGV("Boot animation set up. Joining pool.");
IPCThreadState::self()->joinThreadPool();
4.启动run方法绘制
boot拿到动画包后,执行了一个run方法,但是在执行run方法前,会先执行一个名字为readyToRun()方法,让我们看看这个方法做了什么
status_t BootAnimation::readyToRun() {
........................................
//1.获取一个SurfaceControl,这就是为什么在run前面,要进行一个waitForSurfaceFlinger();等待画布加载的过程,没有画布,有动画,也没有用
sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
resolution.getWidth(), resolution.getHeight(), PIXEL_FORMAT_RGB_565,
ISurfaceComposerClient::eOpaque);
}
这个readyToRun主要就是获取一个画布,然后就可以看我们的Run方法了,为什么boot会可以实现一个run方法?这个不是线程的吗?可以去BootAnimation.h文件查看,其实他继承了线程类
bool BootAnimation::threadLoop() {
bool result;
initShaders();
// We have no bootanimation file, so we use the stock android logo
// animation.
if (mZipFileName.isEmpty()) {
ALOGD("No animation file");
result = android(); //1.走的是android的流程
} else {
result = movie();// 2.走的是movie的流程
}
mCallbacks->shutdown();
eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(mDisplay, mContext);
eglDestroySurface(mDisplay, mSurface);
mFlingerSurface.clear();
mFlingerSurfaceControl.clear();
eglTerminate(mDisplay);
eglReleaseThread();
IPCThreadState::self()->stopProcess();
return result;
}
来看android的流程
bool BootAnimation::android() {
.................
do {
processDisplayEvents();
const GLint xc = (mWidth - mAndroid[0].w) / 2;
const GLint yc = (mHeight - mAndroid[0].h) / 2;
const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);
glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
updateRect.height());
nsecs_t now = systemTime();
double time = now - startTime;
float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;
GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;
GLint x = xc - offset;
glDisable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
drawTexturedQuad(x, yc, mAndroid[1].w, mAndroid[1].h);
drawTexturedQuad(x + mAndroid[1].w, yc, mAndroid[1].w, mAndroid[1].h);
glEnable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
drawTexturedQuad(xc, yc, mAndroid[0].w, mAndroid[0].h);
EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
if (res == EGL_FALSE)
break;
// 12fps: don't animate too fast to preserve CPU
const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);
if (sleepTime > 0)
usleep(sleepTime);
checkExit(); // 检测退出
} while (!exitPending());
}
5.等待检测退出
进行一系列的动画展示,然后退出,实行checkExit()方法,继续跟进
void BootAnimation::checkExit() {
// Allow surface flinger to gracefully request shutdown
char value[PROPERTY_VALUE_MAX];
property_get(EXIT_PROP_NAME, value, "0");
int exitnow = atoi(value);
if (exitnow) {
requestExit();
}
}
这里主要获取一个属性的值,EXIT_PROP_NAME,static const char EXIT_PROP_NAME[] = "service.bootanim.exit"; 0代表不退出,1代表退出,那么这个值,是谁赋值的呢?我们只能通过grep检索看看是谁赋值的了
grep -rn "service.bootanim.exit"

一个是SurfaceFlinger.cpp一个是WindowManagerService.java,我们先来看WindowManagerService
private void performEnableScreen() {
android.util.Log.i("mxt", "Activity onCreate", new Exception());
.............................
if (!mBootAnimationStopped) {
Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
// stop boot animation
// formerly we would just kill the process, but we now ask it to exit so it
// can choose where to stop the animation.
SystemProperties.set("service.bootanim.exit", "1"); // 这里
mBootAnimationStopped = true;
}
............................
}

疑惑点问答
1.什么是idle状态
“idle 状态” 是指系统核心初始化完成、关键服务就绪,且暂时无高优先级任务执行时的空闲状态
2.为什么进入idle状态
进入 idle 状态是 Android 启动流程的 “自然过渡”,核心原因是系统完成了启动核心流程,需等待后续任务触发
3.整个详细流程
init 启动 SurfaceFlinger →
启动 system_server →
system_server 启动 AMS/WMS →
系统进入 idle → (1.)
AMS 执行postFinishBooting → (2.)
WMS 执行WMS.performEnableScreen() 设置 service.bootanim.exit=1 → (3.)
WMS 跨进程调用 SurfaceFlinger → (4.)
SurfaceFlinger 执行显示参数配置(清理动画图层、激活正常 UI 图层、调整合成策略)→
屏幕正式启用,显示桌面
4.具体的分析堆栈流程就不说了,可以尝试打,小技巧那里枚举的就是这个堆栈打法,下面给出省略步骤
// AMS 中
private void postFinishBooting() {
// 其他启动完成操作...
// 向 WMS 发送消息,触发屏幕启用
mWindowManager.enableScreenAfterBoot(); // 这是 AMS 调用 WMS 的关键接口
}
// WMS 中
public void enableScreenAfterBoot() {
// 通过 Handler 发送消息,异步执行 performEnableScreen()
mH.sendEmptyMessage(H.ENABLE_SCREEN_AFTER_BOOT);
}
WMS中
public void enableScreenAfterBoot() {
...........
performEnableScreen();
}
总结如下:进入一个idle状态,然后跨进程调用到AMS中,执行了postFinishBooting,再调用AMS中的performEnableScreen,跨进程通信调用到SurfaceFlinger,SurfaceFlinger执行pro的设置,WindowManagerService的调用流程就结束了。
最后我们来看SurfaceFlinger.cpp的设置
void SurfaceFlinger::bootFinished() {
...................
// stop boot animation
// formerly we would just kill the process, but we now ask it to exit so it
// can choose where to stop the animation.
property_set("service.bootanim.exit", "1");
...................
}
那又是谁设置为1呢,通过查找,得到 如下信息

普及:以 .aidl 结尾的文件是 Android Interface Definition Language(Android 接口定义语言) 文件,用于定义跨进程通信(IPC)的接口。
因为是跨进程通信,所以会调用SurfaceFlinger.cpp中的一个方法onTransact ,这个方法的作用就是:接收并处理来自其他进程的 Binder 调用请求,根据请求的 code(操作码)执行对应的逻辑,并通过 reply 向调用方返回结果。
我们来查看这个方法(SurfaceFlinger.cpp)
status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
uint32_t flags) {
if (const status_t error = CheckTransactCodeCredentials(code); error != OK) {
return error;
}
status_t err = BnSurfaceComposer::onTransact(code, data, reply, flags); // 调用bootFinish
.......................
}
这里又有一个onTransact,找到这个onTransact,可以发现是在ISurfaceComposer.cpp中的【grep找到的】,这有点迷糊了,为什么能bootFinish这么调用呢?bootFinish不是SurfaceFlinger的吗?查看SurfaceFlinger.h,SurfaceFlinger是ISurfaceComposer的子类,原来如此


/ 以下的所有信息,都是原生的,有MTK有点难溯源,后面可以从这仿照着下面的过程继续查询**/**
那我们现在知道了是调用到了ISurfaceComposer.cpp,那么疑问来了,BOOT.FINISHED是谁传递过来的呢

通过grep,可以查到,是一个IBinder传过来的一个FIRST_CALL_TRANSACTION,那么这个IBinder又是谁呢?继续grep

grep结果如下

竟然是WindowManagerService。查看代码

WindowManagerService 发送了两次 service.bootanim.exit = 1,代码源码是android 8 的【教学视频】,现在我的环境是Android14 + MTK平台,在android 8 的时候发送两次,可能第二次发送的时候,还有其他的功能,不单单是设置为1,在14,把功能都分割了,不重复发送。教学博主也说不太清楚为什么要这么做,可能的原因就是还有其他功能吧。
好了,这就是差不多整个流程,从启动到关闭,总结一下流程图

1.3 总结
源码启动流程
init启动 ->
surfaceflinger进程启动 ->
surfaceflinger的init启动 ->
StartPropertySetThread线程的执行 ->
property_set 通知 init 启动bootanimation程序 ->
bootanimation的main方法执行
开机动画播放流程
bootanimation执行main方法,创建一个Bootanimation对象,由于这个对象创建被sp智能指针所引用,在第一次创建对象时,sp 的构造函数会检测到这是 第一次持有该对象(引用计数从 0 变为 1),因此会主动调用对象的 onFirstRef() 方法。在onFirstRef方法中会对动画进行一系列的预加载,最终返回一个通过opengi绘制完成的动画对象赋值给boot对象,然后等待SurfaceFlinger的启动与初始化。最后一步是执行boot的run方法,但在执行run方法之前,还会先启动一个readyrun方法,获取SurfaceFlinger的control,目的是获取到SurfaceFlinger这个画布,获取完成后才执行run方法,进行开机动画的展示。绘制完成后检测退出。
启动 bootanimation(执行 main) ->
创建 BootAnimation 对象(sp 指针) ->
触发 onFirstRef () 方法 ->
动画资源加载(查找文件→预加载→解析配置) ->
获取加载完成的 boot 对象 ->
启动 SurfaceFlinger 服务 ->
执行 readyrun ()(获取 SurfaceFlinger 画布) ->
执行 run () 方法(逐帧绘制开机动画) ->
检测动画完成
开机动画检测退出流程
进入一个idle状态,然后跨进程调用到AMS中,执行了postFinishBooting,再调用AMS中的performEnableScreen,跨进程通信调用到SurfaceFlinger,SurfaceFlinger执行property_set的设置,将service.bootanim.exit = 1,动画退出
进入 idle 状态 ->
跨进程调用 AMS 的 postFinishBooting () ->
AMS 执行 performEnableScreen () ->
跨进程通信调用 SurfaceFlinger ->
SurfaceFlinger 执行 property_set 相关设置
PS:有趣的部分
MTK的 bootanimation 文件进行了覆盖原生的
在bootanimation_main.cpp 中,绘制开机动画的流程有所改变,但是基本的情况都差不多还是会创建对象,还是加入线程执行run方法

二、开机动画opengl绘制分析
已绘制android字样的源码为例
bool BootAnimation::android() {
//1.激活OpenGL的0号纹理通道(后续绘制图片需绑定纹理到该通道)
glActiveTexture(GL_TEXTURE0);
SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
elapsedRealtime());
/**android图片的存放地方frameworks\base\core\res\assets\images
一张是android字样图,一张是一个两边白中间黑的图片,仔细看开机的时候会有一闪一闪的感觉是吧,其实是两张图片叠加到一起,让android字样从左边移动到右边,
android的字体是空心的,背景什么颜色它就是什么颜色**/
// 初始化两张核心纹理:mask是空心Android字体蒙版,shine是闪烁光效图(从资源目录加载)
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
mCallbacks->init({});
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glUseProgram(mImageShader);
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(mDisplay, mSurface);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
const nsecs_t startTime = systemTime();
do {
processDisplayEvents();
const GLint xc = (mWidth - mAndroid[0].w) / 2;
const GLint yc = (mHeight - mAndroid[0].h) / 2;
const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);
glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
updateRect.height());
nsecs_t now = systemTime();
double time = now - startTime;
float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;
GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;
GLint x = xc - offset;
glDisable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
//2.绑定纹理
glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
drawTexturedQuad(x, yc, mAndroid[1].w, mAndroid[1].h);
drawTexturedQuad(x + mAndroid[1].w, yc, mAndroid[1].w, mAndroid[1].h);
//3.对参数的设定(叠加光效,形成彩色空心字体)
glEnable(GL_BLEND);
//4.如果环境切换【gl已经切换过其他纹理】,重新绑定
glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
drawTexturedQuad(xc, yc, mAndroid[0].w, mAndroid[0].h);
EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
if (res == EGL_FALSE)
break;
//5.以12fps绘制动画
const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);
if (sleepTime > 0)
usleep(sleepTime);
checkExit();
} while (!exitPending());
//6.动画结束,清理OpenGL纹理资源(释放mask和shine图的纹理内存)
glDeleteTextures(1, &mAndroid[0].name);
glDeleteTextures(1, &mAndroid[1].name);
return false;
}
激活OpenGL的0号纹理通道 ->
glBindTexture绑定纹理 ->
对参数的设定(叠加光效,形成彩色空心字体) ->
如果环境切换,重新绑定 ->
以12fps绘制动画 ->
动画结束,清理OpenGL纹理资源 ->
三、开机动画zip形式绘制分析及实战
如果有zip动画的话,走的是movie(),而不是android()模式了
zip包的资源
- part 图片资源
- desc.txt 描述
为什么指定这两个内容呢?能不能随便命名啊?
在路径 frameworks\base\cmds\bootanimation 下,有一个 FORMAT.md 文件,这里就介绍了为什么要这么做
// 1. 这里介绍了part部分,可以有很多个part组成,part里面装着动画资源图片
## zipfile layout
The `bootanimation.zip` archive file includes:
desc.txt - a text file
part0 \
part1 \ directories full of PNG frames
... /
partN /
//2.这里讲诉desc.txt内容
## desc.txt format
// 第一行内容:宽 高 帧率
The first line defines the general parameters of the animation:
WIDTH HEIGHT FPS
* **WIDTH:** animation width (pixels)
* **HEIGHT:** animation height (pixels)
* **FPS:** frames per second, e.g. 60
// 第二行内容:
It is followed by a number of rows of the form:
TYPE COUNT PAUSE PATH [#RGBHEX CLOCK]
* **TYPE:** a single char indicating what type of animation segment this is:
+ `p` -- this part will play unless interrupted by the end of the boot 除非在启动结束时中断,否则这部分将播放
+ `c` -- this part will play to completion, no matter what 无论如何,都不会被打断
* **COUNT:** how many times to play the animation, or 0 to loop forever until boot is complete 播放动画多少次,0永远循环直到启动完成
* **PAUSE:** number of FRAMES to delay after this part ends 此部分结束后要延迟的帧数
* **PATH:** directory in which to find the frames for this part (e.g. `part0`) 用于查找此部件的帧的目录
后面的两个个参数可以不要(一般不会要),但是可以了解一下
* **RGBHEX:** _(OPTIONAL)_ a background color, specified as `#RRGGBB` 背景颜色,指定为“#RRGGBB”
* **CLOCK:** _(OPTIONAL)_ the y-coordinate at which to draw the current time (for watches) 绘制当前时间的 y 坐标
组成情况如下
WIDTH HEIGHT FPS
TYPE COUNT PAUSE PATH [#RGBHEX CLOCK]
转换为看得懂的
1200 540 60
p 1 0 part0
p 0 0 part1
//3.将文件夹放置的位置/system/media/bootanimation.zip
There is also a special TYPE, `$SYSTEM`, that loads `/system/media/bootanimation.zip`
and plays that.
MTK平台的话,不做客制化,放在./device/mediatek/system/mssi_64_cn/bootanimation.zip这个路径下,大差不差。
上面就是整个zip的内容,将zip放到指定位置,那么整个动画包就完成了,下面来梳理一下源码的流程
因为运行的是movie(),来看movie()方法
movie(){
if (mAnimation == nullptr) {
// 解析动画包
mAnimation = loadAnimation(mZipFileName);
...........................
// 播放动画
playAnimation(*mAnimation);
..........................
}
}
首先通过loadAnimation,解析这个动画包
BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) {
.................
parseAnimationDesc(*animation);
...............
return animation;
}
解析完成后要加载这个动画资源包,调用parseAnimationDesc加载
bool BootAnimation::parseAnimationDesc(Animation& animation) {
String8 desString;
// 这就是为什么要命名为desc.txt的原因
if (!readFile(animation.zip, "desc.txt", desString)) {
return false;
}
.......................................这里有一个else if,要 >=4 必须要有四个参数,所有前四个必须有,后面两个可有可无
} else if (sscanf(l, "%c %d %d %" STRTO(ANIM_PATH_MAX) "s%n",
&pathType, &count, &pause, path, &nextReadPos) >= 4) {
if (pathType == 'f') {
sscanf(l + nextReadPos, " %d #%6s %16s %16s", &framesToFadeCount, color, clockPos1,
clockPos2);
} else {
sscanf(l + nextReadPos, " #%6s %16s %16s", color, clockPos1, clockPos2);
}
// SLOGD("> type=%c, count=%d, pause=%d, path=%s, framesToFadeCount=%d, color=%s, "
// "clockPos1=%s, clockPos2=%s",
// pathType, count, pause, path, framesToFadeCount, color, clockPos1, clockPos2);
Animation::Part part;
if (path == dynamicColoringPartName) {
// Part is specified to use dynamic coloring.
part.useDynamicColoring = true;
part.postDynamicColoring = false;
postDynamicColoring = true;
} else {
// Part does not use dynamic coloring.
part.useDynamicColoring = false;
part.postDynamicColoring = postDynamicColoring;
}
part.playUntilComplete = pathType == 'c';
part.framesToFadeCount = framesToFadeCount;
part.count = count;
part.pause = pause;
part.path = path;
part.audioData = nullptr;
part.animation = nullptr;
if (!parseColor(color, part.backgroundColor)) {
SLOGE("> invalid color '#%s'", color);
part.backgroundColor[0] = 0.0f;
part.backgroundColor[1] = 0.0f;
part.backgroundColor[2] = 0.0f;
}
parsePosition(clockPos1, clockPos2, &part.clockPosX, &part.clockPosY);
animation.parts.add(part);
}
......................................
return true;
}
加载完成后,到了播放,回到movi(),然后看playAnimation()这个方法
bool BootAnimation::playAnimation(const Animation& animation) {
//1.这里获取有几个part
const size_t pcount = animation.parts.size();
nsecs_t frameDuration = s2ns(1) / animation.fps;
}
....................
//2.然后是绘制过程,绘制的过程也是通过openGL绘制,绘制图片的方法在上面已经介绍了
....................
}
最后修改一个Android.mk,加上cp操作,将bootanimation.zip放在out目录下

开机动画整个过程就播放完成了
整个流程如下

要注意的点就是,存储方式必须是store(可存储的方式),不然开机动画跑不起来
四、PS
如果想更换开机动画包,已经编译过系统,不想删除整个环境,执行如下操作【在out的上一级目录】
find out/ -name "KERNEL_OBJ" | xargs rm -rf
find out/ -name "LK_OBJ" | xargs rm -rf
find out/ -name "media" | xargs rm -rf
如果不止这个一个out目录,可以换名为out_sys,也要重复上面的操作,记得改out目录,目的是把上一套的开机资源删除干净
五、拓展:不是zip包,以视频形式
功能实现是基于AOSP 10.0.0_r1版本。
问题: 最开始想根据自定义开机动画的方式,将视频文件放到/system/media/目录下,同时添加Android.mk文件,文件中将 视频文件复制到product的out目录下的/system/media/下
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CFLAGS += ${bootanimation_CommonCFlags}
$(shell cp $(LOCAL_PATH)/bootvideo.mp4
$(ANDROID_PRODUCT_OUT)/system/media/bootvideo.mp4)
...
同时也对selinux文件进行配置相关权限,视频一直播放不了,提示找不到bootvideo.mp4文件,但是这个文件在输出目录下存在,找不到原因。后台也一直报这个日志:
/dev/binder
于是我们采用下面的方案,自定义自己的product。
添加自己的product
这块查阅了不少相关资料。亲自动手实操下。AOSP 中 Product 配置文件保存在两个目录:
- build/target:aosp 提供的 product 配置文件保存在这个目录下,默认选择的 aosp_x86_64-eng Product 就配置在这个目录下。
- device:芯片及方案厂商提供的 product 配置文件。
lunch的时候,我们默认选择的 aosp_x86_64-eng,我们主要关注它对应的几个文件:
- /board/generic_x86_64/BoardConfig.mk : 用于硬件相关配置
- /product/AndroidProducts.mk /product/aosp_x86_64.mk:用于配置 Product
大家可以用AI了解下这方面的知识。于是我们开始创建自己的Product。
我们在aosp源码的device目录下创建自己的设备文件夹,结构如下:
device
└── MyProduct/
└── MyDevice
├── AndroidProducts.mk
├── BoardConfig.mk
└── MyDevice.mk
其中BoardConfig.mk文件可参考系统的build/target/board/generic_x86_64/BoardConfig.mk文件,内容如下:
# x86_64 emulator specific definitions
TARGET_CPU_ABI := x86_64
TARGET_ARCH := x86_64
TARGET_ARCH_VARIANT := x86_64
TARGET_2ND_CPU_ABI := x86
TARGET_2ND_ARCH := x86
TARGET_2ND_ARCH_VARIANT := x86_64
TARGET_PRELINK_MODULE := false
include build/make/target/board/BoardConfigGsiCommon.mk
include build/make/target/board/BoardConfigEmuCommon.mk
BOARD_USERDATAIMAGE_PARTITION_SIZE := 576716800
BOARD_SEPOLICY_DIRS += device/generic/goldfish/sepolicy/x86
# Wifi.
BOARD_WLAN_DEVICE := emulator
BOARD_HOSTAPD_DRIVER := NL80211
BOARD_WPA_SUPPLICANT_DRIVER := NL80211
BOARD_HOSTAPD_PRIVATE_LIB := lib_driver_cmd_simulated
BOARD_WPA_SUPPLICANT_PRIVATE_LIB := lib_driver_cmd_simulated
WPA_SUPPLICANT_VERSION := VER_0_8_X
WIFI_DRIVER_FW_PATH_PARAM := "/dev/null"
WIFI_DRIVER_FW_PATH_STA := "/dev/null"
WIFI_DRIVER_FW_PATH_AP := "/dev/null"
BoardConfig.mk 包含了硬件芯片架构配置,分区大小配置等信息,所以直接使用 aosp_x86_64 的 BoardConfig.mk 就行。
MyDevice.mk可以参考 build/target/product/aosp_x86_64.mk,内容如下:
PRODUCT_USE_DYNAMIC_PARTITIONS := true
# The system image of aosp_x86_64-userdebug is a GSI for the devices with:
# - x86 64 bits user space
# - 64 bits binder interface
# - system-as-root
# - VNDK enforcement
# - compatible property override enabled
# This is a build configuration for a full-featured build of the
# Open-Source part of the tree. It's geared toward a US-centric
# build quite specifically for the emulator, and might not be
# entirely appropriate to inherit from for on-device configurations.
# GSI for system/product
$(call inherit-product, $(SRC_TARGET_DIR)/product/core_64_bit.mk)
$(call inherit-product, $(SRC_TARGET_DIR)/product/gsi_common.mk)
# Emulator for vendor
$(call inherit-product-if-exists, device/generic/goldfish/x86_64-vendor.mk)
$(call inherit-product, $(SRC_TARGET_DIR)/product/emulator_vendor.mk)
$(call inherit-product, $(SRC_TARGET_DIR)/board/generic_x86_64/device.mk)
# Enable mainline checking for excat this product name
#ifeq (aosp_x86_64,$(TARGET_PRODUCT))
PRODUCT_ENFORCE_ARTIFACT_PATH_REQUIREMENTS := relaxed
#endif
PRODUCT_ARTIFACT_PATH_REQUIREMENT_WHITELIST += \
root/init.zygote32_64.rc \
root/init.zygote64_32.rc \
# Copy different zygote settings for vendor.img to select by setting property
# ro.zygote=zygote64_32 or ro.zygote=zygote32_64:
# 1. 64-bit primary, 32-bit secondary OR
# 2. 32-bit primary, 64-bit secondary
# init.zygote64_32.rc is in the core_64_bit.mk below
PRODUCT_COPY_FILES += \
system/core/rootdir/init.zygote32_64.rc:root/init.zygote32_64.rc
# Overrides
PRODUCT_BRAND := MyProduct
PRODUCT_NAME := MyDevice
PRODUCT_DEVICE := MyDevice
PRODUCT_MODEL := Android SDK built for x86_64 MyDevice
AndroidProducts.mk 内容如下:
PRODUCT_MAKEFILES := \
$(LOCAL_DIR)/MyDevice.mk
COMMON_LUNCH_CHOICES := \
MyDevice-eng \
MyDevice-userdebug \
MyDevice-user
放置视频文件
将要播放的视频文件放置到自定义的product目录下:
MyProduct/
└── MyDevice
├── AndroidProducts.mk
├── BoardConfig.mk
└── MyDevice.mk
└── bootvideo.mp4 // 要播放的视频文件
在MyDevice.mk文件中增加代码,将视频文件复制到/system/media下
# 将视频文件copy到system/media下
PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/bootvideo.mp4:/system/media/bootvideo.mp4
PRODUCT_ARTIFACT_PATH_REQUIREMENT_WHITELIST +=\
/system/media/bootvideo.mp4
更改源码
在BootAnimation.h文件中增加播放视频的函数
status_t initTexture(Texture* texture, AssetManager& asset, const char* name);
status_t initTexture(FileMap* map, int* width, int* height);
status_t initFont(Font* font, const char* fallback);
bool android();
bool movie();
bool playVideo(); // 新增
void drawText(const char* str, const Font& font, bool bold, int* x, int* y);
void drawClock(const Font& font, const int xPos, const int yPos);
bool validClock(const Animation::Part& part);
在BootAnimation.cpp文件中增加此方法的实现
const char *bootVideoPath = "/system/media/bootvideo.mp4";
bool BootAnimation::playVideo() {
ALOGD("bootVideo play start");
eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroySurface(mDisplay, mSurface);
Int fd = open(bootVideoPath, O_RDONLY);
sp<MediaPlayer> mp = new MediaPlayer();
mp->reset();
mp->setDataSource(fd, 0,0x7ffffffffffffffLL); // 长度覆盖文件的全部内容
mp->setVideoSurfaceTexture(mFlingerSurface->getIGraphicBufferProducer());
mp->prepare();
mp->start(); // 开始播放
mp->setVolume(1.0, 1.0);
while (true) {
if (exitPending()) {
break;
}
usleep(100);
checkExit(); // 检查退出
}
ALOGD("bootVideo play finish");
mp->stop();
mp->release();
return true;
}
上诉文件有问题 Int fd = open(bootVideoPath, O_RDONLY); 用int
同时增加调用视频播放方法(此方法已经被声明)
bool BootAnimation::threadLoop()
{
static int64_t startTime = 0;
if (startTime == 0) {
startTime = systemTime(SYSTEM_TIME_MONOTONIC);
ALOGI("BOOT_ANIMATION_START: %lld", (long long)startTime);
}
bool r;
// We have no bootanimation file, so we use the stock android logo
// animation.
//if (mZipFileName.isEmpty()) {
// r = android();
//} else {
// r = movie();
//}
r = video();
eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(mDisplay, mContext);
eglDestroySurface(mDisplay, mSurface);
mFlingerSurface.clear();
mFlingerSurfaceControl.clear();
eglTerminate(mDisplay);
eglReleaseThread();
IPCThreadState::self()->stopProcess();
return r;
}
更改selinux权限配置文件
1.修改system/sepolicy/prebuilts/api/29.0/public/ 目录下的 bootanim.te 文件:
# 增加对 mediaserver 的访问权限
binder_call(bootanim, mediaserver)
binder_call(bootanim, audioserver)
2.修改system/sepolicy/prebuilts/api/29.0/public/ 目录下的 mediaserver.te 文件:
# 让mediaserver 可以通过 binder 访问 bootanim
binder_call(mediaserver, bootanim)
binder_service(mediaserver)
3.修改system/sepolicy/public 目录下的 bootanim.te文件:
# 增加对mediaserver 的访问权限
binder_call(bootanim, mediaserver)
# 使 bootanim 可以从 service_manager 获取到 mediaserver 服务
allow bootanim mediaserver_service:service_manager find;
4.修改system/sepolicy/public 目录下的mediaserver.te文件:
# mediaserver 可以通过 binder 访问 bootanim
binder_call(mediaserver, bootanim)
binder_service(mediaserver)
最后编译自己的product,看效果
source build/envsetup.sh
lunch MyDevice-eng
make
emulator
当前视频是横版视频,所以会有拉伸,要想效果好,可以准备模拟器屏幕宽高比例的视频。
耗时对比
通过在bool BootAnimation::threadLoop()方法开始处记录开始时间
static int64_t startTime = 0;
if (startTime == 0) {
startTime = systemTime(SYSTEM_TIME_MONOTONIC);
ALOGI("CAREY_BOOT_ANIMATION_START: %lld", (long long)startTime);
}
在bool BootAnimation::checkExit()检查退出方法中打印时间戳
int64_t endTime = systemTime(SYSTEM_TIME_MONOTONIC);
ALOGI("CAREY_BOOT_ANIMATION_END: lld%" , endTime);
通过命令过滤日志
adb logcat | grep "CAREY_BOOT_ANIMATION"
经分析,默认开机动画耗时大概4-6s左右;开机视频耗时大概5-7s左右。当然和设备的性能多方面因素都有关系。
默认动画是逐帧png序列,解码速度快,GPU负载可控;
视频播放解码会稍微耗时,高分辨率视频会增加GPU的负载。
、