AOSP开机动画调测技术点(基于Android13)
开机动画替换
-
首先,在你的计算机上创建一个名为"bootanimation"的文件夹,并将"part0"、"part1"和"desc.txt"这三个文件复制到该文件夹中。这些文件包含了开机动画的图像帧和描述信息。
-
在命令行中切换到bootanimation文件夹,并执行以下命令将文件夹中的内容打包成一个名为"bootanimation.zip"的压缩文件:
bash
zip -r -0 bootanimation.zip part0 part1 desc.txt
这个命令的含义是将当前目录下的"part0"、"part1"和"desc.txt"三个文件打包成一个名为"bootanimation.zip"的压缩文件。
-r
:表示递归地将指定目录下的所有文件和子目录都包含在压缩文件中。-0
:表示使用不进行压缩的存储模式,即不对文件进行压缩处理,直接存储到压缩包中。
- 使用ADB(Android Debug Bridge)将生成的bootanimation.zip文件复制到设备的/system/media/目录中。请注意,这一步需要设备具有root权限以及重新挂载/system分区。你可以使用以下命令完成这一步骤:
bash
adb root
adb remount
adb push bootanimation.zip /system/media/
- 执行以下命令来启动新的开机动画:
bash
adb shell setprop service.bootanim.exit 0
adb shell setprop ctl.start bootanim
- 现在你可以观察到新的开机动画效果。如果想退出新的开机动画,执行以下命令:
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
: 每秒的帧数,例如60PROGRESS
: 是否在最后一部分显示进度百分比- 百分比将以"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
- 如果同时提供
CLOCK1
和CLOCK2
,则CLOCK1
是x坐标,CLOCK2
是y坐标 - 值可以是正整数、负整数或
c
c
-- 将文本居中n
-- 将文本定位到距起始位置的n像素处;在x轴上为左边缘,在y轴上为底部边缘-n
-- 将文本定位到距结束位置的n像素处;在x轴上为右边缘,在y轴上为顶部边缘
- 示例:
-24
或c -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实现的代码分析动画播放的具体流程。
动画播放流程
- 首先从main函数分析,开机动画运行在一个线程中
cpp
//frameworks/base/cmds/bootanimation/bootanimation_main.cpp
int main()
{
sp<BootAnimation> boot = new BootAnimation(audioplay::createAnimationCallbacks());
waitForSurfaceFlinger();
boot->run("BootAnimation", PRIORITY_DISPLAY);
}
- 找寻开机动画文件
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
};
......
}
- 加载开机动画文件并解析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;
}
- 初始化显示参数
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);
- 进入最终播放流程
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;
}
///
}