1.简介.
Android 开机动画,老罗里面流程狠清楚了,这只是记录bootanimation相关。
2.制作bootanimation.zip
2.1bootanimation.zip的文件目录:
bootanimation.zip
---- fd0 (文件夹名可自定义)
----------xx.png (数目可多)
........ (可放多个文件)
desc.txt (描述文件)
2.2 desc.txt文件解析
第一行:显示图片的分辨率宽度 高度 帧速(fps)
第二行:p 播放次数(0:表示循环) 在两次循环显示之间的时间间隔(单位:1帧的显示时间) 图片所在文件夹名
....... (如第二行一样,对含有图片的文件夹进行配置)
例子:若bootanimatiom.zip 中 若文件夹fd0中有5张有效图片,fd1中有1张有效图片,desc.txt文件如下配置:
800 480 2
p 1 5 fd0
p 1 5 fd1
p 0 0 fd2
其中 "800 480 2"表示显示图片为 800 * 480 ,帧率为2(即:每张图片显示0.5s),"p 1 5 fd0"表示播放一遍,循环的间隔为 5*1/1(这里只播放一遍,则会停留在最
后一张图片的时间),显示的图片为文件夹fd0中的图片。
2.3压缩文件
注意:压缩的格式为ZIP,方式:存储,在window压缩的时候,需注意压缩后文件时候,压缩包不能含有中间路径(即:压缩的时候,不能包含bootanimatiom这个文件名)
压缩完成后,需要检查。
2.4push 到手机,
push到手机的路径为"/oem/media/bootanimation.zip"或者"/system/media/bootanimation.zip" 源码中定义代码路径:
/frameworks/base/cmds/bootanimation/BootAnimation.cpp
[cpp] view plain copy
#define OEM_BOOTANIMATION_FILE "/oem/media/bootanimation.zip"
#define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip"
2.5在android5.0以后还有audio_conf.txt可以配置audio播放相关内容内容,我还没有配置成功过,(待续)
3.代码流程:
部分内容转自:也借鉴的内容
3.1bootanimation的启动
其中bootanimation的中的配置为disabled,则表明不自启动,其中启动的地方为frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::startBootAnim() {
// start boot animation
property_set("service.bootanim.exit", "0"); ////=1退出动画
property_set("ctl.start", "bootanim");
}
3.2
frameworks/base/cmds/bootanimation/bootanimation_main.cpp
[cpp] view plain
int main(int argc, char** argv)
{
#if defined(HAVE_PTHREADS)
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
#endif
char value[PROPERTY_VALUE_MAX];
property_get("debug.sf.nobootanimation", value, "0");
int noBootAnimation = atoi(value);
ALOGI_IF(noBootAnimation, "boot animation disabled");
if (!noBootAnimation) {
sp<ProcessState> proc(ProcessState::self());
ProcessState::self()->startThreadPool();
// create the boot animation object
sp<BootAnimation> boot = new BootAnimation();
IPCThreadState::self()->joinThreadPool();
}
return 0;
}
这个函数首先检查系统属性“debug.sf.nobootnimaition”的值是否不等于0。如果不等于的话,那么接下来就会启动一个Binder线程池,并且创建一个BootAnimation对象。这个BootAnimation对象就是用来显示第三个开机画面的。由于BootAnimation对象在显示第三个开机画面的过程中,需要与SurfaceFlinger服务通信,因此,应用程序bootanimation就需要启动一个Binder线程池。
BootAnimation类间接地继承了RefBase类,并且重写了RefBase类的成员函数onFirstRef,因此,当一个BootAnimation对象第一次被智能指针引用的时,这个BootAnimation对象的成员函数onFirstRef就会被调用。
ootAnimation类的成员函数onFirstRef实现在文件frameworks/base/cmds/bootanimation/BootAnimation.cpp中,如下所示:
[cpp] view plain copy
1. void BootAnimation::onFirstRef() {
2. status_t err = mSession->linkToComposerDeath(this);
3. ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
4. if (err == NO_ERROR) {
5. run("BootAnimation", PRIORITY_DISPLAY);
6. }
7. }
mSession是BootAnimation类的一个成员变量,它的类型为SurfaceComposerClient,是用来和SurfaceFlinger执行Binder进程间通信的,它是在BootAnimation类的构造函数中创建的,如下所示:
[cpp] view plain copy
1. BootAnimation::BootAnimation() : Thread(false)
2. {
3. new SurfaceComposerClient();
4. }
SurfaceComposerClient类内部有一个实现了ISurfaceComposerClient接口的Binder代理对象mClient,这个Binder代理对象引用了SurfaceFlinger服务,SurfaceComposerClient类就是通过它来和SurfaceFlinger服务通信的。
回到BootAnimation类的成员函数onFirstRef中,由于BootAnimation类引用了SurfaceFlinger服务,因此,当SurfaceFlinger服务意外死亡时,BootAnimation类就需要得到通知,这是通过调用成员变量mSession的成员函数linkToComposerDeath来注册SurfaceFlinger服务的死亡接收通知来实现的。
该函数启动了一个BootAnimation线程,用于显示开机动画。由于BootAnimation继承了Thread类,当调用父类的run()时,会在在这个线程运行前,调用readyToRun(),进行一些初始化工作。
[html] view plain copy
- status_t BootAnimation::readyToRun() {
1. mAssets.addDefaultAssets();
2.
3. <IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
4. ISurfaceComposer::eDisplayIdMain));
5. DisplayInfo dinfo;
6. status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
7. if (status)
8. return -1;
9. <SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
10. dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
11. <Surface> s = control->getSurface();
12.
13. // initialize opengl and egl
14. const EGLint attribs[] = {
15. EGL_RED_SIZE, 8,
16. EGL_GREEN_SIZE, 8,
17. EGL_BLUE_SIZE, 8,
18. EGL_DEPTH_SIZE, 0,
19. EGL_NONE
20. };
21. EGLint w, h, dummy;
22. EGLint numConfigs;
23. EGLConfig config;
24. EGLSurface surface;
25. EGLContext context;
26.
27. display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
28.
29. eglInitialize(display, 0, 0);
30. eglChooseConfig(display, attribs, &config, 1, &numConfigs);
31. surface = eglCreateWindowSurface(display, config, s.get(), NULL);
32. context = eglCreateContext(display, config, NULL, NULL);
33. eglQuerySurface(display, surface, EGL_WIDTH, &w);
34. eglQuerySurface(display, surface, EGL_HEIGHT, &h);
35.
36. if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
37. return NO_INIT;
38.
39. mDisplay = display;
40. mContext = context;
41. mSurface = surface;
42. mWidth = w;
43. mHeight = h;
44. mFlingerSurfaceControl = control;
45. mFlingerSurface = s;
46.
47. // If the device has encryption turned on or is in process
48. // of being encrypted we show the encrypted boot animation.
49. char decrypt[PROPERTY_VALUE_MAX];
50. property_get("vold.decrypt", decrypt, "");
51.
52. encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt);
53.
54. zipFile = NULL;
55. if ((encryptedAnimation &&
56. (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) &&
57. zipFile = ZipFileRO::open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE)) != NULL)) ||
58. ((access(OEM_BOOTANIMATION_FILE, R_OK) == 0) &&
59. zipFile = ZipFileRO::open(OEM_BOOTANIMATION_FILE)) != NULL)) ||
60. ((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) &&
61. zipFile = ZipFileRO::open(SYSTEM_BOOTANIMATION_FILE)) != NULL))) {
62. mZip = zipFile;
63. }
64. return NO_ERROR;
65. }
readyToRun函数主要做了一下几个工作:
第一,调用SurfaceComposerClient对象mSession的成员函数createSurface,获得一个SurfaceControl对象control,然后调用control的成员函数getSurface,获得一个Surface对象s。control和s都可以与SurgaceFlinger通过binder进行通信。
第二,初始化IOPENEGL和EGL。主要是四个参数:EGLDisplay对象display,用来描述一个EGL显示屏;EGLConfig对象config,用来描述一个EGL帧缓冲区配置参数;EGLSurface对象surface,用来描述一个EGL绘图表面;EGLContext对象context,用来描述一个EGL绘图上下文。
第三,读取动画文件。动画文件的读取是按顺序进行的,如果读取成功,则不再读取后续的文件,如果失败,则读取下一个文件。
"
为了能够在OpenGL和Android窗口系统之间的建立一个桥梁,我们需要一个EGLDisplay对象display,一个EGLConfig对象config,一个EGLSurface对象surface,以及一个EGLContext对象context,其中,EGLDisplay对象display用来描述一个EGL显示屏,EGLConfig对象config用来描述一个EGL帧缓冲区配置参数,EGLSurface对象surface用来描述一个EGL绘图表面,EGLContext对象context用来描述一个EGL绘图上下文(状态),它们是分别通过调用egl库函数eglGetDisplay、EGLUtils::selectConfigForNativeWindow、eglCreateWindowSurface和eglCreateContext来获得的。注意,EGLConfig对象config、EGLSurface对象surface和EGLContext对象context都是用来描述EGLDisplay对象display的。有了这些对象之后,就可以调用函数eglMakeCurrent来设置当前EGL库所使用的绘图表面以及绘图上下文。
还有另外一个地方需要注意的是,每一个EGLSurface对象surface有一个关联的ANativeWindow对象。这个ANativeWindow对象是通过函数eglCreateWindowSurface的第三个参数来指定的。在我们这个场景中,这个ANativeWindow对象正好对应于前面所创建的 Surface对象s。每当OpenGL需要绘图的时候,它就会找到前面所设置的绘图表面,即EGLSurface对象surface。有了EGLSurface对象surface之后,就可以找到与它关联的ANativeWindow对象,即Surface对象s。有了Surface对象s之后,就可以通过其内部的Binder代理对象mSurface来请求SurfaceFlinger服务返回帧缓冲区硬件设备的一个图形访问接口。这样,OpenGL最终就可以将要绘制的图形渲染到帧缓冲区硬件设备中去,即显示在实际屏幕上。屏幕的大小,即宽度和高度,可以通过函数eglQuerySurface来获得。
"
[cpp] view plain copy
1. bool BootAnimation::threadLoop()
2. {
3. bool r;
4. // We have no bootanimation file, so we use the stock android logo
5. // animation.
6. if (mZip == NULL) {
7. r = android();
8. } else {
9. r = movie();
10. }
11. eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
12. eglDestroyContext(mDisplay, mContext);
13. eglDestroySurface(mDisplay, mSurface);
14. mFlingerSurface.clear();
15. mFlingerSurfaceControl.clear();
16. eglTerminate(mDisplay);
17. IPCThreadState::self()->stopProcess();
18. return r;
19. }
如果BootAnimation类的成员变量mAndroidAnimation的值等于true,那么接下来就会调用BootAnimation类的成员函数android来显示系统默认的开机动画,否则的话,就会调用BootAnimation类的成员函数movie来显示用户自定义的开机动画。显示完成之后,就会销毁前面所创建的EGLContext对象mContext、EGLSurface对象mSurface,以及EGLDisplay对象mDisplay等。
3.3 movie()的实现
播放bootanimation.zip
a.读取desc.txt和audio_cof.txt文件
1. .............
2. if (!readFile("desc.txt", desString)) {
return false;
}
char const* s = desString.string();
// Create and initialize an AudioPlayer if we have an audio_conf.txt file
String8 audioConf;
if (readFile("audio_conf.txt", audioConf)) {
mAudioPlayer = new AudioPlayer;
if (!mAudioPlayer->init(audioConf.string())) {
ALOGE("mAudioPlayer.init failed");
mAudioPlayer = NULL;
}
}3. .............
desc.txt文件
[cpp] view plain copy
1.
............
for (;;) {
const char* endl = strstr(s, "\n");
if (!endl) break;
String8 line(s, endl - s);
const char* l = line.string();
int fps, width, height, count, pause;
char path[ANIM_ENTRY_NAME_MAX];
char color[7] = "000000"; // default to black if unspecified
char pathType;
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);
Animation::Part part;
part.playUntilComplete = pathType == 'c';
part.count = count;
part.pause = pause;
part.path = path;
part.audioFile = NULL;
if (!parseColor(color, part.backgroundColor)) {
ALOGE("> invalid color '#%s'", color);
part.backgroundColor[0] = 0.0f;
part.backgroundColor[1] = 0.0f;
part.backgroundColor[2] = 0.0f;
}
animation.parts.add(part);
}
s = ++endl;
}2. .............
c.读取图片和Audio内容
[cpp] view plain copy
1. // read all the data structures
const size_t pcount = animation.parts.size();
void *cookie = NULL;
if (!mZip->startIteration(&cookie)) {
return false;
}
ZipEntryRO entry;
char name[ANIM_ENTRY_NAME_MAX];
while ((entry = mZip->nextEntry(cookie)) != NULL) {
const int foundEntryName = mZip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX);
if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) {
ALOGE("Error fetching entry file name");
continue;
}
const String8 entryName(name);
const String8 path(entryName.getPathDir());
const String8 leaf(entryName.getPathLeaf());
if (leaf.size() > 0) {
for (size_t j=0 ; j<pcount ; j++) {
if (path == animation.parts[j].path) {
int method;
// supports only stored png files
if (mZip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) {
if (method == ZipFileRO::kCompressStored) {
FileMap* map = mZip->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.audioFile = map;
} else {
Animation::Frame frame;
frame.name = leaf;
frame.map = map;
part.frames.add(frame);
}
}
}
}
}
}
}
}
d.之后的代码就是依次进行图片的显示相关的代码和,在checkExit()后进行退出。
4.开机音乐(很多内容来源于网站,因为开发板的播放音乐还没搞好,之后再测试一下,先记录)
在5.0之前第一种 方法。(据说这种方法不行了)
使用MediaPlay实现,思路大致如下:
Makefile文件中添加:
[html] view plain copy
1. LOCAL_SHARED_LIBRARIES += \
2. libmedia
BootAnimation.cpp中添加:
[html] view plain copy
1. #include <system/audio.h>
2.
3.
4. bool BootAnimation :: soundplay()
5. {
6. mfd = open(xxxxx, O_RDONLY); //xxxxx为音乐文件
7.
8. mp = new MediaPlayer();
9. >setDataSource(mfd, 0, 0x7ffffffffffffffLL);
10. >setAudioStreamType(/*AUDIO_STREAM_MUSIC*/AUDIO_STREAM_SYSTEM);
11. >prepare();
12. >start();
13. }
14.
15. bool BootAnimation::soundstop()
16. {
17. if (mp != NULL)
18. >stop();
19. }
在movie()中,播放开机动画前调用soundplay,结束时调用soundstop。
5.0版本后确实源码中提供了AudioPlayer类,用作播放音乐。本部分内容,还没有配置好,之后再更新,待续。