Android系统启动过程中个,最多有三个开机画面。第一个开机画面是在内核启动过程中出现的一个静态画面(默认不显示),第二个是在init启动过程中出现的一个静态画面(我们平常所说的logo),第三个开机画面是在系统服务启动过程中出现的,为动态画面。
第一个和第二个开机画面图片位置:
mediatek/custom/common/uboot/logo/wvga_a56_doov_cta。
第三个开机动画位置:
mediatek/custom/huaqin75_cu_ics/system/bootanim/bootanimation
第一个和第二个开机画面知道更换图片的位置即可, 这两帧画面是由底层uboot或者Kernel显示的,第三个开机动画则是由系统服务启动的。
开机动画由应用程序bootanimation负责显示。应用程序bootanimation在启动脚本init.rc中被配置成一个服务,如下所示:
service bootanim /system/bin/bootanimation
class core
user graphics
group graphics audio
disabled
oneshot
程序段说明:
bootanim
为服务名称,它对应执行/system/bin/bootanimation应用程序。
user 和group
应用程序bootanimation的用户和用户组名称分别设置为graphics。
disabled
用来启动应用程序bootanimation的服务是disable的,即init进程在启动的时候,不会主动将应用程序bootanimation启动起来。当SurfaceFlinger服务启动的时候,它会通过修改系统属性ctl.start的值来通知init进程启动应用程序bootanimation,以便可以显示第三个开机画面。System进程将系统中的关键服务都启动起来后,ActivityManagerService服务会通知SurfaceFlinger服务通过修改系统属性ctl.stop的值通知init进程停止执行应用程序bootanimation,停止显示第三个开机画面。
oneshot
启动一次。
下面基于Android6.0分析bootanimation的工作流程
bootanimation的实现在
frameworks/base/cmds/bootanimation
目录下,其入口函数main是实现在
frameworks/base/cmds/bootanimation/bootanimation_main.cpp
int main()
{
setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
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;
}
从代码中可以看出,实际真正工作是由BootAnimation
类来完成的。
main函数首先检查系统属性“debug.sf.nobootnimaition
”的值是否等于0。如果为0则不做任何操作,反之则先启动一个Binder线程池。由于BootAnimation对象在显示开机动画的过程中,需要与SurfaceFlinger服务通信,因此,应用程序bootanimation就需要启动一个Binder线程池。
BootAnimation
类间接地继承了RefBase
类,并且重写了RefBase
类的成员函数onFirstRef
,因此,当一个BootAnimation
对象第一次被智能指针引用的时,这个BootAnimation
对象的成员函数onFirstRef
就会被调用。所以下面的代码会被执行:
void BootAnimation::onFirstRef() {
status_t err = mSession->linkToComposerDeath(this);
ALOGE_IF(err, "linkToComposerDeath failed (%s) ", strerror(-err));
if (err == NO_ERROR) {
run("BootAnimation", PRIORITY_DISPLAY);
}
}
mSession
是BootAnimation
类的一个成员变量,它的类型为SurfaceComposerClient
,是用来和SurfaceFlinger
执行Binder进程间通信的,它是在BootAnimation
类的构造函数中创建的,如下所示
BootAnimation::BootAnimation() : Thread(false), mZip(NULL)
{
mData = new PlayData();
mSession = new SurfaceComposerClient();
}
SurfaceComposerClient类(frameworks/native/include/gui/)内部有一个实现了ISurfaceComposerClient接口的Binder代理对象mClient,这个Binder代理对象引用了SurfaceFlinger服务,SurfaceComposerClient类就是通过它来和SurfaceFlinger服务通信的。
回到BootAnimation
类的成员函数onFirstRef
中,由于BootAnimation
类引用了SurfaceFlinger
服务,因此,当SurfaceFlinger
服务意外死亡时,BootAnimation
类就需要得到通知,这是通过调用成员变量mSession
的成员函数linkToComposerDeath
来注册SurfaceFlinger
服务的死亡接收通知来实现的。
BootAnimation
类继承了Thread
类,因此,当BootAnimation
类的成员函数onFirstRef调用了父类Thread的成员函数run之后,系统就会创建一个线程,这个线程在第一次运行之前,会调用BootAnimation
类的成员函数readyToRun来执行一些初始化工作,后面再调用BootAnimation类的成员函数htreadLoop来显示第三个开机画面。
status_t BootAnimation::readyToRun() {
mAssets.addDefaultAssets();
sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
ISurfaceComposer::eDisplayIdMain));
DisplayInfo dinfo;
status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
if (status)
return -1;
// create the native surface
sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
SurfaceComposerClient::openGlobalTransaction();
control->setLayer(0x40000000);
SurfaceComposerClient::closeGlobalTransaction();
sp<Surface> s = control->getSurface();
//原生代码
// initialize opengl and egl
/*const EGLint attribs[] = {
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_DEPTH_SIZE, 0,
EGL_NONE
};
EGLint w, h;
EGLint numConfigs;
EGLConfig config;
EGLSurface surface;
EGLContext context;
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display, 0, 0);
eglChooseConfig(display, attribs, &config, 1, &numConfigs);
surface = eglCreateWindowSurface(display, config, s.get(), NULL);
context = eglCreateContext(display, config, NULL, NULL);
eglQuerySurface(display, surface, EGL_WIDTH, &w);
eglQuerySurface(display, surface, EGL_HEIGHT, &h);
if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
return NO_INIT;
mDisplay = display;
mContext = context;
mSurface = surface;
mWidth = w;
mHeight = h;*/
mFlingerSurfaceControl = control;
mFlingerSurface = s;
mVideoSurface = s;
// If the device has encryption turned on or is in process
// of being encrypted we show the encrypted boot animation.
char decrypt[PROPERTY_VALUE_MAX];
property_get("vold.decrypt", decrypt, "");
bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt);
ZipFileRO* zipFile = NULL;
//厂商定制代码
#ifdef INTEL_FEATURE_SHUTDOWNANIM_SUPPORT
char shutdown_value_str[PROPERTY_VALUE_MAX];
property_get(SHUTDOWN_PROP_NAME, shutdown_value_str, "1");
int shutdown_value = atoi(shutdown_value_str);
if(shutdown_value == 0 ) {
if ((encryptedAnimation &&
(access(SYSTEM_ENCRYPTED_SHUTDOWNANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(SYSTEM_ENCRYPTED_SHUTDOWNANIMATION_FILE)) != NULL)) ||
((access(OEM_SHUTDOWNANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(OEM_SHUTDOWNANIMATION_FILE)) != NULL)) ||
((access(SYSTEM_SHUTDOWNANIMATION_FILE, R_OK) == 0) &&
((zipFile = ZipFileRO::open(SYSTEM_SHUTDOWNANIMATION_FILE)) != NULL))) {
mZip = zipFile;
}
}
else{
#endif
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;
}
#ifdef INTEL_FEATURE_SHUTDOWNANIM_SUPPORT
}
#endif
return NO_ERROR;
}
BootAnimation
类的成员函数session()
用来返回BootAnimation
类的成员变量mSession
所描述的一个SurfaceComposerClient
对象。
通过调用成员函数createSurface
可以获得一个SurfaceControl
对象control
,createSurface
首先调用内部的Binder
代理对象mClient
来请求SurfaceFlinger
返回一个类型为SurfaceLayer
的Binder
代理对象,接着再使用这个Binder
代理对象来创建一个SurfaceControl
对象。
创建出来的SurfaceControl
对象的成员变量mSurfaceData
就指向了从SurfaceFlinger
返回来的类型为SurfaceLayer
的Binder
代理对象。有了这个Binder
代理对象之后,SurfaceControl
对象就可以和SurfaceFlinger
服务通信了。
调用SurfaceControl
对象control的成员函数getSurface
会返回一个Surface
对象s。这个Surface
对象s
内部也有一个类型为SurfaceLayer
的Binder
代理对象mSurface
,Surface
对象s
也可以通过其内部的Binder
代理对象mSurface
来和SurfaceFlinger
服务通信。
OpenGL
需要通过ANativeWindow
类来间接地操作Android窗口系统。这种桥梁关系是通过EGL
库来建立的,所有以egl为前缀的函数名均为EGL库提供的接口。为了能够在OpenGL
和Android窗口系统之间的建立一个桥梁,我们需要一个EGLDisplay
对象display
(用来描述一个EGL显示屏),一个EGLConfig
对象config(用来描述一个EGL帧缓冲区配置参数),一个EGLSurface
对象surface
(用来描述一个EGL绘图表面),以及一个EGLContext
对象context
(用来描述一个EGL绘图上下文(状态)),EGLConfig
对象config
、EGLSurface
对象surface
和EGLContext
对象context
都是用来描述EGLDisplay
对象display
的。有了这些对象之后,就可以调用函数eglMakeCurrent
来设置当前EGL
库所使用的绘图表面以及绘图上下文。
每当OpenGL
需要绘图的时候,它就会找到前面所设置的绘图表面,即EGLSurface
对象surface
。有了EGLSurface
对象surface
之后,就可以找到与它关联的ANativeWindow
对象,即Surface
对象s
。有了Surface
对象s
之后,就可以通过其内部的Binder
代理对象mSurface
来请求SurfaceFlinger
服务返回帧缓冲区硬件设备的一个图形访问接口。这样,OpenGL
最终就可以将要绘制的图形渲染到帧缓冲区硬件设备中去,即显示在实际屏幕上。屏幕的大小,即宽度和高度,可以通过函数eglQuerySurface
来获得。
BootAnimation类的成员变量mZip是一开机动画Zip文件路径。如果用户自定的的开机动画
/system/media/bootanimation.zip
都不存在时,它的值变为null,即当它的值等于null的时候,要显示的开机动画是Android系统默认的开机动画,即要执行mp4()
函数,反之开机动画就是由用户自定义的开机动画,即要执行movie()
函数。
这一步执行完成之后,用来显示第三个开机画面的线程的初始化工作就执行完成了,接下来,就会执行这个线程的主体函数,即BootAnimation
类的成员函数threadLoop()
。
BootAnimation
类的成员函数threadLoop的实现如下所示:
bool BootAnimation::threadLoop()
{
bool r;
// We have no bootanimation file, so we use the stock android logo
// animation.
if (mZip == NULL) {
r = mp4();
} 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;
}
首先判断用户定义的开机动画zip文件是否为空,为空则通过mp4()
函数播放视频文件, 不为空则通过movie()
函数播放用户自定义动画。 显示完成之后,就会销毁前面所创建的EGLContext
对象mContext
、EGLSurface
对象mSurface
,以及EGLDisplay
对象mDisplay
等。
android默认开机动画代码如下:
bool BootAnimation::android()
{
initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");
initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");
// clear screen
glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glClearColor(0,0,0,1);
glClear(GL_COLOR_BUFFER_BIT);
eglSwapBuffers(mDisplay, mSurface);
glEnable(GL_TEXTURE_2D);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
const GLint xc = (mWidth - mAndroid[0].w) / 2;
const GLint yc = (mHeight - mAndroid[0].h) / 2;
const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);
glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),
updateRect.height());
// Blend state
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
const nsecs_t startTime = systemTime();
do {
nsecs_t now = systemTime();
double time = now - startTime;
float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;
GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;
GLint x = xc - offset;
glDisable(GL_SCISSOR_TEST);
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);
glDrawTexiOES(x, yc, 0, mAndroid[1].w, mAndroid[1].h);
glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);
glEnable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);
glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);
EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);
if (res == EGL_FALSE)
break;
// 12fps: don't animate too fast to preserve CPU
const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);
if (sleepTime > 0)
usleep(sleepTime);
checkExit();
} while (!exitPending());
glDeleteTextures(1, &mAndroid[0].name);
glDeleteTextures(1, &mAndroid[1].name);
return false;
}
Android系统默认的开机动画是由图片android-logo-mask.png
和android-logo-shine.png
制作(在frameworks/base/core/res/assets/images目录中),它们最终会被编译在framework-res模块(frameworks/base/core/res)中,即编译在framework-res.apk
文件中。编译在framework-res模块中的资源文件可以通过AssetManager
类来访问。
BootAnimation
类的成员函数android首先调用另外一个成员函数initTexture
来将根据图片android-logo-mask.png
和android-logo-shine.png
的内容来分别创建两个纹理对象,这两个纹理对象就分别保存在BootAnimation类的成员变量mAndroid所描述的一个数组中。通过混合渲染这两个纹理对象,我们就可以得到一个开机动画,这是通过中间的while循环语句来实现的
这个while循环语句会一直被执行,直到应用程序*/system/bin/bootanimation*被结束为止。
播放用户定义的动画代码流程如下:
bool BootAnimation::movie()
{
String8 desString;
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;
}
}
Animation animation;
// Parse the description file
for (;;) {
const char* endl = strstr(s, "\n");
if (endl == NULL) 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;
}
// 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) {
uint16_t 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);
}
}
}
}
}
}
}
}
mZip->endIteration(cookie);
glShadeModel(GL_FLAT);
glDisable(GL_DITHER);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D, 0);
glEnable(GL_TEXTURE_2D);
glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
const int xc = (mWidth - animation.width) / 2;
const int yc = ((mHeight - animation.height) / 2);
nsecs_t frameDuration = s2ns(1) / animation.fps;
Region clearReg(Rect(mWidth, mHeight));
clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height));
for (size_t i=0 ; i<pcount ; i++) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size();
glBindTexture(GL_TEXTURE_2D, 0);
for (int r=0 ; !part.count || r<part.count ; r++) {
// Exit any non playuntil complete parts immediately
if(exitPending() && !part.playUntilComplete)
break;
// only play audio file the first time we animate the part
if (r == 0 && mAudioPlayer != NULL && part.audioFile) {
mAudioPlayer->playFile(part.audioFile);
}
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();
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);
}
checkExit();
}
usleep(part.pause * ns2us(frameDuration));
// For infinite parts, we've now played them at least once, so perhaps exit
if(exitPending() && !part.count)
break;
}
// free the textures for this part
if (part.count != 1) {
for (size_t j=0 ; j<fcount ; j++) {
const Animation::Frame& frame(part.frames[j]);
glDeleteTextures(1, &frame.tid);
}
}
}
#ifdef INTEL_FEATURE_SHUTDOWNANIM_SUPPORT
property_set(SHUTDOWN_PROP_ENABLE, "true");
char shutdown_value_str[PROPERTY_VALUE_MAX];
property_get(SHUTDOWN_PROP_NAME, shutdown_value_str, "1");
int shutdown_value = atoi(shutdown_value_str);
if(shutdown_value == 0 ) {
property_set(SHUTDOWN_PROP_NAME, "1");
do {
// wait for shutdownthread.java
sleep(100);
property_get(SHUTDOWN_PROP_NAME, shutdown_value_str, "1");
shutdown_value = atoi(shutdown_value_str);
} while (shutdown_value != 2 );
}
#endif
return false;
}
从前面BootAnimation类的成员函数readyToRun的实现可以知道,如果目标设备上存在压缩文件mZip。无论BootAnimation类的成员变量mZip指向的是哪一个压缩文件,这个压缩文件都必须包含有一个名称为“desc.txt”的文件,用来描述用户自定义的开机动画是如何显示的。
文件desc.txt的内容格式如下面的例子所示:
480 800 6
p 1 2 folder1
p 0 2 folder2
第一行的三个数字分别表示开机动画在屏幕中的显示宽度、高度以及帧速(fps)。剩余的每一行都用来描述一个动画片断,这些行必须要以字符“p”来开头,后面紧跟着两个数字以及一个文件目录路径名称。第一个数字表示一个片段的循环显示次数,如果它的值等于0,那么就表示无限循环地显示该动画片断。第二个数字表示每一个片段在两次循环显示之间的时间间隔。这个时间间隔是以一个帧的时间为单位的。文件目录下面保存的是一系列png文件,这些png文件会被依次显示在屏幕中。
接下来的第一个for循环语句分析完成desc.txt文件的内容后,就得到了开机动画的显示大小、速度以及片断信息。这些信息都保存在Animation对象animation中,其中,每一个动画片断都使用一个Animation::Part对象来描述,并且保存在Animation对象animation的成员变量parts所描述的一个片断列表中。而后第二个for循环中,BootAnimation类的成员函数movie再断续将每一个片断所对应的png图片读取出来。每一个png图片都表示一个动画帧,使用一个Animation::Frame对象来描述,并且保存在对应的Animation::Part对象的成员变量frames所描述的一个帧列表中。
获得了开机动画的所有信息之后,接下来的前面的一系列gl函数首先用来清理屏幕,接下来的一系列gl函数用来设置OpenGL的纹理显示方式。
变量xc和yc的值用来描述开机动画的显示位置,即需要在屏幕中间显示开机动画,另外一个变量frameDuration的值用来描述每一帧的显示时间,它是以纳秒为单位的。Region对象clearReg用来描述屏幕中除了开机动画之外的其它区域,它是用整个屏幕区域减去开机动画所点据的区域来得到的。
准备好开机动画的显示参数之后,就可以用最后一个for循环执行显示开机动画的操作了,第一层for循环用来显示每一个动画片断,第二层的for循环用来循环显示每一个动画片断,第三层的for循环用来显示每一个动画片断所对应的png图片。这些png图片以纹理的方式来显示在屏幕中。
如果Region对象clearReg所包含的区域不为空,那么在调用函数glDrawTexiOES和eglSwapBuffers来显示每一个png图片之前,首先要将它所包含的区域裁剪掉,避免开机动画可以显示在指定的位置以及大小中。
每当显示完成一个png图片之后,都要将变量frameDuration的值从纳秒转换为毫秒。如果转换后的值大小于,那么就需要调用函数usleep函数来让线程睡眠一下,以保证每一个png图片,即每一帧动画都按照预先指定好的速度来显示。注意,函数usleep指定的睡眠时间只能精确到毫秒,因此,如果预先指定的帧显示时间小于1毫秒,那么BootAnimation类的成员函数movie是无法精确地控制地每一帧的显示时间的。
还有另外一个地方需要注意的是,每当循环显示完成一个片断时,需要调用usleep
函数来使得线程睡眠part.pause * ns2us(frameDuration)毫秒,以便可以按照预先设定的节奏来显示开机动画。
最后一个if语句判断一个动画片断是否是循环显示的,即循环次数不等于1。如果是的话,那么就说明前面为它所对应的每一个png图片都创建过一个纹理对象。现在既然这个片断的显示过程已经结束了,因此,就需要释放前面为它所创建的纹理对象。
bootanimation的启动在surfaceflinger初始化后,代码如下:
frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
void SurfaceFlinger::init() {
ALOGI( "SurfaceFlinger's main thread ready to run. "
"Initializing graphics H/W...");
Mutex::Autolock _l(mStateLock);
///
//省略无关代码
//
// set initial conditions (e.g. unblank default device)
initializeDisplays();
// start boot animation
startBootAnim();
}
开机动画的结束是在SystemServer中完成的,流程如下:
enableScreenAfterBoot()(ActivityManagerService)——>enableScreenAfterBoot()(WindowManagerService)——>
performEnableScreen()(WindowManagerService) ——> binder调用SurfaceFlinger中结束动画的函数,代码如下:
if (!mBootAnimationStopped) {
// Do this one time.
try {
IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");
if (surfaceFlinger != null) {
//Slog.i(TAG, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");
Parcel data = Parcel.obtain();
data.writeInterfaceToken("android.ui.ISurfaceComposer");
surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED
data, null, 0);
data.recycle();
}
} catch (RemoteException ex) {
Slog.e(TAG, "Boot completed: SurfaceFlinger is dead!");
}
mBootAnimationStopped = true;
}
最终调用SurfaceFlinger中的bootFinished函数结束动画的播放。
void SurfaceFlinger::bootFinished()
{
const nsecs_t now = systemTime();
const nsecs_t duration = now - mBootTime;
ALOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );
mBootFinished = true;
// wait patiently for the window manager death
const String16 name("window");
sp<IBinder> window(defaultServiceManager()->getService(name));
if (window != 0) {
window->linkToDeath(static_cast<IBinder::DeathRecipient*>(this));
}
// 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.
property_set("service.bootanim.exit", "1");
}