安卓S开机动画流程

news2024/11/14 2:30:18

安卓S开机动画流程

开机动画是在SurfaceFlinger实例通过调用startBootAnim()启动的,BootAnim是如何启动和结束的,总体框架图如下:

1.SurfaceFlinger进程启动

# /frameworks/native/services/surfaceflinger/surfaceflinger.rc
service surfaceflinger /system/bin/surfaceflinger
    class core animation
    user system
    group graphics drmrpc readproc
    capabilities SYS_NICE
    onrestart restart zygote
    task_profiles HighPerformance
    socket pdx/system/vr/display/client     stream 0666 system graphics u:object_r:pdx_display_client_endpoint_socket:s0
    socket pdx/system/vr/display/manager    stream 0666 system graphics u:object_r:pdx_display_manager_endpoint_socket:s0
    socket pdx/system/vr/display/vsync      stream 0666 system graphics u:object_r:pdx_display_vsync_endpoint_socket:s0

init进程会根据surfaceflinger.rc配置启动surfaceflinger进程,surfaceflinger进程(/system/bin/surfaceflinger)启动,会走到main函数里面。

# /frameworks/native/services/surfaceflinger/Android.bp
filegroup {
    name: "surfaceflinger_binary_sources",
    srcs: [
        ":libsurfaceflinger_sources",
        "main_surfaceflinger.cpp",
    ],
}

cc_binary {
    name: "surfaceflinger",
    defaults: ["libsurfaceflinger_binary"],
    init_rc: ["surfaceflinger.rc"],
    srcs: [
        ":surfaceflinger_binary_sources",
        // Note: SurfaceFlingerFactory is not in the filegroup so that it
        // can be easily replaced.
        "SurfaceFlingerFactory.cpp",
    ],
    shared_libs: [
        "libSurfaceFlingerProp",
    ],

     logtags: ["EventLog/EventLogTags.logtags"],
}

可以看到编译surfaceflinger二进制进程的源文件为surfaceflinger_binary_sourcesSurfaceFlingerFactory.cpp,其中surfaceflinger_binary_sources来源于main_surfaceflinger.cpp

2.注册启动surfaceflinger服务

// /frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp
int main(int, char**) {
	// ...

    sp<SurfaceFlinger> flinger = surfaceflinger::createSurfaceFlinger(); //创建surfaceflinger服务实例

	// ...
    flinger->init();  // 初始化flinger实例

    // 向ServiceManager注册surfaceflinger服务
    sp<IServiceManager> sm(defaultServiceManager());
    sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false,
                   IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);

	// ...

    flinger->run();   // 启动surfaceflinger服务

    return 0;
}

调用SurfaceFlinger对象的init方法

// /frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::init() {
    // ...
    mStartPropertySetThread = getFactory().createStartPropertySetThread(presentFenceReliable);

    if (mStartPropertySetThread->Start() != NO_ERROR) {
        ALOGE("Run StartPropertySetThread failed!");
    }

    ALOGV("Done initializing");
}

SurfaceFlinger调用init方法时会获取mStartPropertySetThread,调用该对象的Start方法,其实是准备启动一个线程去启动BootAnimation

// /frameworks/native/services/surfaceflinger/StartPropertySetThread.cpp
status_t StartPropertySetThread::Start() {
	return run("SurfaceFlinger::StartPropertySetThread", PRIORITY_NORMAL);
}

bool StartPropertySetThread::threadLoop() {
    // Set property service.sf.present_timestamp, consumer need check its readiness
    property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");
    // 清除BootAnimation退出标志位service.bootanim.exit
    property_set("service.bootanim.exit", "0");
    // 设置bootanim的进度为0
    property_set("service.bootanim.progress", "0");
    // 通过service.bootanim.exit
    property_set("ctl.start", "bootanim");
    // 立即退出
    return false;
}
  • 这里一开始看起来比较疑惑,首先是StartPropertySetThread::Start函数,在StartPropertySetThread.h表明StartPropertySetThread继承自父类Thread,而父类Thread是由<utils/Thread.h> 引入的,所以这里是子类引用父类方法,这里的run函数就是就是thread的run方法。这里会启动一个线程去运行,线程名为"StartPropertySetThread",线程优先级为PRIORITY_NORMAL。线程启动以后,最终会调用 _threadLoop 函数,它会去调用threadLoop函数。这里整个函数调用栈就清楚了:

  • 当系统属性发生改变时,init进程就会接收到一个系统属性变化通知,这个通知最终是由在init进程中的函数handle_property_set_fd来处理

3.bootanim进程启动

// /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线程池
        sp<ProcessState> proc(ProcessState::self());
        ProcessState::self()->startThreadPool();
        
        sp<BootAnimation> boot = new BootAnimation(audioplay::createAnimationCallbacks());

        waitForSurfaceFlinger();
        
        boot->run("BootAnimation", PRIORITY_DISPLAY);
        // ...
    }
    return 0;
}

bool bootAnimationDisabled() {
    char value[PROPERTY_VALUE_MAX];
    // 如果debug.sf.nobootanimation=1,则不会显示动画
    property_get("debug.sf.nobootanimation", value, "0");
    if (atoi(value) > 0) {
        return true;
    }
	// 如果ro.boot.quiescent=1,则不显示开机动画
    property_get("ro.boot.quiescent", value, "0");
    if (atoi(value) > 0) {
        // Only show the bootanimation for quiescent boots if this system property is set to enabled
        if (!property_get_bool("ro.bootanim.quiescent.enabled", false)) {
            return true;
        }
    }

    return false;
}

判断完BootAnimation是不是disabled之后,如果noBootAnimation为false,则创建一个BootAnimation对象。创建完了BootAnimation对象后,调用其run方法,由于BootAnimation也继承了Thread,所以最终也会走到对应的threadLoop方法

bool BootAnimation::threadLoop()
{
    bool r;

    if (mZipFileName == NULL) {
        ...
    } else {
        r = movie();  // 调用movie方法
    }
    // 销毁 opengl 和 egl
    eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
    eglDestroyContext(mDisplay, mContext);
    eglDestroySurface(mDisplay, mSurface);
    mFlingerSurface.clear();
    mFlingerSurfaceControl.clear();
    eglTerminate(mDisplay);
    IPCThreadState::self()->stopProcess();
    return r;
}

bool BootAnimation::movie()
{
    String8 desString;
    // 读取 desc.txt 配置文件
    if (!readFile("desc.txt", desString)) {
        return false;
    }
    char const* s = desString.string();

    // 解析描述文件
    for (;;) {
        ...
    }

    for (size_t i=0 ; i<pcount ; i++) {
        for (int r=0 ; !part.count || r<part.count ; r++) {
            // opengl 绘制操作
            glClearColor(
                    part.backgroundColor[0],
                    part.backgroundColor[1],
                    part.backgroundColor[2],
                    1.0f);
            for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
                const Animation::Frame& frame(part.frames[j]);
                nsecs_t lastFrame = systemTime();
                ...
                if (r > 0) {
                    glBindTexture(GL_TEXTURE_2D, frame.tid);
                } else {
                    ...
                    initTexture(frame);
                }
                
                // specify the y center as ceiling((mHeight - animation.height) / 2)
                // which is equivalent to mHeight - (yc + animation.height)
                glDrawTexiOES(xc, mHeight - (yc + animation.height),
                              0, animation.width, animation.height);
                eglSwapBuffers(mDisplay, mSurface);

                // 不断绘制时检测是否需要退出
                checkExit();
            }
            // 如果退出了就跳出结束绘制
            if(exitPending() && !part.count)
                break;
        }

        // free the textures for this part
        if (part.count != 1) {
            for (size_t j=0 ; j<fcount ; j++) {
                const Animation::Frame& frame(part.frames[j]);
                glDeleteTextures(1, &frame.tid);
            }
        }
    }
    return false;
}

// 读取 service.bootanim.exit 值是否是 1 
#define EXIT_PROP_NAME "service.bootanim.exit"
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();
        if (mAudioPlayer != NULL) {
            mAudioPlayer->requestExit();
        }
    }
}

启动动画底层采用的是 opengles 的方式来渲染绘制的,绘制的内容是本地的一个启动动画资源包,在绘制的过程中会不断的判断是否需要退出,读取的字段是 service.bootanim.exit ,为 1 代表需要 break 退出循环绘制。因此我们只需要找到 service.bootanim.exit 在哪里设置为 1 的,便可找到退出启动动画的入口。关闭动画的入口还是在 SurfaceFlinger 中只是这个调用流程比较复杂而已:

final void handleResumeActivity(IBinder token,
                                boolean clearHide, boolean isForward, boolean reallyResume) {
    ActivityClientRecord r = performResumeActivity(token, clearHide);
    if (r != null) {
        if (!r.onlyLocalRequest) {
            r.nextIdle = mNewActivities;
            mNewActivities = r;
            // 添加了一个 IdleHandler 消息
            Looper.myQueue().addIdleHandler(new Idler());
        }
    } else {
        ...
    }
}

private class Idler implements MessageQueue.IdleHandler {
    @Override
        public final boolean queueIdle() {
        ActivityClientRecord a = mNewActivities;
        if (a != null) {
            mNewActivities = null;
            IActivityManager am = ActivityManagerNative.getDefault();
            ActivityClientRecord prev;
            do {
                if (a.activity != null && !a.activity.mFinished) {
                    try {
                        // 调用 AMS 的 activityIdle
                        am.activityIdle(a.token, a.createdConfig, stopProfiling);
                    } catch (RemoteException ex) {
                        // Ignore
                    }
                }
            } while (a != null);
        }
        return false;
    }
}


@Override
    public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {
    synchronized (this) {
        ActivityStack stack = ActivityRecord.getStackLocked(token);
        if (stack != null) {
            ActivityRecord r = mStackSupervisor.activityIdleInternalLocked(token, false, config);
        }
    }
    Binder.restoreCallingIdentity(origId);
}

// Checked.
final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
                                                Configuration config) {
    ActivityRecord r = ActivityRecord.forTokenLocked(token);
    if (r != null) {
        ...
            if (isFrontStack(r.task.stack) || fromTimeout) {
                booting = checkFinishBootingLocked();
            }
    }
    ...
        return r;
}

private boolean checkFinishBootingLocked() {
    final boolean booting = mService.mBooting;
    boolean enableScreen = false;
    mService.mBooting = false;
    if (!mService.mBooted) {
        mService.mBooted = true;
        enableScreen = true;
    }
    if (booting || enableScreen) {
        mService.postFinishBooting(booting, enableScreen);
    }
    return booting;
}

void enableScreenAfterBoot() {
    mWindowManager.enableScreenAfterBoot();
    synchronized (this) {
        updateEventDispatchingLocked();
    }
}

public void performEnableScreen() {
    synchronized(mWindowMap) {
        if (!mBootAnimationStopped) {
            // 向SurfaceFlinger 进程发起关闭开机界面的消息
            try {
                IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
                if (surfaceFlinger != null) {
                    Parcel data = Parcel.obtain();
                    data.writeInterfaceToken("android.ui.ISurfaceComposer");
                    // 向SurfaceComposer发送
                    surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
                                            data, null, 0);
                    data.recycle();
                }
            } catch (RemoteException ex) {
                ...
            }
            mBootAnimationStopped = true;
        }
        ...
    }
}

// ----------------------------------------------------------------------
enum ISurfaceComposerTag {
    BOOT_FINISHED = IBinder::FIRST_CALL_TRANSACTION,
    // ...
}

// /frameworks/native/libs/gui/ISurfaceComposer.cpp
status_t BnSurfaceComposer::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags){
    switch(code) {
            // ...
        case BOOT_FINISHED: {
            CHECK_INTERFACE(ISurfaceComposer, data, reply);
            bootFinished();
            return NO_ERROR;
        }
            // ...
    }
}
// ----------------------------------------------------------------------

void SurfaceFlinger::bootFinished() {
    // ...
    // 把 service.bootanim.exit 属性设置为 1 ,bootanim 进程读到 1 时就会退出开机启动动画
    property_set("service.bootanim.exit", "1");
}

关闭开机启动动画的流程还是比较复杂的,我们来缕一缕整个逻辑,我们的 Launcher 进程启动后会启动我们 Launcher Activity 界面,而 Activity 的生命周期调用都是由 ActivityThread 来执行的,其中就会执行到 handleResumeActivity 方法,在该方法中会添加一个 IdleHandler 消息,会调用到 AMS 的 activityIdle 方法,AMS 会调用 WMS 的 enableScreenAfterBoot 方法,WMS 会跨进程通知 SurfaceFlinger 去关闭我们的开机启动动画。

4.开机动画包里有什么

这里建议先看一下官方文档:/frameworks/base/cmds/bootanimation/FORMAT.md

开机动画指的是以bootanimation.zip方式存在,启动的时候会依次选择一个bootanimation.zip加载

  1. /system/media/bootanimation-encrypted.zip (if getprop(“vold.decrypt”) = ‘1’)
  2. /system/media/bootanimation.zip
  3. /oem/media/bootanimation.zip

bootanimation.zip 文件中包含:

在这里插入图片描述

desc.txt - a text file
part0  \
part1   \  directories full of PNG frames
...     /
partN  /

“desc.txt”用来描述用户自定义的开机动画是如何显示的

以下面的例子为例:

1280 720 1

p 1 1 part0

p 0 1 part1

第一行的三个数字分别表示开机动画在屏幕中的显示宽度、高度以及帧速(fps)。剩余的每一行都用来描述一个动画片断,这些行必须要以字符“p”来开头,后面紧跟着两个数字以及一个文件目录路径名称。
第一个数字表示一个片断的循环显示次数,如果它的值等于0,那么就表示无限循环地显示该动画片断。
第二个数字表示每一个片断在两次循环显示之间的时间间隔。这个时间间隔是以一个帧的时间为单位的。
文件目录下面保存的是一系列png文件,这些png文件会被依次显示在屏幕中。

参考资料:

  • https://blog.51cto.com/u_11176305/3796348
  • https://www.cnblogs.com/lufeibin/p/13529981.html
  • https://blog.csdn.net/weixin_36044720/article/details/117277602?spm=1001.2014.3001.5506

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/176828.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux inode详解

1.inode 和 block 概述. 操作系统的文件数据除了实际内容之外&#xff0c;通常含有非常多的属性&#xff0c;例如Linux操作系统的文件权限与文件属性。文件系统通常会将这两部分内容分别存放在inode和block中。 文件是存储在硬盘上的&#xff0c;硬盘的最小存储单位叫做扇区sec…

行为型模式

1.模版方法 超类中定义了一个算法的框架&#xff0c; 允许子类在不修改结构的情况下重写算法的特定步骤 结构 抽象类&#xff1a;负责给出一个轮廓与骨架&#xff0c;由一个模版方法和若干个基本方法构成 模版方法&#xff1a;按某种顺序调用其包含的基本方法基本方法&#xf…

计算机视觉OpenCv学习系列:第八部分、图像操作-4

第八部分、图像操作-4第一节、图像卷积操作1.图像卷积定义2.卷积函数3.代码练习与测试第二节、高斯模糊1.高斯模糊2.函数解释3.代码练习与测试第三节、像素重映射1.像素重映射定义2.重映射函数3.代码练习与测试学习参考第一节、图像卷积操作 1.图像卷积定义 卷积的基本原理&am…

java spring IOC xml 方式 内部Bean注入

上次说了外部 Bean注入 这次来演示一个内部的 Bean注入 我们先创建一个spring 项目 导入最基本的 spring 包 在项目src目录下创建一个包 cascade cascade包下创建两个类 Dept 部门类 参考代码如下 package cascade;//部门类 public class Dept {private String dname;publi…

windows ssdt

前言 我们 ring 3 跳转 ring0 另一种方式使用sysenter命令。 sysenter 相比起jmp,int xx方式相比速度更快&#xff0c;因为sysenter指令大量的使用了MSR寄存器 存储跳转地址等。 MSR寄存器相关读/写命令 //读取msr寄存器 rdmsr xxxx //写入msr寄存器 wrmsr xxxx其中xxx是ms…

轻量实时操作系统学习(一)

306xH系列产品基于高性能RISC-V CPU核&#xff0c;工作频率最高到200MHz&#xff0c;集成了FPU浮点处理单元&#xff0c;支持浮点乘法&#xff0c;支持浮点乘法&#xff0c;除法和开方等复杂数学运算指令&#xff0c;支持16KB的SRAM和最高160KB的flash存储单元。 该MCU集成最多…

【My Electronic Notes系列——二极管】

目录 序言&#xff1a; &#x1f3ee;&#x1f3ee;新年的钟声响&#xff0c;新年的脚步迈&#xff0c;祝新年的钟声&#xff0c;敲响你心中快乐的音符&#xff0c;幸运与平安&#xff0c;如春天的脚步紧紧相随&#xff0c;春节快乐&#xff01;春华秋实&#xff0c;我永远与…

【目标检测论文解读复现NO.25】基于改进Yolov5的地铁隧道附属设施与衬砌表观病害检测方法

前言 此前出了目标改进算法专栏&#xff0c;但是对于应用于什么场景&#xff0c;需要什么改进方法对应与自己的应用场景有效果&#xff0c;并且多少改进点能发什么水平的文章&#xff0c;为解决大家的困惑&#xff0c;此系列文章旨在给大家解读最新目标检测算法论文&#xff0c…

【Unity Shader 赛博小人01】UV准备 SD制作特效贴图

写在前面 Unity 卡通渲染 程序化天空盒提到了——“再整个uv1将云片平铺&#xff0c;将uv1对应到世界坐标x轴旋转角、y轴旋转角&#xff0c;消散信息放到顶点色。”&#xff0c;就是这句话&#xff01;我又遇到了几个问题&#xff08;本菜鸡不知道的东西太多太多了&#xff09…

Spring的使用

开篇点题&#xff1a;为什么要用到Spring&#xff1f;参考这篇文章&#xff1a;http://t.csdn.cn/oR5lM一、创建一个Maven项目1.创建一个Maven项目2.添加Spring依赖/框架支持在pom.xml中添加框架的支持&#xff0c;xml配置如下&#xff1a;&#xff08;下载失败参考&#xff1a…

设计模式_自定义Spring框架(IOC)

设计模式_自定义Spring框架&#xff08;IOC&#xff09; 笔记整理自 黑马程序员Java设计模式详解&#xff0c; 23种Java设计模式&#xff08;图解框架源码分析实战&#xff09; Spring使用回顾 自定义spring 框架前&#xff0c;先回顾一下 Spring 框架的使用&#xff0c;从而分…

acwing基础课——快速幂

由数据范围反推算法复杂度以及算法内容 - AcWing 常用代码模板4——数学知识 - AcWing 基本思想&#xff1a; 求一个数的n次时&#xff0c;我们的时间复杂度为O(n),当n特别大时&#xff0c;效率会很低可能超时&#xff0c;此时我们就需要运用到快速幂&#xff0c;将我们的时间…

基于PHP和MySQL的新闻发布系统——【功能优化】

前言 2023年第一篇文章&#xff0c;祝大家在新的一年里“卯”足干劲&#xff0c;在技术上 “兔”飞猛进&#xff01; 上一篇文章 基于PHP和MySQL的新闻发布系统 给大家介绍了制作一个新闻发布系统的主要功能的实现&#xff0c;在文章的末尾还提出了一些需要完善的方面。那么…

Vue3【style-scoped、style-module、类和内联样式、props、练习】

文章目录style-scopedstyle-module类和内联样式props练习style-scoped 可以直接通过style标签来编写样式&#xff0c; 如果直接通过style标签写样式&#xff0c;此时编写的样式是全局样式会影响到所有的组件 可以为style标签添加一个scoped属性&#xff0c;这样样式将成为局部…

为什么会有右值引用?(移动构造、移动赋值)

目录 1、左值引用的缺陷 2、移动构造&#xff1a;解决临时对象的深拷贝 3、拓展&#xff1a;移动赋值 1、左值引用的缺陷 左值引用作为函数参数传递&#xff0c;减少了参数拷贝&#xff1b;但是作为函数返回值&#xff0c;并不适用于所有场景&#xff0c;比如要返回一个临…

Linux——innode

目录 回顾缓冲区 标准错误流的理解 文件系统 Inode VS 文件名 创建/删除/查看文件系统做了什么 软硬链接 动静态库 习题 回顾缓冲区 关掉1&#xff0c;log.txt中没文件是因为&#xff0c;字符串在缓冲区当中&#xff0c;缓冲区还没刷新&#xff0c;我们把fd给关了…

堆的实现及应用

下面用C语言介绍堆的实现以及应用 文章目录1. 堆的简介2. 堆的实现HeapInitHeapDestroyHeapPushHeapPop3. 堆的应用堆排序TopK问题1. 堆的简介 堆是一颗完全二叉树。这里所说的堆是一种非连续的数据结构&#xff0c;与操作系统内存分布的堆是两回事&#xff0c;它们没有任何联…

金融帝国实验室(Capitalism Lab)官方中文汉化包下载(v4.03)

<FCT汉化小组>Vol.001号作品 ————————————— ◎作品名称&#xff1a;金融帝国实验室&#xff08;CapLab&#xff09;官方汉化包 ◎汉化作者&#xff1a;FCT汉化小组&#xff08;Enlight Software认证&#xff09; ◎发布版本&#xff1a;CapLab Simplifi…

POJ 1845 Sumdiv题解(C++ 整数惟一分解定理+分治法求等比数列之和+快速幂)

文章目录整数惟一分解定理分治法求等比数列和完整代码传送门&#xff1a; POJ 1845 SumDiv 整数惟一分解定理 任何一个大于1的整数n都可以分解成若干个质因数&#xff08;素因数&#xff09;的连乘积&#xff0c;如果不计各个素因数的顺序&#xff0c;那么这种分解是唯一的&a…

王者荣耀入门技能树

前言 最近在学习技能树&#xff0c;我也试着写一写技能树&#xff0c;放松一下。 这里附上一张可爱的兔兔应景。仔细看&#xff0c;后边题目会提到&#xff0c;哈哈。 职业 以下哪个不属于王者荣耀中的职业&#xff1a; 射手法师辅助亚瑟 技能 以下哪个技能可以加快打野…