制作开机动画两个要点
- 压缩时选择“存储”模式
- 资源文件命名序号,需要和最大序号位数相同,位数不够,前面补零。如00、01、02、。。。、10、11。
- 系统开机动画支持功能
// 注意:不同的android版本,配置方法可能不同,该配置是Android6.0的方法
配置debug.sf.nobootanimation 为0
若要关闭开机动画功能,在device目录下的mk文件中配置,确保系统开机默认值为1;若要支持动画,不用配置,默认为0
启动开机动画
- 定义服务
开机动画在init.rc中定义为native service,如
service shutdownanim /system/bin/bootanimation /system/media/shutdownanimation.zip
class core
user graphics
group graphics audio media
disabled
oneshot
- 开机init进程注册服务
此时服务只是被注册,并没有实际运行 - 系统启动开机动画
在frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp中,surfaceflinger初始化时执行startBootAnim()
void SurfaceFlinger::init() {
ALOGI( "SurfaceFlinger's main thread ready to run. "
"Initializing graphics H/W...");
{ // Autolock scope
Mutex::Autolock _l(mStateLock);
// initialize EGL for the default display
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(mEGLDisplay, NULL, NULL);
......
// initialize our drawing state
mDrawingState = mCurrentState;
// set initial conditions (e.g. unblank default device)
initializeDisplays();
mRenderEngine->primeCache();
// start boot animation
startBootAnim();
ALOGV("Done initializing");
}
startBootAnim()函数实现
void SurfaceFlinger::startBootAnim() {
// start boot animation
property_set("service.bootanim.exit", "0");
property_set("ctl.start", "bootanim");
}
从实现看出,在系统起来后,若要运行./bootanimtion,需要先把service.bootanim.exit的值设置为0,然后通过ctl.start启动。命令如下:
setprop service.bootanim.exit 0
setprop ctl.start bootanim
开机动画运行过程
代码位置
frameworks/base/cmds/bootanimation
bootanimation/
├── Android.mk 编译脚本
├── audioplay.cpp 音频播放
├── AudioPlayer.cpp 播放器
├── AudioPlayer.h
├── audioplay.h
├── BootAnimation.cpp 开机动画播放主流程,重点关注
├── BootAnimation.h
├── bootanimation_main.cpp init启动过程注册服务文件
├── bootanim.rc Android新版本,服务启动方式
└── FORMAT.md 说明文档,markdown格式
运行简介
主要介绍开机动画解析播放流程,不做细节说明,文件是BootAnimation.cpp
1、初始化
status_t BootAnimation::readyToRun() {
- 初始化显示功能
- 确定bootanimation.zip文件位置,此处客制化较多。部分代码如下:
ZipFileRO* zipFile = NULL;
if ((encryptedAnimation &&
(access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE)) != NULL)) ||
((access(OEM_BOOTANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(OEM_BOOTANIMATION_FILE)) != NULL)) ||
((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(SYSTEM_BOOTANIMATION_FILE)) != NULL))) {
mZip = zipFile;
}
2、播放线程
bool BootAnimation::threadLoop()
{
bool r;
// We have no bootanimation file, so we use the stock android logo
// animation.
playMusic();
if (mZip == NULL) {
r = android();
} else {
r = movie();
}
eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
eglDestroyContext(mDisplay, mContext);
eglDestroySurface(mDisplay, mSurface);
mFlingerSurface.clear();
mFlingerSurfaceControl.clear();
eglTerminate(mDisplay);
IPCThreadState::self()->stopProcess();
return r;
}
如果没有找到开机动画文件,默认播放Android默认字样,执行android();
3、movie实现
保存开机动画文件数据的结构体,BootAnimation.h中定义
struct Animation {
struct Frame {
String8 name;
FileMap* map;
mutable GLuint tid;
bool operator < (const Frame& rhs) const {
return name < rhs.name;
}
};
struct Part {
int count;
int pause;
String8 path;
SortedVector<Frame> frames;
bool playUntilComplete;
float backgroundColor[3];
FileMap* audioFile;
};
int fps;
int width;
int height;
Vector<Part> parts;
};
- 解析desc.txt文件
获取播放配置,初始化Animation变量。注意,Animation是开机动画中定义的结构体,是对开机动画文件解析后数据存储对象。
if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {
// ALOGD("> 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", &pathType, &count, &pause, path, color) >= 4) {
// ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s", pathType, count, pause, path, color);
// 动画中可以有多个播放文件夹,一个文件夹一个播放规则,结构体中保存多个part,记录不同规则
Animation::Part part;
part.playUntilComplete = pathType == 'c';
part.count = count; // 播放次数,0 循环
part.pause = pause; // 没播放完一周期,暂停时间,循环播放有效
part.path = path; // 该值是该part的文件夹名
part.audioFile = NULL; // 播放音频的文件
if (!parseColor(color, part.backgroundColor)) { // 检查颜色配置,没有设置,默认是00000
ALOGE("> invalid color '#%s'", color);
part.backgroundColor[0] = 0.0f;
part.backgroundColor[1] = 0.0f;
part.backgroundColor[2] = 0.0f;
}
animation.parts.add(part); // 保存一个播放规则的part
- 录入图片数据
Animation::Part& part(animation.parts.editItemAt(j)); // 获取一个part
if (leaf == "audio.wav") {
// a part may have at most one audio file
part.audioFile = map;
} else {
// 一个part中保存一个frame容器,播放文件在容器中按照文件名自动排序,因此必须留意文件名命令
Animation::Frame frame;
frame.name = leaf;
frame.map = map;
// add的同时和已经保存的文件比较,插入正确位置,保证后面有序播放
part.frames.add(frame);
}
- 播放
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);
}
initTexture(frame);
}
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 - animation.height) / 2)
// which is equivalent to mHeight - (yc + animation.height)
glDrawTexiOES(xc, mHeight - (yc + animation.height),
0, animation.width, animation.height);
eglSwapBuffers(mDisplay, mSurface);
nsecs_t now = systemTime();
nsecs_t delay = frameDuration - (now - lastFrame);
//ALOGD("%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, NULL);
} while (err<0 && errno == EINTR);
}
// 检查是否退出,在ActivityManagerService中通知退出
checkExit();
退出设置
base/services/core/java/com/android/server/am/ActivityManagerService.java:6992: SystemProperties.set("service.bootanim.exit", "1");
开机动画遇到的问题
- 动画播放文件乱序
原因: 文件命名错误,仅是简单的1、2、3、。。。,播放文件超过9,就会出错
解决方案: 参考“制作开机动画两个要点” - 开机动画前面几帧被遮住,没有看到被播放
原因: kernel logo播放完后,从kernel空间切换到用户空间,存在场景切换,会黑屏。亮屏条件是播放器检测到有帧数据送入,因此前面的帧数据会被遮住(遮住的帧数,因芯片解决方案不同而不同)。
解决方案: 确定具体平台会被隐藏的帧数,开始播放时,重复送入第一帧数据
Animation::Frame tmp_frame;
if(wait_count < WAIT_FRAME_COUNT && i == 0 && (j == 0 || j == 1)) {
j = 0;
}
// ALOGD("---------- i = %d j = %d wait_count = %d",i,j,wait_count);
// add end
const Animation::Frame& frame(part.frames[j]);
nsecs_t lastFrame = systemTime();
if (r > 0 || (wait_count > 0 && wait_count < WAIT_FRAME_COUNT)) {
// ALOGD("-------r------ wait_count: %d",wait_count);
glBindTexture(GL_TEXTURE_2D, frame.tid);
} else {
// ALOGD("------------- wait_count: %d",wait_count);
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);
}
initTexture(frame);
}
// add by icetech
// resolv bootanim can't display the first three frame
if(wait_count < WAIT_FRAME_COUNT) wait_count++;
// add end
3、相同的文件,压缩方式相同,一个可以播放一个不可以播放
desc.txt要使用文本文档编辑;使用notepad++编辑,无法播放(和配置可能相关)。