前面几篇讲解了开机动画的整体流程以及开机动画OpenGL绘制分析与实战整体的开机动画的原理差不多已经莫清楚了,zip的形式是目前的主流形式,详细讲解zip绘制原理.
1 如何使用zip
如何通过zip的形式绘制开机动画呢?一般我们都会从网络上搜索,但是如果没有网络如何去查找呢?
其实google提供的源码中已经写好了,进入到源码的bootanimation目录会发现里面有一个FORMAT.md这个文件就是告诉你如何通过zip去绘制开机动画.
这个文件的内容如下:里面已经告诉你zip这个文件放到哪里,以及zip文件中的内容布局等等.
## zipfile paths
The system selects a boot animation zipfile from the following locations, in order:
/system/media/bootanimation-encrypted.zip (if getprop("vold.decrypt") = '1')
/system/media/bootanimation.zip
/oem/media/bootanimation.zip
zipfile paths 这个就不用说了,这个是告诉你zip文件可以放到哪里,一般都会放到/system/media/中。
在来看zipfile layout 部分,这就是zip的资源文件,part0 .... partN 是图片帧,里面保存的就是一帧帧图片,desc.txt 是描述文件
## zipfile layout
The `bootanimation.zip` archive file includes:
desc.txt - a text file
part0 \
part1 \ directories full of PNG frames
... /
partN /
desc.txt 的描述格式,FPS 就是帧率,例如part0 里面有120张图片,例如1S 120fps,1S中就会执行120张帧图片
## desc.txt format
The first line defines the general parameters of the animation:
WIDTH HEIGHT FPS
* **WIDTH:** animation width (pixels) -- 宽
* **HEIGHT:** animation height (pixels) -- 高
* **FPS:** frames per second, e.g. 60 -- 帧率
It is followed by a number of rows of the form:
TYPE COUNT PAUSE PATH [#RGBHEX [CLOCK1 [CLOCK2]]]
* **TYPE:** a single char indicating what type of animation segment this is:
(除非在引导结束时中断,否则该部分将播放)
+ `p` -- this part will play unless interrupted by the end of the boot
+ `c` -- this part will play to completion, no matter what (无论如何,这部分都会发挥到极致 不会被打断)
COUNT : 0 表示一直循环,直到开机动画属性置位1 开机动画结束
* **COUNT:** how many times to play the animation, or 0 to loop forever until boot is complete
PAUSE :当一个part执行完毕后,停留几帧,1 表示 16ms,0 表示不停留
* **PAUSE:** number of FRAMES to delay after this part ends
* **PATH:** directory in which to find the frames for this part (e.g. `part0`)
* **RGBHEX:** _(OPTIONAL)_ a background color, specified as `#RRGGBB`
* **CLOCK1, CLOCK2:** _(OPTIONAL)_ the coordinates at which to draw the current time (for watches)
2 实现原理
在前两篇文章中分析了bootanimation的原理,在 开机动画的整体流程 中,可以指导当mZipFileName 不为空的时候执行moive方法
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);
...... opengl 初始化纹理
playAnimation(*mAnimation);
.......
releaseAnimation(mAnimation);
mAnimation = nullptr;
return false;
}
在上述代码中,会先执行loadAnimation方法,解析zip文件的内容:
如果看过该系列第一篇文章,你会发现BootAnimation::onFirstRef() 方法中,调用了preloadAnimation 并且找到zip文件,当mZipFileName不为空的时候调用了loadAnimation方法,并且mAnimation 赋值了,到执行到moive方法可以直接开始动画,这里做了一些小优化,继续看loadAnimation 如何解析zip文件的
- 打开zip压缩文件
- 构造Animation对象
parseAnimationDesc解析描述文件desc.txt,记录执行规则preloadZip将所有的图片加载进来
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 *animation = new Animation;
animation->fileName = fn;
animation->zip = zip;
animation->clockFont.map = nullptr;
mLoadedFiles.add(animation->fileName);
parseAnimationDesc(*animation);//解析描述文件desc.txt
if (!preloadZip(*animation)) {//加载所有的图片进来
return nullptr;
}
mLoadedFiles.remove(fn);
return animation;
}
如下parseAnimationDesc,解析后的文本按照行数进行解析,这部分代码很简单:
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;
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;
if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
// SLOGD("> w=%d, h=%d, fps=%d", width, height, fps);
animation.width = width;
animation.height = height;
animation.fps = fps;
} else if (sscanf(l, " %c %d %d %s #%6s %16s %16s",
&pathType, &count, &pause, path, color, clockPos1, clockPos2) >= 4) {
//SLOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s, clockPos1=%s, clockPos2=%s",
// pathType, count, pause, path, color, clockPos1, clockPos2);
Animation::Part part;
part.playUntilComplete = pathType == 'c';
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);
}
......
s = ++endl;
}
return true;
}
然后看preloadZip 核心代码:Animation 中有变量 Vector<Part> parts,用来存储每个part的图片帧,Part结构体中有SortedVector<Frame> frames; 每一帧的图片都会存储到frames中,同时也可以看到leaf == "audio.wav" 说明开机动画是支持音频的
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");
}
}
}
}
ok loadAnimation方法执行完毕,这时候zip的文件资源的信息已经全部知道了,继续分析movie方法,下面就是playAnimation 播放开机动画:
for (size_t i=0 ; i<pcount ; i++) {//part0 --- partN
const Animation::Part& part(animation.parts[i]);// 获取Part实例
const size_t fcount = part.frames.size();//获取图片的帧数
.....
//desc.txt COUNT如果配置为0 表示一直循环
for (int r=0 ; !part.count || r<part.count ; r++) {
......
//desc.txt RGBHEX配置,背景颜色
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();
//使用opengl 进行绘制
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);
}
.......
checkExit();//检查开机动画结束标记
}
usleep(part.pause * ns2us(frameDuration));//执行完一个part 停留的帧数
...
}
.....
}
3 实战自定义的zip开机动画
准备开机动画资源,编写desc.txt 文件:
1080 360 60 # 宽x高=1080x192, fps=60帧
c 1 0 part0 #ffee00 c c # part0 只播放1次,但需全部图片都播放,不能打断 最后两个c c 是坐标在中间的位置
c 0 0 part1 #ffee00 c c # part1 COUNT:0 只要开机动画结束属性不为1 这个part1就会一直循环播放
c 1 0 part2 #ffee00 c c # 背景颜色 #ffee00
c 1 1 part3 #ffee00 c c # 只播放1次,但需全部图片都播放,不能打断,在播放结束时延迟1帧,即1/fps=1/60秒
c 1 0 part4 #ffee00 c c
注意打包zip要使用存储方式命令如下:
zip -r -X -Z store bootanimation part*/* desc.txt
接着我们把 bootanimation.zip 动画文件预制到 /system/media/ 目录下(你需要添加自己的品牌设备添加Product):
把 bootanimation.zip 动画文件移动到 device/sufulu/su_os 文件中。
在我们的自定义 Product 配置文件 device/sufulu/su_os/su_os.mk 中添加如下内容
PRODUCT_ARTIFACT_PATH_REQUIREMENT_WHITELIST += \
root/init.zygote32_64.rc \
root/init.zygote64_32.rc \
/system/media/bootanimation.zip \
# Copy different zygote settings for vendor.img to select by setting property
# ro.zygote=zygote64_32 or ro.zygote=zygote32_64:
# 1. 64-bit primary, 32-bit secondary OR
# 2. 32-bit primary, 64-bit secondary
# init.zygote64_32.rc is in the core_64_bit.mk below
PRODUCT_COPY_FILES += \
system/core/rootdir/init.zygote32_64.rc:root/init.zygote32_64.rc \
$(LOCAL_PATH)/bootanimation.zip:/system/media/bootanimation.zip \
如果你使用的AOSP 8及以下的,只需要修改bootanimation/Android.mk
$(shell cp $(LOCAL_PATH)/bootanimation.zip $(ANDROID_PRODUCT_OUT)/system/media/bootanimation.zip)
回顾整个开机动画的流程如下:从系统启动→ init进程启动(属性系统监听) → sf进程启动 → bootanimation启动 → 播放那个开机动画 -> 开机动画绘制 → 何时结束开机动画的完整流程