Android8开机动画,2:bootanimation运行和结束

(学习课程为千里马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() → 逐帧渲染
 │
 ▼
退出 → 释放资源

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值