AOSP开机动画调测技术点(基于Android13)

AOSP开机动画调测技术点(基于Android13)

开机动画替换

  1. 首先,在你的计算机上创建一个名为"bootanimation"的文件夹,并将"part0"、"part1"和"desc.txt"这三个文件复制到该文件夹中。这些文件包含了开机动画的图像帧和描述信息。

  2. 在命令行中切换到bootanimation文件夹,并执行以下命令将文件夹中的内容打包成一个名为"bootanimation.zip"的压缩文件:

bash 复制代码
zip -r -0 bootanimation.zip part0 part1 desc.txt

这个命令的含义是将当前目录下的"part0"、"part1"和"desc.txt"三个文件打包成一个名为"bootanimation.zip"的压缩文件。

  • -r:表示递归地将指定目录下的所有文件和子目录都包含在压缩文件中。
  • -0:表示使用不进行压缩的存储模式,即不对文件进行压缩处理,直接存储到压缩包中。
  1. 使用ADB(Android Debug Bridge)将生成的bootanimation.zip文件复制到设备的/system/media/目录中。请注意,这一步需要设备具有root权限以及重新挂载/system分区。你可以使用以下命令完成这一步骤:
bash 复制代码
adb root
adb remount
adb push bootanimation.zip /system/media/
  1. 执行以下命令来启动新的开机动画:
bash 复制代码
   adb shell setprop service.bootanim.exit 0
   adb shell setprop ctl.start bootanim
  1. 现在你可以观察到新的开机动画效果。如果想退出新的开机动画,执行以下命令:
bash 复制代码
   adb shell setprop service.bootanim.exit 1

Android开机动画(desc.txt)格式

开机动画分为2个阶段的图片资源加载,part0和part2, 加载规则在desc.txt文件中进行描述

bash 复制代码
desc.txt  part0  part1

part0与part1中的图片需要按照数字大小顺序标记。

desc.txt内容如下:

通用参数

第一行定义了动画的一般参数:

bash 复制代码
WIDTH HEIGHT FPS [PROGRESS]
  • WIDTH: 动画的宽度(像素)
  • HEIGHT: 动画的高度(像素)
  • FPS: 每秒的帧数,例如60
  • PROGRESS: 是否在最后一部分显示进度百分比
    • 百分比将以"x"坐标为基准,在动画高度的1/3处显示。

动态着色属性

如果使用动态着色功能,则提供一个可选的行来指定动态着色属性的格式。如果不使用动态着色,则可以跳过此行。

动画部分

接下来,根据以下格式提供多行来定义动画的各个部分:

bash 复制代码
TYPE COUNT PAUSE PATH [FADE [#RGBHEX [CLOCK1 [CLOCK2]]]]
  • TYPE: 单个字符,表示此动画段的类型:
    • p -- 该部分会播放,除非在启动结束之前被中断
    • c -- 该部分将播放到完成,无论如何
    • f -- 与 p 类似,但在继续播放的同时,指定的帧数正在淡出。只有第一个被中断的 f 部分会被淡出,其他后续的 f 部分会被跳过。
  • COUNT: 播放动画的次数,或者为0以无限循环,直到启动完成
  • PAUSE: 此部分结束后延迟的帧数
  • PATH: 包含此部分帧图片的目录(例如 part0
  • FADE: (仅适用于 f 类型) 被中断时要淡出的帧数,其中 0 表示立即淡出,使 f ... 0 的行为类似于 p,并且不将其视为淡出部分
  • RGBHEX: (可选) 背景颜色,表示为 #RRGGBB
  • CLOCK1, CLOCK2: (可选) 绘制当前时间(用于手表)的坐标:
    • 如果只提供 CLOCK1,则它是时钟的y坐标,x坐标默认为 c
    • 如果同时提供 CLOCK1CLOCK2,则 CLOCK1 是x坐标,CLOCK2 是y坐标
    • 值可以是正整数、负整数或 c
      • c -- 将文本居中
      • n -- 将文本定位到距起始位置的n像素处;在x轴上为左边缘,在y轴上为底部边缘
      • -n -- 将文本定位到距结束位置的n像素处;在x轴上为右边缘,在y轴上为顶部边缘
    • 示例:
      • -24c -24 将文本定位到距离屏幕顶部24像素处,水平居中
      • 16 c 将文本定位到距离屏幕左侧16像素处,垂直居中
      • -32 32 将文本定位到距离屏幕边缘向上32像素,向左32像素的位置

此外,还有一个特殊的类型 $SYSTEM,它加载并播放 /system/media/bootanimation.zip

以上是关于Android开机动画的desc.txt配置文件的格式说明。该文件定义了动画的属性和各个部分的行为。

源码分析

frameworks/base/cmds/bootanimation

bash 复制代码
.
├── Android.bp
├── audioplay.cpp
├── audioplay.h
├── BootAnimation.cpp
├── BootAnimation.h
├── bootanimation_main.cpp
├── BootAnimationUtil.cpp
├── BootAnimationUtil.h
├── bootanim.rc
├── FORMAT.md
└── OWNERS

定义服务

开启动画启动规则定义在bootanim.rc

bash 复制代码
service bootanim /system/bin/bootanimation
    class core animation
    user graphics
    group graphics audio
    disabled
    oneshot
    ioprio rt 0
    task_profiles MaxPerformance

开机动画的服务配置文件字段解释如下:

  • service: 指明服务的名称为"bootanim",即开机动画服务。
  • /system/bin/bootanimation: 指定了开机动画的执行文件路径为"/system/bin/bootanimation"。
  • class core animation: 表示此服务属于核心服务,并且是与动画相关的服务。
  • user graphics: 指定服务运行的用户为"graphics"。
  • group graphics audio: 指定服务运行的组为"graphics audio",表示具有这两个组的权限。
  • disabled: 表示此服务当前处于禁用状态,不会自动启动。
  • oneshot: 表示此服务只执行一次,完成任务后即退出。
  • ioprio rt 0: 设置了I/O调度优先级。
  • task_profiles MaxPerformance: 指定了任务的性能规格为最大性能。

根据你提供的信息,开机动画服务当前处于禁用状态,不会自动启动。

启动开机动画

开机动画在SurfaceFlinger初始化完成之后播放

cpp 复制代码
//frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::init() {
	......
	......
	//启动mStartPropertySetThread线程播放开机动画
	mStartPropertySetThread = getFactory().createStartPropertySetThread(presentFenceReliable);

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

那么看看StartPropertySetThread线程中具体如何运行

cpp 复制代码
//frameworks/native/services/surfaceflinger/StartPropertySetThread.cpp
bool StartPropertySetThread::threadLoop() {
    // Set property service.sf.present_timestamp, consumer need check its readiness
    property_set(kTimestampProperty, mTimestampPropertyValue ? "1" : "0");
    // Clear BootAnimation exit flag
    property_set("service.bootanim.exit", "0");
    property_set("service.bootanim.progress", "0");
    // Start BootAnimation if not started
    property_set("ctl.start", "bootanim");
    // Exit immediately
    return false;
}

StartPropertySetThread中通过设置属性方式启动动画播放。

所以如果需要手动运行bootanimation, 需要通过下面命令完成:

bash 复制代码
setprop service.bootanim.exit 0
setprop ctl.start bootanim

接下来我们进入bootanimation实现的代码分析动画播放的具体流程。

动画播放流程

  1. 首先从main函数分析,开机动画运行在一个线程中
cpp 复制代码
//frameworks/base/cmds/bootanimation/bootanimation_main.cpp
int main()
{
sp<BootAnimation> boot = new BootAnimation(audioplay::createAnimationCallbacks());
waitForSurfaceFlinger();
boot->run("BootAnimation", PRIORITY_DISPLAY);
}
  1. 找寻开机动画文件
cpp 复制代码
//frameworks/base/cmds/bootanimation/BootAnimation.cpp
void BootAnimation::onFirstRef() {
	......
	preloadAnimation();
	......
}
bool BootAnimation::preloadAnimation() {
	  //查找开机动画
	  findBootAnimationFile();
    if (!mZipFileName.isEmpty()) {
    	//加载开机动画
        mAnimation = loadAnimation(mZipFileName);
        return (mAnimation != nullptr);
    }
    return false;
}
//找寻开机动画文件主要流程即在该函数中完成
void BootAnimation::findBootAnimationFile() {
	......
	//加密设备开机动画路径
	static const std::vector<std::string> encryptedBootFiles = {
            PRODUCT_ENCRYPTED_BOOTANIMATION_FILE, SYSTEM_ENCRYPTED_BOOTANIMATION_FILE,
        };
    // 启动动画路径
    static const std::vector<std::string> bootFiles = {
        APEX_BOOTANIMATION_FILE, playDarkAnim ? PRODUCT_BOOTANIMATION_DARK_FILE : PRODUCT_BOOTANIMATION_FILE,
        OEM_BOOTANIMATION_FILE, SYSTEM_BOOTANIMATION_FILE
    };   
    ......    
}
  1. 加载开机动画文件并解析desc文件
cpp 复制代码
//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;
    }
    ZipFileRO *zip = ZipFileRO::open(fn);
    if (zip == nullptr) {
        SLOGE("Failed to open animation zip \"%s\": %s",
            fn.string(), strerror(errno));
        return nullptr;
    }
    ALOGD("%s is loaded successfully", fn.string());
	//解析bootanimation.zip并填充Animation
    Animation *animation =  new Animation;
    animation->fileName = fn;
    animation->zip = zip;
    animation->clockFont.map = nullptr;
    mLoadedFiles.add(animation->fileName);
	//解析desc.txt文件并填充Animation对象
    parseAnimationDesc(*animation);
    if (!preloadZip(*animation)) {
        releaseAnimation(animation);
        return nullptr;
    }
    mLoadedFiles.remove(fn);
    return animation;
}
  1. 初始化显示参数
cpp 复制代码
//frameworks/base/cmds/bootanimation/BootAnimation.cpp
status_t BootAnimation::readyToRun() {

//设置分辨率、创建Surface等
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);

}

// initialize opengl and egl
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    eglInitialize(display, nullptr, nullptr);
    
  1. 进入最终播放流程
cpp 复制代码
//frameworks/base/cmds/bootanimation/BootAnimation.cpp
bool BootAnimation::threadLoop() {
    bool result;
    initShaders();
    // 初始化着色器

    // 为启动视频功能进行初始化
    mStartbootanimaTime = 0;
    mBootVideoTime = -1;
	//Android还支持播放视频文件,这样可以方便广告植入,开展开机广告业务
    if (mVideoAnimation) {
        result = video();
    } else {
        // 如果没有启动动画文件,那么使用默认的安卓logo动画。
        if (mZipFileName.isEmpty()) {
            ALOGD("No animation file");
            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;
}

bool BootAnimation::movie() {
    if (mAnimation == nullptr) {
        mAnimation = loadAnimation(mZipFileName);
    }
	......
	 playAnimation(*mAnimation);
	......
	releaseAnimation(mAnimation);
	......
}

bool BootAnimation::playAnimation(const Animation& animation) {
    const size_t pcount = animation.parts.size();
    nsecs_t frameDuration = s2ns(1) / animation.fps;
    for (size_t i=0 ; i<pcount ; i++) {
        const Animation::Part& part(animation.parts[i]);
        const size_t fcount = part.frames.size();

        // Handle animation package
        if (part.animation != nullptr) {
            playAnimation(*part.animation);
            if (exitPending())
                break;
            continue; //to next part
        }
        //
        /
        //使用gl绘制每一帧的图像
        //第2轮及以后的播放
         if (r > 0) {
                    glBindTexture(GL_TEXTURE_2D, frame.tid);
                } else {
                //第一轮播放需要初始化
                    glGenTextures(1, &frame.tid);
                    glBindTexture(GL_TEXTURE_2D, frame.tid);
                    int w, h;
                    // Set decoding option to alpha unpremultiplied so that the R, G, B channels
                    // of transparent pixels are preserved.
                    initTexture(frame.map, &w, &h, false /* don't premultiply alpha */);
                }
       //
       //
   }
}
    

动画退出时机

java 复制代码
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
private void performEnableScreen() {
	
	///
	/
	if (!mBootAnimationStopped) {
                Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "Stop bootanim", 0);
                // stop boot animation
                // formerly we would just kill the process, but we now ask it to exit so it
                // can choose where to stop the animation.
                SystemProperties.set("service.bootanim.exit", "1");
                mBootAnimationStopped = true;
            }
	
	///
}
相关推荐
阿巴斯甜19 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker20 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952721 小时前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android