(学习课程为千里马framework的教程,整理成笔记自用)
在bootanimation的启动中,整理到surfaceFlinger启动了bootanimation.
frameworks/base/cmds/bootanimation/bootanimation_main.cpp
int main(int argc, char** argv)
{
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
// create the boot animation object
sp<BootAnimation> boot = new BootAnimation();//创建BootAnimation实例
//加入Binder通信
IPCThreadState::self()->joinThreadPool();//binder线程池,与surfaceflinger通信用的。
}
return 0;
}
接下来在bootanimation_main.cpp文件中,开始理解bootanimation
bootanimation_main.cpp是入口点,main函数,负责初始化启动动画。
BootAnimation.cpp 则实现了BootAnimation类的核心逻辑,包括动图的加载、渲染和退出机制。
BootAnimationUtil.cpp提供了一些实用函数,比如检查是否禁用动画和等待SurfaceFlinger服务就绪。
bootanimation_main.cpp中的main方法:
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();//启动binder线程池方便跨进程通信
waitForSurfaceFlinger();//等待SurfaceFlinger启动
// create the boot animation object
sp<BootAnimation> boot = new BootAnimation(new AudioAnimationCallbacks());//新建一个BootAnimation对象,它实际是一个线程对象
ALOGV("Boot animation set up. Joining pool.");
IPCThreadState::self()->joinThreadPool();
}
ALOGV("Boot animation exit");
return 0;
}
接下来分段讲解:(解释在上,代码在下)
·设置线程属性优先级。
·这里调用类setpriority函数来调整当前进程的优先级。
·PRIO_PROCESS表示调整的是进程优先级 ; 0 表示当前进程 ;ANDROID_PRIORITY_DISPLAY与显示相关优先级。
·原理:通过Linux的setpriority调整进程调度策略,优先处理显示任务。
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
检查开机动画有没有被禁止,如果该函数返回true,则跳过动画启动
bool noBootAnimation = bootAnimationDisabled();
ALOGI_IF(noBootAnimation, "boot animation disabled");
ProcessState
创建ProcessState实例:
·ProcessState是Android Binder机制的一部分,负责管理进程的Binder通信。
作用:
·ProcessState::self() 获取单例实例,并启动线程池,初始化Binder通信,获取进程的Binderbool noBootAnimation = bootAnimationDisabled();
ALOGI_IF(noBootAnimation, "boot animation disabled");
·startThreadPool() 启动Binder线程池,使进程能处理跨进程调用。(如 SurfaceFlinger交互)
必要性:
BootAnimatio需要通过Binder与SurfaceFlinger、AudioFlinger等服务提交。
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
等待SurfaceFlinger就绪。
实现逻辑在BootAnimationUtil.cpp中
---> 循环查询SurfaceFlinger服务是否注册到ServiceManager,直到服务就绪
---> 避免动画在SurfaceFlinger未初始化时尝试渲染
waitForSurfaceFlinger();
create the bootanimation object 创建引导动画对象
创建BootAnimation对象
构造BootAnimation对象,传入 AudioAnimationCallbacks处理音频播放
sp<BootAnimation> boot = new BootAnimation(new AudioAnimationCallbacks());
ALOGV("Boot animation set up. Joining pool.");
加入Binder线程池
作用:主线程加入Binder线程池,处理跨进程请求(如属性变更通知)
IPCThreadState::self()->joinThreadPool();
这里创建了一个BootAnimation对象,下面进入到BootAnimation.cpp文件中,继续跟踪流程。
从上述代码得知,创建了BootAnimation对象,调用BootAnimation.cpp文件中的BootAnimation方法。所以进入到BootAnimation.cpp文件中,继续跟踪流程。下面对BootAnimation.cpp中涉及的代码进行分段分析。
BootAnimation.cpp文件中,对象初始化与线程启动
framework/base/cmds/bootanimation/BootAnimation.
关键成员:
---> mSession:SurfaceComposerClient对象,用于与SurfaceFlinger交互
---> mCallbacks:处理音频播放的回调接口
sp<BootAnimation>的作用:智能指针管理对象生命周期,首次引用触发onFirstRef()
通信机制:
SurfaceComposerClient:通过 Binder IPC 与 SurfaceFlinger通信。
初始化流程:构造时未启动线程,需等待首次引发触动 onFirstRef() 。
BootAnimation::BootAnimation(sp<Callbacks> callbacks)
::Thread(false), mClockEnabled(ture),mTimeIsAccurate(false),
mTimeFormate12Hour(false),mTimeCheckThread(NULL),mCallbacks(callbacks){
mSession = new SurfaceComposerClient();
...
}
下一步进入 onFirstRef() , onFirstRef() 的触发与线程启动
onFirstRef() 的触发与线程启动
触发条件:
BootAnimation 继承自 RefBase , 当首次被sp<T>引用时,触发onFirstRef().
智能指针机制: sp<T>在构造时增加引用计数,首次引用时调用onFirstRef().
关键操作:
---> linkToComposerDeath(); 监听SurfaceFlinger服务是否崩溃
---> run(), 调用 readToRun() 初始化资源,并启动threadLoop() 循环
run()触发线程执行:
---> 调用:readyToRun() 初始化资源
---> 进入:threadLoop() 循环。
void BootAnimation::onFirstRef() {
//绑定SurfaceFlinger死亡通知
status_err = mSession->linkToComposerDeath(this);
ALOGE_IF(err, "linkToComposerDeath failed(%s) " , strerror(-err));
if (err == NO_ERROR) {
//调用run()启动线程
run("BootAnimation",PRIORITY_DISPLAY);
}
}
由上述代码得知,调用了run()启动线程,接着进入到readToRun()方法中。
进入readToRun()方法
核心逻辑:
1:初始化显示信息:
status_t BootAnimation::readyToRun(){
...
//获取屏幕信息
sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
ISurfaceComposer::eDisplayIDMain));
DisplayInfo dinfo;
//获取主屏幕分辨率(dinfo.w 和 dinfo.h)
status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
...
}
2:创建Surface和EGL环境
创建SurfaceControl 和 Surface 对象,用于后与OpenGL 渲染
status_t BootAnimation::readyToRun(){
...
sp<SurfaceControl> control = session() -> createSurface(String8("BootAnimation"),dinfo.w , dinfo.h , PIXEL_FORMAT_RGB_565);
...
sp<Surface> s = control -> getSurface();
...
}
3:初始化EGL上下文
建立OpenGL 与 Surface 的关联,使渲染内容可显示到屏幕
status_t BootAnimation::readyToRun(){
...
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, 0 , 0);
eglChooseConfig(display , attribs , &config , 1 , &numConfigs);
surface = eglCreateWindowSurface(display , config , s.get() , NULL);
...
}
4:加载动画文件
e.g. /system/media/bootanimation.zip
检查是否存在自定义动画文件(优先级:加密动画 > OEM > 系统默认);
通信机制:
----> createSurface(): 通过Binder调用 SurfaceFlinger创建图层(SurfaceFlinger)
----> Surface 与 EGL 的关联: eglCreateWindowSurfcace将Surface绑定到OpenGL绘制表
status_t BootAnimation::readyToRun(){
...
for (constr char* f : ( !mShuttingDown ? bootFiles : shutdownFiles)){
if (access(f , R_OK) == 0) {
mZipFileName = f;
return NO_ERROR;
}
]
...
}
渲染threadLoop()
核心逻辑:
1:选择动画模式
//进入线程执行端
bool BootAnimation::threadLoop(){
bool r;
//We have no bootanimation file , so we use the stock android logo animation
if (mZipFileName.isEmpty()) {
//默认Android Logo动画
//如果不存在Zip动画,则播放默认的android动画
r = android();
} else {
//自定义 ZIP 动画
//执行解析Zip动画,播放Zip动画
r = movie();
}
...
}
2:默认动画 android()
-----> 渲染流程
---> 加载 android-logo-mask.png 和 android-logo-shine.png
---> 逐帧绘制动画光效,帧率 12fps
---> 检查退出信号 service.bootanim.exit = 1
3:自定义动画 movie()
-----> 核心函数:playAnimation()
BootAnimation::playAnimation 函数逐帧渲染流程分析
playAnimation(animation)
│
├── 初始化参数(帧率、位置)
│
├── 遍历动画分块(part0, part1...)
│ │
│ ├── 处理子动画(递归调用 playAnimation)
│ │
│ └── 遍历分块播放次数(循环播放逻辑)
│ │
│ ├── 设置背景颜色
│ │
│ ├── 逐帧渲染
│ │ ├── 绑定纹理
│ │ ├── 清理区域
│ │ ├── 绘制帧(glDrawTexiOES)
│ │ ├── 提交到 Surface(eglSwapBuffers)
│ │ ├── 同步帧率(clock_nanosleep)
│ │ └── 检查退出(checkExit)
│ │
│ └── 分块间暂停(usleep)
│
└── 释放纹理资源(glDeleteTextures)
接下来对 BootAnimation::playAnimation方法中的代码进行简单的分段分析
1:参数初始化
//动画分块数量(如 part0 , part1 )
const size_t pcount = animation.parts.size()//每帧持续时间(基于帧率计算)
nsecs_t frameDuration = s2ns(1) / animation.fps;
//动画在屏幕上的水平居中位置
const int animationX = (mWidth - animation.width) / 2;
//动画在屏幕上的垂直居中位置
const int animationY = (mHight - animation.height) / 2;
作用:计算动画的全局位置和帧率参数,为后续渲染做准备
2:遍历动画分块(Parts)
关键:
---> 每个分块(part)可能是一个独立动画判断(如开机Logo、加载进度条)。
---> fcount 表示当前分块的帧数(例如 part0 包含 30 帧图片)
...
for (size_t i = 0 ; i < pcount ; i ++ ) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size(); //当前分块的帧数
glBindTexture(GL_TEXTURE_2D , 0 ); //重置纹理
}
...
3:处理嵌套动画(递归调用)
作用:支持嵌套动画结构(例如分块 part1 本身是一个复杂动画)
递归逻辑:子动画会独立执行自己的playAnimation 流程
...
if(part.animation != NULL) {
playAnimation(*part.animation); //递归处理子动画
if(exitPending()) bread;
continue; //跳过当前分块的其他逻辑
}
...
4:分块播放次数循环
参数说明:
·part.count : 分块播放次数(0 表示无限循环)
·part.playUntilComplete : 是否必须播放完当前分块才能退出
...
for (int r = 0 ; !part.count || r<part.count ; r ++ ) {
//如果退出信号已触发且分块不要求播放完成,则退出
if (exitPending() && !part.playUntilComplete) break;
//调用回调(如播放音频)
mCallbacks -> playPart( i , part , r);
}
...
5:逐帧渲染逻辑
...
for (size_t j = 0 ; j < count ; j ++ ) {
const Animation::Frame& frame(part.frames[j]); //当前帧数据
nsecs_t lastFrame = systemTime(); //记录帧开始时间
//---纹理绑定与初始化---
if(r > 0){
glBindTexture(GL_TEXTURE_2D , frame.tid); //重用纹理
} else {
if (part.count != 1) {
glGenTextures(1 , &frame.tid); //生成纹理ID
glBindTextures(GL_TEXTURE_2D, frame.tid);
glTexParameterx(...); //设置纹理参数
}
initTexture(frame.map , &w , &h); //加载纹理数据
}
...
//---计算绘制区域---
const int xc = animationX + frame.trimX; //帧的水平起始位置
const int yc = animationY + frame.trimY; //帧的垂直起始位置
Region clearReg(...); //定义需要清除的区域
clearReg.subtractSelf(...); //排除当前帧区域
...
//---清理背景---
if(!clearReg.isEmpty()){
glEnable(GL_SCISSOR_TEST);
//遍历所有需要清除的矩形区域
while(head != tail) {
glScissor(...); //设置裁剪区域
glClear(...); //清除颜色缓冲区
}
glDisable(GL_SCISSOR_TEST);
}
...
//---绘制当前帧---
glDrawTexiOES(
xc, //水平位置
mHeight - (yc + frame.trimHeight), //垂直位置(转换为OpenGL坐标系)
0, //Z轴
frame.trimWidth, //帧宽度
frame.trimHeight, //帧高度
);
...
//---绘制时钟(若启用)---
if(mClockEnabled && mTimeIsAccurate && validClock(part)){
drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
}
...
//---提交帧到 SurfaceFlinger---
eglSwapBuffers(mDisplay, mSurface);
...
//---同步帧率---
nsecs_t now = systemTime();
nsecs_t delay = frameDuration - (now - lastFrame);
if (delay > 0){
clock_nanosleep(...); //精确休眠以控制帧率
}
//---检查退出信号---
checkExit();
}
关键操作:
1:纹理管理
·首次播放生成纹理(gIGenTexturess),后续循环重用
·initTexture 从动画ZIP文件加载图片数据到纹理
2:坐标系转化
·OpenGL 坐标系原点在屏幕左下角,需将垂直位置转换为 mHeight - (yc + height)
3:区域清理
·使用 glScissor和glClear清除非当前帧区域,避免残影
4:帧率同步
·计算每帧的理论持续时间(frameDuration),通过clock_nanosleep 精准控制帧率
6:分块间暂停
作用:根据 desc.txt 中配置的 pause 参数,在分块播放结束后暂停(例如开机Logo显示后暂停1秒)
usleep(part.pause * ns2us(frameDuration)); //分块播放后的暂停时间
7:释放纹理资源
逻辑:
---> 单词播放的分块(part.count == 1)无需释放纹理,可能被其他分块复用
---> 循环播放的分块需显示释放纹理,避免内存泄漏
for(const Animation::Part& part : animation.parts) {
if(part.count != 1) {
//仅释放循环播放分块的纹理
for(size_t j = 0 ; j < fcount ; j ++) {
glDeleteTexture(1 , &frame.tid); //删除纹理
}
}
}
通信协作机制
1. SurfaceFlinger的交互
·SurfaceFlingerClient:
----> 通过Binder IPC 与 SurfaceFlinger 通信,管理图层的创建和属性设置。
·eglSwapBuffers:
----> 将OpenGL 绘制的帧提交到 Surface 和 BufferQueue, SurfaceFligner 从队列中取出帧进行合成。
2. 帧率同步
·frameDuration:
----> 根据动画配置的帧率(如 30fps) 计算每帧的理论持续时间(33.3ms)
·clock_nanosleep:
----> 使用绝对时间(TIMER_ABSTIMES)避免累计误差,确保帧率精确
3.退出机制
·checkExit()
----> 检查 service.bootanim.exit属性,若为 1 则调用 requestExit()终止线程
·exitPending()
----> 在 threadLoop中检测退出标志,立即停止渲染
总结
1.逐帧渲染代码位置:
位于 for (size_t j = 0 ; j < fcount ; j ++) 循环内,包含 glDrawTexiOES和 eglSwapBuffers.
2.核心逻辑
加载纹理 ---> 清理区域 ---> 绘制帧 ---> 同步帧率 ---> 检查退出
3. 资源管理
按需生成和释放纹理,避免内存泄漏
4. 协作机制
通过SurfaceFlinger提交帧,通过属性服务响应退出信号。
步骤分析及整理流程
1. 入口函数 main() (bootanimation_main.cpp)
----设置线程优先级
----检查动画是否被禁用----启动Binder线程池
----等待SurfaceFlinger服务就绪
----加入Binder线程池
2. BootAnimation对象并启动线程
----创建SurfaceComposerClient对象
----绑定SurfaceFlinger的死亡通知
----启动线程运行 ”readyToRun()" 和 “threadLoop()"3. readyToRun() 方法
----初始化显示信息----创建Surface和EGL环境
----加载动画文件(如bootanimation.zip)
4. threadLoop()方法
----根据是否存在动画文件选择播放默认动画或自定义动画
----进入渲染循环,逐帧绘制并处理退出条件
整体调用流程
main()(bootanimation_main.cpp)
│
▼
BootAnimation 构造函数(BootAnimation.cpp)
│
▼
onFirstRef()(首次引用触发)
│
▼
run() → 启动线程
│
▼
readyToRun() → 初始化 OpenGL/Surface
│
▼
threadLoop() → 进入循环
│
├── android() → 默认动画
│
└── movie() → 自定义动画
│
▼
playAnimation() → 逐帧渲染
│
▼
退出 → 释放资源