哈喽大家好我是Zzz今天带来的是Framework开机动画绘制分析及实战,还是分析源码为主希望喜欢,环境基于aosp13_r6。

上编文章讲到bootanimaiton_main的main方法中会执行一个Thread线程Bootanimation.run(),会在Bootanimation.cpp中执行到threadLoop中这里会判断一个文件是否为空,如果为空就会执行默认的openGL进行绘制,不为空就执行zip动画。

我学习fm开发参考的是:千里马学框架-

一、openGL实现

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(); //openGL绘制
    } else {
        result = movie(); //zip绘制
    }

    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() {
    glActiveTexture(GL_TEXTURE0);

    SLOGD("%sAnimationShownTiming start time: %" PRId64 "ms", mShuttingDown ? "Shutdown" : "Boot",
            elapsedRealtime());
    initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");//初始化纹理,图片资源在frameworks/base/core/res/assets/images/下
    initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");

    mCallbacks->init({});

    // clear screen
    glDisable(GL_DITHER);
    glDisable(GL_SCISSOR_TEST);
    glUseProgram(mImageShader);

    glClearColor(0,0,0,1);
    glClear(GL_COLOR_BUFFER_BIT);
    eglSwapBuffers(mDisplay, mSurface);

    // Blend state
    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);
        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);//为了手机功耗这里设置为12帧
        if (sleepTime > 0)
            usleep(sleepTime);

        checkExit();
    } while (!exitPending());

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

接下来我们实战在开机动画中增加一个时间,在Bootanimation.cpp中本身有一个drawClock()方法,这个方式是用来获取时间的

//参数1:Font& font 字体类,这个类在Bootanimation.h的中 这个类中主要就是 纹理、宽、高属性的设置
void BootAnimation::drawClock(const Font& font, const int xPos, const int yPos) {
    static constexpr char TIME_FORMAT_12[] = "%l:%M";//12小时格式
    static constexpr char TIME_FORMAT_24[] = "%H:%M";//24小时格式
    static constexpr int TIME_LENGTH = 6;//长度

    time_t rawtime;
    time(&rawtime);
    struct tm* timeInfo = localtime(&rawtime);

    char timeBuff[TIME_LENGTH];
    const char* timeFormat = mTimeFormat12Hour ? TIME_FORMAT_12 : TIME_FORMAT_24;//构造时间的字符串
    size_t length = strftime(timeBuff, TIME_LENGTH, timeFormat, timeInfo);

    if (length != TIME_LENGTH - 1) {
        SLOGE("Couldn't format time; abandoning boot animation clock");
        mClockEnabled = false;
        return;
    }

    char* out = timeBuff[0] == ' ' ? &timeBuff[1] : &timeBuff[0];
    int x = xPos;
    int y = yPos;
    drawText(out, font, false, &x, &y);//绘制文字
}

查看drawText方法

void BootAnimation::drawText(const char* str, const Font& font, bool bold, int* x, int* y) {
    glEnable(GL_BLEND);  // Allow us to draw on top of the animation
    glBindTexture(GL_TEXTURE_2D, font.texture.name);//绑定纹理
    glUseProgram(mTextShader);
    glUniform1i(mTextTextureLocation, 0);

    const int len = strlen(str);
    const int strWidth = font.char_width * len;

    if (*x == TEXT_CENTER_VALUE) {
        *x = (mWidth - strWidth) / 2;
    } else if (*x < 0) {
        *x = mWidth + *x - strWidth;
    }
    if (*y == TEXT_CENTER_VALUE) {
        *y = (mHeight - font.char_height) / 2;
    } else if (*y < 0) {
        *y = mHeight + *y - font.char_height;
    }

    for (int i = 0; i < len; i++) {
        char c = str[i];

        if (c < FONT_BEGIN_CHAR || c > FONT_END_CHAR) {
            c = '?';
        }

        // Crop the texture to only the pixels in the current glyph
        const int charPos = (c - FONT_BEGIN_CHAR);  // Position in the list of valid characters
        const int row = charPos / FONT_NUM_COLS;
        const int col = charPos % FONT_NUM_COLS;
        // Bold fonts are expected in the second half of each row.
        float v0 = (row + (bold ? 0.5f : 0.0f)) / FONT_NUM_ROWS;
        float u0 = ((float)col) / FONT_NUM_COLS;
        float v1 = v0 + 1.0f / FONT_NUM_ROWS / 2;
        float u1 = u0 + 1.0f / FONT_NUM_COLS;
        glUniform4f(mTextCropAreaLocation, u0, v0, u1, v1);
        drawTexturedQuad(*x, *y, font.char_width, font.char_height);

        *x += font.char_width;
    }

    glDisable(GL_BLEND);  // Return to the animation's default behaviour
    glBindTexture(GL_TEXTURE_2D, 0);
}

有了上面介绍的前提知识,接下来我们开始进行绘制

1、调用drawClock首先我们初始化一个字体变量 在Bootanimation.h尾部增加

//新增一个字体变量
Font mClockFont;

2、在android()中增加字体初始化

bool BootAnimation::android() {
    glActiveTexture(GL_TEXTURE0);

    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({});
  //字体初始化开始
    bool hasInitFont = false;//这里标记一下纹理是否初始化成功
  //mClockFont 初始化字体,CLOCK_FONT_ASSET 文件名 这个文件是数字1234567890的图片,由于在openGL中不支持直接输入字符,只支持一张张的图片纹理
    if(initFont(&mClockFont,CLOCK_FONT_ASSET) == NO_ERROR){
        hasInitFont = true;
        ALOGD("android init font ok,fontname= %u",&mClockFont.texture.name);
    }
  //字体初始化结束
......

 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*2);//裁剪区域增大
            glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
                      updateRect.height()*2);//裁剪区域增大
		//在循环中增加字体绘制,第一个参数字体,第二个参数文字位置,第三个高度
        drawClock(mClockFont,TEXT_CENTER_VALUE,yc+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);
	//如果初始化成功最后删除纹理,否则可能会内存泄漏
    if(hasInitFont){
        glDeleteTextures(1, &mClockFont.texture.name);
    }

}

二、zip开机动画实现(bootanimation.zip 存放路径在:/system/media/bootanimation.zip)

1.准备开机动画文件***不要超过5m

2.配置描述文件

1080:代表绘制的宽

360:代表绘制的高

60:代表帧率

TYPE COUNT PAUSE PATH [FADE [#RGBHEX [CLOCK1 [CLOCK2]]]]

c:代表Type这里的type有两种 一种是c只有在启动结束的时候会打断 p:不能被打断

1:count 循环几次,1是一次,0是无限次

0:PAUSE停留几帧0不停留

part0:压缩包中文件名称这里面放置的是每一帧的图片可以叫任意名称但最好用成part0

#ffee00:背景颜色(非必填实际项目一般用不到)

c:坐标X(非必填实际项目一般用不到)

c:坐标Y(非必填实际项目一般用不到)

1080 360 60
c 1 0 part0 #ffee00 c c
c 0 0 part1 #ffee00 c c
c 1 0 part2 #ffee00 c c
c 1 1 part3 #ffee00 c c
c 1 0 part4 #ffee00 c c

为什么必须要按照这种形式去做,在frameworks/base/cmds/FORMAT.md中有说明

The `bootanimation.zip` archive file includes:

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

## desc.txt format
....

3.生成bootanimation.zip以存储的方式

使用命令:zip -r -X -Z store bootanimation part*/* desc.txt

4.aosp13预置到/system/media/bootanimation.zip

4.1、在build/envsetup.sh中增加

# defined in core/config.mk
local targetgccversion=$(get_build_var TARGET_GCC_VERSION)
local targetgccversion2=$(get_build_var 2ND_TARGET_GCC_VERSION)
export TARGET_GCC_VERSION=$targetgccversion
export DISABLE_ARTIFACT_PATH_REQUIREMENTS="true"//增加这个

4.2、在build/target/product/handheld_system_ext.mk 中增加

$(call inherit-product, $(SRC_TARGET_DIR)/product/media_system_ext.mk)
//增加copy
PRODUCT_COPY_FILES+=\
packages/services/Car/car_product/car_ui_portrait/bootanimation/bootanimation.zip:system/media/bootanimation.zip

4.3、Bootanimation.zip放到 packages/services/Car/car_product/car_ui_portrait/bootanimation/

最后在整体make就好了