跳转至

bootanimation程序简介

bootanimation是在kernel启动完毕后,由init程序根据rc文件call起来的程序,其主要功能是在启动Android服务时,屏幕显示一个开机动画,以此来改善用户体验的一个程序。本文分析该程序主要流程,以及个功能模块的代码实现。

代码结构

(图片来源于网络,从Android12.0的代码没有看到支持video的部分,请忽略)

如上图所示,程序的主要功能类都已经在上图中描述。应用代码位于frameworks/base/cmds/bootanimation,各代码文件如下表所示:

文件名 简要说明
Android.bp 编译脚本
bootanim.rc 开机启动脚本
bootanimation_main.cpp 程序入口
BootAnimation.cpp 应用主类,功能代码基本在该文件中实现
BootAnimation.h --
BootAnimationUtil.cpp --
BootAnimationUtil.h --
audioplay.cpp --
audioplay.h --

创建BootAnimation对象

BootAnimation的程序入口为bootanimation_main.cpp中的main方法,该方法中会创建BootAnimation对象。

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/bootanimation_main.cpp

int main()
{
    ...
        bool noBootAnimation = bootAnimationDisabled();
    if (!noBootAnimation) {

        ...

            // create the boot animation object (may take up to 200ms for 2MB zip)
            sp<BootAnimation> boot = new BootAnimation(audioplay::createAnimationCallbacks());

        waitForSurfaceFlinger();

        ...
        }
    return 0;
}

main方法中创建BootAnimation对象后会将其赋值给一个sp[BootAnimation]对象,sp类重载了=运算符,赋值时会调用右值对象的incStrong()方法,以此标记当前对象正在使用。赋值的最终目的是将BootAnimation对象对象指针保存到sp对象的m_ptr成员中。

sp[BootAnimation] 应该写成 sp<BootAnimation> ,目前因为 obsidian 对markdown 支持还有点问题。
http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

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());
}

BootAnimation的构造函数中:

  • mSession是BootAnimation类的一个成员变量,它的类型为SurfaceComposerClient,是用来和SurfaceFlinger执行Binder进程间通信。

SurfaceComposerClient类内部有一个实现了ISurfaceComposerClient接口的Binder代理对象mClient,这个Binder代理对象引用了SurfaceFlinger服务,SurfaceComposerClient类就是通过它来和SurfaceFlinger服务通信的。 由于BootAnimation类引用了SurfaceFlinger服务,因此,当SurfaceFlinger服务意外死亡时,BootAnimation类就需要得到通知,并执行binderDied函数。

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp


void BootAnimation::binderDied(const wp<IBinder>&) {
    // woah, surfaceflinger died!
    SLOGD("SurfaceFlinger died, exiting...");

    // calling requestExit() is not enough here because the Surface code
    // might be blocked on a condition variable that will never be updated.
    kill( getpid(), SIGKILL );
    requestExit();
}

binderDied函数会杀死当前进程,并退出。

  • 通过读取属性“sys.powerctl”来确定当前播放的是开机动画还是关机动画。

对于不熟悉cpp代码的同学看到这就觉得很奇怪,在bootanimation_main.cpp的main还是里调用BootAnimation构造函数,也没看到BootAnimation的构造函数里继续做任何工作啊,那这个程序怎么跑呢? 首先我们看下BootAnimation的头文件:

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.h

class BootAnimation : public Thread, public IBinder::DeathRecipient    //继承了Thread类和IBinder::DeathRecipient类
{
    ...

 private:
    virtual bool        threadLoop();//线程体,如果返回true,且requestExit()没有被调用,则该函数会再次执行;如果返回false,则threadloop中的内容仅仅执行一次,线程就会退出
    virtual status_t    readyToRun();//线程体执行前的初始化工作
    virtual void        onFirstRef();//属于其父类RefBase,该函数在强引用sp新增引用计数時调用,就是当有sp包装的类初始化的时候调用    
    virtual void        binderDied(const wp<IBinder>& who);//当对象死掉或者其他情况导致该Binder结束时,就会回调binderDied()方法

    ...
}

onFirstRef()

BootAnimation类继承了Thread类和IBinder::DeathRecipient类,BootAnimation类间接地继承了RefBase类,并且重写了RefBase类的成员函数onFirstRef,当一个BootAnimation对象第一次被智能指针引用时,这个BootAnimation对象的成员函数onFirstRef就会被调用。

readyToRun()

BootAnimation类继承了Thread类,当BootAnimation类的成员函数onFirstRef调用了父类Thread的成员函数run之后,系统就会创建一个线程,这个线程在第一次运行之前,会调用BootAnimation类的成员函数readyToRun来执行一些Thread执行前的初始化工作。

threadLoop()

后面再调用BootAnimation类的成员函数threadLoop来显示开机画面,每个线程类都要实现的,在这里定义thread的执行内容。这个函数如果返回true,且没有调用requestExit(),则该函数会再次执行;如果返回false,则threadloop中的内容仅仅执行一次,线程就会退出。

android()或者movie()

显示完成之后,就会销毁前面所创建的EGLContext对象mContext、EGLSurface对象mSurface,以及EGLDisplay对象mDisplay等。android()方法里主要是使用方法绘制安卓字样,这边的退出使用的是checkExit();一般使用自定义动画播放,因此主要看一下movie方法里的主要调用方法。

初始化动画文件

前面讲到在sp的赋值过程中会调用RefBase的incStrong方法,该方法会在首次引用时调用当前类的onFirstRef方法,该方法被BootAnimation类重写了。 也就是说BootAnimation类间接继承了RefBase类,且上面的main函数创建BootAnimation对象的时候使用智能指针引用,所以执行BootAnimation类的构造函数创建对象时,也会执行onFirstRef函数,下面是onFirstRef函数:

onFirstRef()

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

void BootAnimation::onFirstRef() {
    //注册SurfaceFlinger服务的死亡接收通知
    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());
    }
}

onFirstRef先注册SurfaceFlinger服务的死亡接收通知,接着调用preloadAnimation加载开机动画资源文件。

preloadAnimation()

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp


bool BootAnimation::preloadAnimation() {
    findBootAnimationFile();
    if (!mZipFileName.isEmpty()) {
        mAnimation = loadAnimation(mZipFileName);
        return (mAnimation != nullptr);
    }

    return false;
}

调用findBootAnimationFile找到动画文件,也就是初始化 mZipFileName。

findBootAnimationFile()

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

void BootAnimation::findBootAnimationFile() {
    // If the device has encryption turned on or is in process
    // of being encrypted we show the encrypted boot animation.
    char decrypt[PROPERTY_VALUE_MAX];
    property_get("vold.decrypt", decrypt, "");

    bool encryptedAnimation = atoi(decrypt) != 0 ||
        !strcmp("trigger_restart_min_framework", decrypt);

    if (!mShuttingDown && encryptedAnimation) {
        static const std::vector<std::string> encryptedBootFiles = {
            PRODUCT_ENCRYPTED_BOOTANIMATION_FILE, SYSTEM_ENCRYPTED_BOOTANIMATION_FILE,
        };
        if (findBootAnimationFileInternal(encryptedBootFiles)) {
            return;
        }
    }

    const bool playDarkAnim = android::base::GetIntProperty("ro.boot.theme", 0) == 1;
    //开机动画文件
    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);
    }
}
  • 首先根据系统属性vold.decrypt来判断系统是否启动加密或者正在加密处理,如果是,则会根据优先级降级的方式,去播放如下两个路径的加密动画:
  • static const char PRODUCT_ENCRYPTED_BOOTANIMATION_FILE[] = "/product/media/bootanimation-encrypted.zip"
  • static const char SYSTEM_ENCRYPTED_BOOTANIMATION_FILE[] = "/system/media/bootanimation-encrypted.zip"
  • 其次根据系统属性sys.init.userspace_reboot.in_progress来判断系统是否正在重启,如果是,则会根据优先级降级的方式,去播放如下三个路径的重启动画:
  • static constexpr const char* PRODUCT_USERSPACE_REBOOT_ANIMATION_FILE = "/product/media/userspace-reboot.zip"
  • static constexpr const char* OEM_USERSPACE_REBOOT_ANIMATION_FILE = "/oem/media/userspace-reboot.zip"
  • static constexpr const char* SYSTEM_USERSPACE_REBOOT_ANIMATION_FILE = "/system/media/userspace-reboot.zip"
  • 再次根据Bootanimation构造函数中获取到的系统属性sys.powerctl判断系统是否正在关机,如果是,则会根据优先级降级的方式,去播放如下三个路径的关机动画:
  • static const char PRODUCT_SHUTDOWNANIMATION_FILE[] = "/product/media/shutdownanimation.zip"
  • static const char OEM_SHUTDOWNANIMATION_FILE[] = "/oem/media/shutdownanimation.zip"
  • static const char SYSTEM_SHUTDOWNANIMATION_FILE[] = "/system/media/shutdownanimation.zip"
  • 否则为开机,会根据优先级降级的方式,去播放如下五个路径的开机动画:
  • static const char APEX_BOOTANIMATION_FILE[] = "/apex/com.android.bootanimation/etc/bootanimation.zip" (apex升级的方式最优先)
  • static const char PRODUCT_BOOTANIMATION_DARK_FILE[] = "/product/media/bootanimation-dark.zip" (根据系统属性ro.boot.theme判断是暗黑主题还是明亮主题)
  • static const char PRODUCT_BOOTANIMATION_FILE[] = "/product/media/bootanimation.zip"
  • static const char OEM_BOOTANIMATION_FILE[] = "/oem/media/bootanimation.zip"
  • static const char SYSTEM_BOOTANIMATION_FILE[] = "/system/media/bootanimation.zip"

    总结:优先级的顺序一般是 product > oem > system

findBootAnimationFileInternal()

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

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;
}

可以看出来就是把数据的第一个元素赋值给mZipFileName。

解析动画文件

在preloadAnimation()初始化mZipFileName后调用loadAnimation(mZipFileName)来解析动画文件。

loadAnimation()

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

BootAnimation::Animation* BootAnimation::loadAnimation(const String8& fn) {
    //如果已经加载,则返回
    if (mLoadedFiles.indexOf(fn) >= 0) {
        SLOGE("File \"%s\" is already loaded. Cyclic ref is not allowed",
            fn.string());
        return nullptr;
    }

    //打开zip文件
    ZipFileRO *zip = ZipFileRO::open(fn);
    if (zip == nullptr) {
        SLOGE("Failed to open animation zip \"%s\": %s",
            fn.string(), strerror(errno));
        return nullptr;
    }

    Animation *animation =  new Animation;
    animation->fileName = fn;
    animation->zip = zip;
    animation->clockFont.map = nullptr;
    mLoadedFiles.add(animation->fileName);

    //解析描述文件"desc.txt"
    parseAnimationDesc(*animation);
    if (!preloadZip(*animation)) {
        releaseAnimation(animation);
        return nullptr;
    }

    mLoadedFiles.remove(fn);
    return animation;
}

打开zip文件并调用parseAnimationDesc解析描述文件"desc.txt"。

parseAnimationDesc()

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

bool BootAnimation::parseAnimationDesc(Animation& animation)  {
    String8 desString;

    if (!readFile(animation.zip, "desc.txt", desString)) {
        return false;
    }
    char const* s = desString.string();

    // Parse the description file
    for (;;) {
        const char* endl = strstr(s, "\n");
        if (endl == nullptr) break;
        String8 line(s, endl - s);
        const char* l = line.string();
        int fps = 0;
        int width = 0;
        int height = 0;
        int count = 0;
        int pause = 0;
        int progress = 0;
        int framesToFadeCount = 0;
        char path[ANIM_ENTRY_NAME_MAX];
        char color[7] = "000000"; // default to black if unspecified
        char clockPos1[TEXT_POS_LEN_MAX + 1] = "";
        char clockPos2[TEXT_POS_LEN_MAX + 1] = "";
        char pathType;

        int nextReadPos;

        int topLineNumbers = sscanf(l, "%d %d %d %d", &width, &height, &fps, &progress);
        if (topLineNumbers == 3 || topLineNumbers == 4) {
            // SLOGD("> w=%d, h=%d, fps=%d, progress=%d", width, height, fps, progress);
            animation.width = width;
            animation.height = height;
            animation.fps = fps;
            if (topLineNumbers == 4) {
              animation.progressEnabled = (progress != 0);
            } else {
              animation.progressEnabled = false;
            }
        } 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;
            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);
        }
        else if (strcmp(l, "$SYSTEM") == 0) {
            // SLOGD("> SYSTEM");
            Animation::Part part;
            part.playUntilComplete = false;
            part.framesToFadeCount = 0;
            part.count = 1;
            part.pause = 0;
            part.audioData = nullptr;
            part.animation = loadAnimation(String8(SYSTEM_BOOTANIMATION_FILE));
            if (part.animation != nullptr)
                animation.parts.add(part);
        }
        s = ++endl;
    }

    return true;
}

乍一看这个代码很复杂,其实主要是通过sscanf函数按照顺序从上到下依次解析每一行。如果第一行包含3或者4个数字则是:用来描述开机动画在屏幕显示的大小及速度。否则为一个播放片段,播放片段分为标识符、循环的次数、阶段切换间隔时间、图片的目录、动画淡出帧。

下面来结合描述文件的具体内容一起分析。

desc.txt描述文件

我们找到一个bootanimation.zip的解压:

╭─[solo@10.0.12.7]  [/Users/solo/Downloads]
╰─(py39tf2.x) ❯❯❯❯❯❯ ll bootanimation
total 8
drwx------@   8 solo  staff   256B Jul  1 12:30 .
drwx------+  45 solo  staff   1.4K Jul  1 12:13 ..
-rw-------@   1 solo  staff    75B Jan 14  2021 desc.txt
drwxr-xr-x@   3 solo  staff    96B Jul  1 12:13 part0
drwxr-xr-x@  36 solo  staff   1.1K Jul  1 12:13 part1
drwxr-xr-x@  41 solo  staff   1.3K Jul  1 12:13 part2
drwxr-xr-x@ 187 solo  staff   5.8K Jul  1 12:13 part3
drwxr-xr-x@ 104 solo  staff   3.3K Jul  1 12:13 part4
其"desc.txt"文件中的内容如下:
512 416 60
c 1 0 part0
c 2 15 part1
c 1 0 part2
c 1 0 part3
f 0 0 part4 10

结合上面的代码,我们把desc.txt拆分成两个部分:topLine和非topLine。

  • 如果topLine包含3或者4个数字,则是用来描述开机动画在屏幕显示的大小及速度。
图片的宽 图片的高 每秒显示的帧数 显示百分比进度
512 416 60 --

具体为:开机动画的宽度为512个像素,高度为416个像素,显示频率为每秒60帧,即每帧显示1/60秒。 Android12.0中加入了“显示百分比”,就是代码中的progressEnabled。

  • 否则为一个播放片段
标识符 循环的次数 阶段切换间隔时间 相应图片的目录 动画淡出帧
c 1 0 part0 --
c 2 15 part1 --
f 0 0 part4 10
  • 标识符:Android5.1之前只有p;Android5.1中加入了c;Android12.0中加入了f。
  • 循环的次数:表示该片段显示的次数,如果为‘0’,表示会无限重复显示。
  • 阶段切换间隔时间:一个片段或与下一个片段之间的时间间隔;阶段切换间隔时间 * (1/每秒显示的帧数)。
  • 相应图片的目录:bootanimation.zip解析出来后的文件目录,
  • **动画淡出帧:**Android12.0中加入了动画淡出帧,也就是代码中的framesToFadeCount,到第几帧后动画开始淡出。

举例:

  • c 1 0 part0:代表该片段显示1次,与下一个片段间隔0s,该片段的显示图片路径为bootanimation.zip/part0。
  • c 2 15 part1:代表该片段显示2次,与下一个片段间隔15*(1/60)=(¼)s,与下一个片段间隔15*(1/60)=(¼)s,该片段的显示图片路径为bootanimation.zip/part1。
  • f 0 0 part4 10:代表该片段无限循环显示,且两次显示的间隔为0s,该片段的显示图片路径为bootanimation.zip/part4,到第10帧动画开始淡出。

通过"desc.txt"文件结合代码(看一眼就能知道的)我们大概知道了其运作原理,但刚才我们是遗留几个问题没有阐述清楚:

  • 标识符
  • **显示百分比**进度
  • 动画淡出帧

标识符 和 动画淡出帧

如果标识符为c:

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

bool BootAnimation::parseAnimationDesc(Animation& animation)  {
    ...

    } 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;
        part.playUntilComplete = pathType == 'c';
        part.framesToFadeCount = framesToFadeCount;
        ...
    }
   ...
}

可以看到,如果pathType等于'c',part.playUntilComplete等于true,否则为false。接着,看一下显示代码:

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

bool BootAnimation::shouldStopPlayingPart(const Animation::Part& part,
                                          const int fadedFramesCount,
                                          const int lastDisplayedProgress) {
    // stop playing only if it is time to exit and it's a partial part which has been faded out
    return exitPending() && !part.playUntilComplete && fadedFramesCount >= part.framesToFadeCount &&
        (lastDisplayedProgress == 0 || lastDisplayedProgress == 100);
}



bool BootAnimation::playAnimation(const Animation& animation) {
    ...
        for (size_t i=0 ; i<pcount ; i++) {
            ...
                // process the part not only while the count allows but also if already fading
                for (int r=0 ; !part.count || r<part.count || fadedFramesCount > 0 ; r++) {
                    if (shouldStopPlayingPart(part, fadedFramesCount, lastDisplayedProgress)) break;

                    ...

                        for (size_t j=0 ; j<fcount ; j++) {
                            if (shouldStopPlayingPart(part, fadedFramesCount, lastDisplayedProgress)) break;

                            ...
                            }

                    ...
                    }
        }

    ....
}

可以看到,如果exitPending()返回值为true且part.playUntilComplete=false,则会break。即:当SurfaceFlinger服务要求bootanimation停止显示动画时,以‘p’标识的片段会停止,而以'c'标识的片段会继续显示。这就是两者之间的主要区别。 这里有个问题:重复循环显示的'c'标识片段,会不受任何约束的一直显示下去,这显然是不合适的。所以在Android12.0又新增了条件:

fadedFramesCount >= part.framesToFadeCount && (lastDisplayedProgress == 0 || lastDisplayedProgress == 100

当前播放的帧大于等于描述文件里设置的framesToFadeCount,就停止动画。

显示百分比进度

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

bool BootAnimation::parseAnimationDesc(Animation& animation)  {
    ...
    int topLineNumbers = sscanf(l, "%d %d %d %d", &width, &height, &fps, &progress);
    if (topLineNumbers == 3 || topLineNumbers == 4) {
        ...
        if (topLineNumbers == 4) {
          animation.progressEnabled = (progress != 0);
        } else {
          animation.progressEnabled = false;
        }
    } 
    ...
}

如果描述文件里第一行有第四个数字,并且不为0,则显示播放动画的时候显示进度。

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

bool BootAnimation::playAnimation(const Animation& animation) {
    ...
    for (size_t i=0 ; i<pcount ; i++) {
        ...

        // process the part not only while the count allows but also if already fading
        for (int r=0 ; !part.count || r<part.count || fadedFramesCount > 0 ; r++) {
            ...

            // For the last animation, if we have progress indicator from
            // the system, display it.
            int currentProgress = android::base::GetIntProperty(PROGRESS_PROP_NAME, 0);
            bool displayProgress = animation.progressEnabled &&
                (i == (pcount -1)) && currentProgress != 0;

            for (size_t j=0 ; j<fcount ; j++) {
                ...

                if (displayProgress) {
                    int newProgress = android::base::GetIntProperty(PROGRESS_PROP_NAME, 0);
                    // In case the new progress jumped suddenly, still show an
                    // increment of 1.
                    if (lastDisplayedProgress != 100) {
                      // Artificially sleep 1/10th a second to slow down the animation.
                      usleep(100000);
                      if (lastDisplayedProgress < newProgress) {
                        lastDisplayedProgress++;
                      }
                    }
                    // Put the progress percentage right below the animation.
                    int posY = animation.height / 3;
                    int posX = TEXT_CENTER_VALUE;
                    drawProgress(lastDisplayedProgress, animation.progressFont, posX, posY);
                }

                ...
            }

            ...
        }
    }

    ...
}

代码细节这里不展开叙述。

preloadZip()

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

bool BootAnimation::preloadZip(Animation& animation) {
    // read all the data structures
    const size_t pcount = animation.parts.size();
    void *cookie = nullptr;
    ZipFileRO* zip = animation.zip;
    if (!zip->startIteration(&cookie)) {
        return false;
    }

    ZipEntryRO entry;
    char name[ANIM_ENTRY_NAME_MAX];
    while ((entry = zip->nextEntry(cookie)) != nullptr) {
        const int foundEntryName = zip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX);
        if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) {
            SLOGE("Error fetching entry file name");
            continue;
        }

        const String8 entryName(name);
        const String8 path(entryName.getPathDir());
        const String8 leaf(entryName.getPathLeaf());
        if (leaf.size() > 0) {
            if (entryName == CLOCK_FONT_ZIP_NAME) {
                FileMap* map = zip->createEntryFileMap(entry);
                if (map) {
                    animation.clockFont.map = map;
                }
                continue;
            }

            if (entryName == PROGRESS_FONT_ZIP_NAME) {
                FileMap* map = zip->createEntryFileMap(entry);
                if (map) {
                    animation.progressFont.map = map;
                }
                continue;
            }

            for (size_t j = 0; j < pcount; j++) {
                if (path == animation.parts[j].path) {
                    uint16_t method;
                    // supports only stored png files
                    if (zip->getEntryInfo(entry, &method, nullptr, nullptr, nullptr, nullptr, nullptr)) {
                        if (method == ZipFileRO::kCompressStored) {
                            FileMap* map = zip->createEntryFileMap(entry);
                            if (map) {
                                Animation::Part& part(animation.parts.editItemAt(j));
                                if (leaf == "audio.wav") {
                                    // a part may have at most one audio file
                                    part.audioData = (uint8_t *)map->getDataPtr();
                                    part.audioLength = map->getDataLength();
                                } else if (leaf == "trim.txt") {
                                    part.trimData.setTo((char const*)map->getDataPtr(),
                                                        map->getDataLength());
                                } else {
                                    Animation::Frame frame;
                                    frame.name = leaf;
                                    frame.map = map;
                                    frame.trimWidth = animation.width;
                                    frame.trimHeight = animation.height;
                                    frame.trimX = 0;
                                    frame.trimY = 0;
                                    part.frames.add(frame);
                                }
                            }
                        } else {
                            SLOGE("bootanimation.zip is compressed; must be only stored");
                        }
                    }
                }
            }
        }
    }

    // If there is trimData present, override the positioning defaults.
    for (Animation::Part& part : animation.parts) {
        const char* trimDataStr = part.trimData.string();
        for (size_t frameIdx = 0; frameIdx < part.frames.size(); frameIdx++) {
            const char* endl = strstr(trimDataStr, "\n");
            // No more trimData for this part.
            if (endl == nullptr) {
                break;
            }
            String8 line(trimDataStr, endl - trimDataStr);
            const char* lineStr = line.string();
            trimDataStr = ++endl;
            int width = 0, height = 0, x = 0, y = 0;
            if (sscanf(lineStr, "%dx%d+%d+%d", &width, &height, &x, &y) == 4) {
                Animation::Frame& frame(part.frames.editItemAt(frameIdx));
                frame.trimWidth = width;
                frame.trimHeight = height;
                frame.trimX = x;
                frame.trimY = y;
            } else {
                SLOGE("Error parsing trim.txt, line: %s", lineStr);
                break;
            }
        }
    }

    zip->endIteration(cookie);

    return true;
}

解析bootanimation.zip中的png或者音频文件audio.wav。

启动BootAnimation线程

readyToRun()

在前面讲到BootAnimation对象创建的时候讲到过,线程的创建先readyToRun()来执行一些Thread执行前的初始化工作。

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

status_t BootAnimation::readyToRun() {
    mAssets.addDefaultAssets();

    mDisplayToken = SurfaceComposerClient::getInternalDisplayToken();
    if (mDisplayToken == nullptr)
        return NAME_NOT_FOUND;

    DisplayMode displayMode;
    const status_t error =
            SurfaceComposerClient::getActiveDisplayMode(mDisplayToken, &displayMode);
    if (error != NO_ERROR)
        return error;

    mMaxWidth = android::base::GetIntProperty("ro.surface_flinger.max_graphics_width", 0);
    mMaxHeight = android::base::GetIntProperty("ro.surface_flinger.max_graphics_height", 0);
    ui::Size resolution = displayMode.resolution;
    resolution = limitSurfaceSize(resolution.width, resolution.height);
    // create the native surface
    sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
            resolution.getWidth(), resolution.getHeight(), PIXEL_FORMAT_RGB_565);

    SurfaceComposerClient::Transaction t;

    // this guest property specifies multi-display IDs to show the boot animation
    // multiple ids can be set with comma (,) as separator, for example:
    // setprop persist.boot.animation.displays 19260422155234049,19261083906282754
    Vector<PhysicalDisplayId> physicalDisplayIds;
    char displayValue[PROPERTY_VALUE_MAX] = "";
    property_get(DISPLAYS_PROP_NAME, displayValue, "");
    bool isValid = displayValue[0] != '\0';
    if (isValid) {
        char *p = displayValue;
        while (*p) {
            if (!isdigit(*p) && *p != ',') {
                isValid = false;
                break;
            }
            p ++;
        }
        if (!isValid)
            SLOGE("Invalid syntax for the value of system prop: %s", DISPLAYS_PROP_NAME);
    }
    if (isValid) {
        std::istringstream stream(displayValue);
        for (PhysicalDisplayId id; stream >> id.value; ) {
            physicalDisplayIds.add(id);
            if (stream.peek() == ',')
                stream.ignore();
        }

        // In the case of multi-display, boot animation shows on the specified displays
        // in addition to the primary display
        auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
        constexpr uint32_t LAYER_STACK = 0;
        for (auto id : physicalDisplayIds) {
            if (std::find(ids.begin(), ids.end(), id) != ids.end()) {
                sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(id);
                if (token != nullptr)
                    t.setDisplayLayerStack(token, LAYER_STACK);
            }
        }
        t.setLayerStack(control, LAYER_STACK);
    }

    t.setLayer(control, 0x40000000)
        .apply();

    sp<Surface> s = control->getSurface();

    // initialize opengl and egl
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    eglInitialize(display, nullptr, nullptr);
    EGLConfig config = getEglConfig(display);
    EGLSurface surface = eglCreateWindowSurface(display, config, s.get(), nullptr);
    EGLContext context = eglCreateContext(display, config, nullptr, nullptr);
    EGLint w, h;
    eglQuerySurface(display, surface, EGL_WIDTH, &w);
    eglQuerySurface(display, surface, EGL_HEIGHT, &h);

    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
        return NO_INIT;

    mDisplay = display;
    mContext = context;
    mSurface = surface;
    mWidth = w;
    mHeight = h;
    mFlingerSurfaceControl = control;
    mFlingerSurface = s;
    mTargetInset = -1;

    projectSceneToWindow();

    // Register a display event receiver
    mDisplayEventReceiver = std::make_unique<DisplayEventReceiver>();
    status_t status = mDisplayEventReceiver->initCheck();
    SLOGE_IF(status != NO_ERROR, "Initialization of DisplayEventReceiver failed with status: %d",
            status);
    mLooper->addFd(mDisplayEventReceiver->getFd(), 0, Looper::EVENT_INPUT,
            new DisplayEventCallback(this), nullptr);

    return NO_ERROR;
}
  • 获取到SurfaceComposer
  • 创建Surface
  • 初始化opengl 和 egl
  • 监听display事件

threadLoop()

这部分可以说是真正的动画入口,threadLoop函数中会调用到具体的动画实现方法。

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

bool BootAnimation::threadLoop() {
    bool result;
    // We have no bootanimation file, so we use the stock android logo
    // animation.
    if (mZipFileName.isEmpty()) {
        result = android();
    } else {
        result = 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;
}

如果线程起来后,mZipFileName还没初始化完成,则播放“原生动画”,否则就播放自定义的动画。 播放完成后,需要处理的资源释放与清理工作。

播放动画

原生动画

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

bool BootAnimation::android() {
    SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
            elapsedRealtime());
    initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
    initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");

    mCallbacks->init({});

    // clear screen
    glShadeModel(GL_FLAT);
    glDisable(GL_DITHER);
    glDisable(GL_SCISSOR_TEST);
    glClearColor(0,0,0,1);
    glClear(GL_COLOR_BUFFER_BIT);
    eglSwapBuffers(mDisplay, mSurface);

    glEnable(GL_TEXTURE_2D);
    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    // Blend state
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

    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);
        glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
        glDrawTexiOES(x,                 yc, 0, mAndroid[1].w, mAndroid[1].h);
        glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);

        glEnable(GL_BLEND);
        glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
        glDrawTexiOES(xc, yc, 0, 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());

    glDeleteTextures(1, &mAndroid[0].name);
    glDeleteTextures(1, &mAndroid[1].name);
    return false;
}

status_t BootAnimation::initTexture(Texture* texture, AssetManager& assets,
        const char* name) {
    Asset* asset = assets.open(name, Asset::ACCESS_BUFFER);
    if (asset == nullptr)
        return NO_INIT;

    AndroidBitmapInfo bitmapInfo;
    void* pixels = decodeImage(asset->getBuffer(false), asset->getLength(), &bitmapInfo);
    auto pixelDeleter = std::unique_ptr<void, decltype(free)*>{ pixels, free };

    asset->close();
    delete asset;

    if (!pixels) {
        return NO_INIT;
    }

    const int w = bitmapInfo.width;
    const int h = bitmapInfo.height;

    GLint crop[4] = { 0, h, w, -h };
    texture->w = w;
    texture->h = h;

    glGenTextures(1, &texture->name);
    glBindTexture(GL_TEXTURE_2D, texture->name);

    switch (bitmapInfo.format) {
        case ANDROID_BITMAP_FORMAT_A_8:
            glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA,
                    GL_UNSIGNED_BYTE, pixels);
            break;
        case ANDROID_BITMAP_FORMAT_RGBA_4444:
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
                    GL_UNSIGNED_SHORT_4_4_4_4, pixels);
            break;
        case ANDROID_BITMAP_FORMAT_RGBA_8888:
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
                    GL_UNSIGNED_BYTE, pixels);
            break;
        case ANDROID_BITMAP_FORMAT_RGB_565:
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB,
                    GL_UNSIGNED_SHORT_5_6_5, pixels);
            break;
        default:
            break;
    }

    glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    return NO_ERROR;
}

原生的开机动画位于frameworks/base/core/res/assets/images目录下,由android-logo-shine.png和android-logo-mask.png两张png图片组成。熟悉framework-res的同学都知道这两张图片其实是编译到了framework-res.apk中。initTexture()函数会根据这两张图片来分别创建两个纹理对象,并存储在Bootanimation类的成员变量数组mAndroid中。通过混合渲染这两个纹理对象,我们就可以得到一个开机动画,这是通过中间的while循环语句来实现的。

图片android-logo-mask.png用作动画前景,它是一个镂空的“ANDROID”图像。图片android-logo-shine.png用作动画背景,它的中间包含有一个高亮的呈45度角的条纹。在每一次循环中,图片android-logo-shine.png被划分成左右两部分内容来显示。左右两个部分的图像宽度随着时间的推移而此消彼长,这样就可以使得图片android-logo-shine.png中间高亮的条纹好像在移动一样。另一方面,在每一次循环中,图片android-logo-shine.png都作为一个整体来渲染,而且它的位置是恒定不变的。由于它是一个镂空的“ANDROID”图像,因此,我们就可以通过它的镂空来看到它背后的图片android-logo-shine.png的条纹一闪一闪地划过。

自定义动画

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

bool BootAnimation::movie() {
    if (mAnimation == nullptr) {
        mAnimation = loadAnimation(mZipFileName);
    }

    if (mAnimation == nullptr)
        return false;

    // mCallbacks->init() may get called recursively,
    // this loop is needed to get the same results
    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;
    }

    // Check if npot textures are supported
    mUseNpotTextures = false;
    String8 gl_extensions;
    const char* exts = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
    if (!exts) {
        glGetError();
    } else {
        gl_extensions.setTo(exts);
        if ((gl_extensions.find("GL_ARB_texture_non_power_of_two") != -1) ||
            (gl_extensions.find("GL_OES_texture_npot") != -1)) {
            mUseNpotTextures = true;
        }
    }

    // Blend required to draw time on top of animation frames.
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glShadeModel(GL_FLAT);
    glDisable(GL_DITHER);
    glDisable(GL_SCISSOR_TEST);
    glDisable(GL_BLEND);

    glBindTexture(GL_TEXTURE_2D, 0);
    glEnable(GL_TEXTURE_2D);
    glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    bool clockFontInitialized = false;
    if (mClockEnabled) {
        clockFontInitialized =
            (initFont(&mAnimation->clockFont, CLOCK_FONT_ASSET) == NO_ERROR);
        mClockEnabled = clockFontInitialized;
    }

    initFont(&mAnimation->progressFont, PROGRESS_FONT_ASSET);

    if (mClockEnabled && !updateIsTimeAccurate()) {
        mTimeCheckThread = new TimeCheckThread(this);
        mTimeCheckThread->run("BootAnimation::TimeCheckThread", PRIORITY_NORMAL);
    }

    playAnimation(*mAnimation);

    if (mTimeCheckThread != nullptr) {
        mTimeCheckThread->requestExit();
        mTimeCheckThread = nullptr;
    }

    if (clockFontInitialized) {
        glDeleteTextures(1, &mAnimation->clockFont.texture.name);
    }

    releaseAnimation(mAnimation);
    mAnimation = nullptr;

    return false;
}
  • 如果在onFirstRef()的流程里还没有加载好动画,则需要调用loadAnimation加载动画。(参考之前的流程)
  • 调用playAnimation()播放动画。
  • 动画播放结束后,释放资源。
http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

bool BootAnimation::playAnimation(const Animation& animation) {
    const size_t pcount = animation.parts.size();
    nsecs_t frameDuration = s2ns(1) / animation.fps;

    SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
            elapsedRealtime());

    int fadedFramesCount = 0;
    int lastDisplayedProgress = 0;
    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);

        // Handle animation package
        if (part.animation != nullptr) {
            playAnimation(*part.animation);
            if (exitPending())
                break;
            continue; //to next part
        }

        // process the part not only while the count allows but also if already fading
        for (int r=0 ; !part.count || r<part.count || fadedFramesCount > 0 ; r++) {
            if (shouldStopPlayingPart(part, fadedFramesCount, lastDisplayedProgress)) break;

            mCallbacks->playPart(i, part, r);

            glClearColor(
                    part.backgroundColor[0],
                    part.backgroundColor[1],
                    part.backgroundColor[2],
                    1.0f);

            // For the last animation, if we have progress indicator from
            // the system, display it.
            int currentProgress = android::base::GetIntProperty(PROGRESS_PROP_NAME, 0);
            bool displayProgress = animation.progressEnabled &&
                (i == (pcount -1)) && currentProgress != 0;

            for (size_t j=0 ; j<fcount ; j++) {
                if (shouldStopPlayingPart(part, fadedFramesCount, lastDisplayedProgress)) break;

                processDisplayEvents();

                const int animationX = (mWidth - animation.width) / 2;
                const int animationY = (mHeight - animation.height) / 2;

                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);
                        glBindTexture(GL_TEXTURE_2D, frame.tid);
                        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                        glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
                    }
                    int w, h;
                    initTexture(frame.map, &w, &h);
                }

                const int xc = animationX + frame.trimX;
                const int yc = animationY + frame.trimY;
                Region clearReg(Rect(mWidth, mHeight));
                clearReg.subtractSelf(Rect(xc, yc, xc+frame.trimWidth, yc+frame.trimHeight));
                if (!clearReg.isEmpty()) {
                    Region::const_iterator head(clearReg.begin());
                    Region::const_iterator tail(clearReg.end());
                    glEnable(GL_SCISSOR_TEST);
                    while (head != tail) {
                        const Rect& r2(*head++);
                        glScissor(r2.left, mHeight - r2.bottom, r2.width(), r2.height());
                        glClear(GL_COLOR_BUFFER_BIT);
                    }
                    glDisable(GL_SCISSOR_TEST);
                }
                // specify the y center as ceiling((mHeight - frame.trimHeight) / 2)
                // which is equivalent to mHeight - (yc + frame.trimHeight)
                const int frameDrawY = mHeight - (yc + frame.trimHeight);
                glDrawTexiOES(xc, frameDrawY, 0, frame.trimWidth, frame.trimHeight);

                // if the part hasn't been stopped yet then continue fading if necessary
                if (exitPending() && part.hasFadingPhase()) {
                    fadeFrame(xc, frameDrawY, frame.trimWidth, frame.trimHeight, part,
                              ++fadedFramesCount);
                    if (fadedFramesCount >= part.framesToFadeCount) {
                        fadedFramesCount = MAX_FADED_FRAMES_COUNT; // no more fading
                    }
                }

                if (mClockEnabled && mTimeIsAccurate && validClock(part)) {
                    drawClock(animation.clockFont, part.clockPosX, part.clockPosY);
                }

                if (displayProgress) {
                    int newProgress = android::base::GetIntProperty(PROGRESS_PROP_NAME, 0);
                    // In case the new progress jumped suddenly, still show an
                    // increment of 1.
                    if (lastDisplayedProgress != 100) {
                      // Artificially sleep 1/10th a second to slow down the animation.
                      usleep(100000);
                      if (lastDisplayedProgress < newProgress) {
                        lastDisplayedProgress++;
                      }
                    }
                    // Put the progress percentage right below the animation.
                    int posY = animation.height / 3;
                    int posX = TEXT_CENTER_VALUE;
                    drawProgress(lastDisplayedProgress, animation.progressFont, posX, posY);
                }

                handleViewport(frameDuration);

                eglSwapBuffers(mDisplay, mSurface);

                nsecs_t now = systemTime();
                nsecs_t delay = frameDuration - (now - lastFrame);
                //SLOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay));
                lastFrame = now;

                if (delay > 0) {
                    struct timespec spec;
                    spec.tv_sec  = (now + delay) / 1000000000;
                    spec.tv_nsec = (now + delay) % 1000000000;
                    int err;
                    do {
                        err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, nullptr);
                    } while (err<0 && errno == EINTR);
                }

                checkExit();
            }

            usleep(part.pause * ns2us(frameDuration));

            if (exitPending() && !part.count && mCurrentInset >= mTargetInset &&
                !part.hasFadingPhase()) {
                if (lastDisplayedProgress != 0 && lastDisplayedProgress != 100) {
                    android::base::SetProperty(PROGRESS_PROP_NAME, "100");
                    continue;
                }
                break; // exit the infinite non-fading part when it has been played at least once
            }
        }
    }

    // Free textures created for looping parts now that the animation is done.
    for (const Animation::Part& part : animation.parts) {
        if (part.count != 1) {
            const size_t fcount = part.frames.size();
            for (size_t j = 0; j < fcount; j++) {
                const Animation::Frame& frame(part.frames[j]);
                glDeleteTextures(1, &frame.tid);
            }
        }
    }

    return true;
}
  • const size_t pcount = animation.parts.size(); 也就是desc.txt里播放片段的数量;所以for (size_t i=0 ; i<pcount ; i++) 循环是有多少个播放片段就循环多少次。
  • 在preloadZip()函数里会把每一个播放片段包含的帧数存在Part.frames,如:part.frames.add(frame);所以for (size_t j=0 ; j<fcount ; j++) 循环是当前播放有多少张图片就循环多少次。
  • 调用shouldStopPlayingPart()判断是否要退出动画。(前面聊标识符和动画淡出帧的时候有介绍)
  • 调用initTexture()显示
  • drawClock()显示时间
  • drawProgress()是否显示进度(Android12.0新增功能)
  • checkExit()检查是否退出动画

退出动画

http://aospxref.com/android-12.0.0_r3/xref/frameworks/base/cmds/bootanimation/BootAnimation.cpp

static const char 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();
    }
}

检测service.bootanim.exit的值,当属性值为1的时候,开机动画会requestExit(),从而结束开机动画。这个属性值的更改主要涉及以下内容:

总结

BootAnimation.cpp方法主要作用如下:

  • onFirstRef() : 建立BootAnimation进程与surfaceFlinger进程的通信,及加载资源
  • preloadAnimation() : 加载开机动画资源文件
  • findBootAnimationFile() : 主要是初始化 mZipFileName
  • findBootAnimationFileInternal() : 将mZipFileName存入索引
  • loadAnimation() : 解析资源,加载动画文件,这里的mZipFileName就是在readyToRun中获取的动画文件位置
  • parseAnimationDesc() : 解析读取desc.txt文件,设置相应animation参数
  • parseColor() : 解析颜色
  • parseTextCoord() : 解析文本坐标
  • parsePosition() : 解析位置
  • readFile() : 读取文件
  • preloadZip() : 用于图像的预加载阶段
  • decodeImage() : 解析图像信息,并存储
  • readyToRun() : 判断开机动画的压缩包是否存在,主要是对opengl工作环境进行初始化,初始化EGL环境,为送图做准备工作,做一个动画播放的预操作
  • threadLoop() : 显示开机画面,每个线程类都要实现的,在这里定义thread的执行内容。这个函数如果返回true,且没有调用requestExit(),则该函数会再次执行;如果返回false,则threadloop中的内容仅仅执行一次,线程就会退出。
  • doThreadLoop() : 超时检测线程的执行函数
  • playAnimation() : 会拿到 mAnimation的图片,还有desc.txt中定义的图片分辨率,帧率等信息,依次播放part0,part1中图片,合成Surface,然后调用eglSwapBuffers(mDisplay, mSurface);动图给显示设备.
  • movie() : 自定义的开机动画
  • getEglConfig() : 绘制目标framebuffer的配置属性及显示窗口内容
  • limitSurfaceSize() : 该方法的作用是将width和height限制在设备GPU支持的范围内
  • resizeSurface() : 调整开机动画的surface大小
  • releaseAnimation() : 释放动画资源
  • initFont() : 加载字体资源
  • initTexture() : 加载系统默认UI资源,通过decodeImage来解码图片,并显示在SurfaceLayer之上
  • drawClock() : 绘制时钟进行当前时间的显示
  • updateIsTimeAccurate() : 记录最新修改的时间
  • addTimeDirWatch() : 增加时间监测
  • handleEvent() : handle事件,更新UI操作信息
  • handleViewport() : 负责图表视图中可见的内容
  • processDisplayEvents() : 处理显示事件
  • checkExit() : 检测开机动画是否停止
  • binderDied() : 当Binder机制的客户端死掉,导致了该Binder结束,会回调此方法(此处一般指surfaceflinger)
  • DisplayEventCallback : 进行事件的处理以及调用surfaceComposerClient里的getPhysicalDisplayToken()进行物理屏幕的显示
  • TimeCheckThread : 超时检测机制线程

整个BootAnimation程序的流程图下图:

评论