[Android5.1]开机动画显示工作流程分析

转自:https://blog.csdn.net/u010753159/article/details/51325500 感谢作者。

网上有很多关于android开机动画显示的分析,但大部分是针对于android的早期版本。在android5.1中,开机动画显示的工作流程做了一些修改,下面就针对android5.1,分析一下开机动画的启动、显示和停止的整个过程。

1. bootanimation应用的启动过程

Android系统开机动画的显示是由bootanimation应用实现的。

bootanimation在init.rc中的定义如下:

[html]  view plain  copy
  1. service bootanim /system/bin/bootanimation  
  2.     class core  
  3.     user graphics  
  4.     group graphics audio  
  5.     disabled  
  6.     oneshot  

可见,由于设置为"disable",该应用在init启动过程中是不会启动的,需要其他地方显示的调用才能启动。那是什么时候启动的呢?当SurfaceFlinger服务启动时,会修改系统属性值ctl.start,通知init进程启动bootanimation。

在早期的Android版本中,SurfaceFlinger服务是由SystemServer启动的。但在Android5.1中,该服务是init进程启动过程中就启动了。在init.rc中能看到对该服务的描述:

[html]  view plain  copy
  1. service surfaceflinger /system/bin/surfaceflinger  
  2.     class core  
  3.     user system  
  4.     group graphics drmrpc  
  5.     onrestart restart zygote  

SurfaceFlinger服务源码路径为:frameworks\native\services\surfaceflinger

服务的入口在main_surfaceflinger.cpp中,具体为:

[html]  view plain  copy
  1. int main(int, char**) {  
  2.     // When SF is launched in its own process, limit the number of  
  3.     // binder threads to 4.  
  4.     ProcessState::self()->setThreadPoolMaxThreadCount(4);  
  5.   
  6.     // start the thread pool  
  7.     sp<ProcessState> ps(ProcessState::self());  
  8.     ps->startThreadPool();  
  9.   
  10.     // instantiate surfaceflinger  
  11.     sp<SurfaceFlinger> flinger = new SurfaceFlinger();  
  12.   
  13. #if defined(HAVE_PTHREADS)  
  14.     setpriority(PRIO_PROCESS, 0, PRIORITY_URGENT_DISPLAY);  
  15. #endif  
  16.     set_sched_policy(0, SP_FOREGROUND);  
  17.   
  18.     // initialize before clients can connect  
  19.     flinger->init();  
  20.   
  21.     // publish surface flinger  
  22.     sp<IServiceManager> sm(defaultServiceManager());  
  23.     sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false);  
  24.   
  25.     // run in this thread  
  26.     flinger->run();  
  27.   
  28.     return 0;  
  29. }  
主要工作是:新建一个SurfaceFlinger对象,然后调用其中的init()方法,最后调用其中的run()方法。

下面主要看一下SurfaceFlinger::init()方法,具体代码为:

[html]  view plain  copy
  1. void SurfaceFlinger::init() {  
  2.     ALOGI(  "SurfaceFlinger's main thread ready to run. "  
  3.             "Initializing graphics H/W...");  
  4.   
  5.     ......  
  6.   
  7.     // start boot animation  
  8.     startBootAnim();  
  9. }  
可以看到,最后调用了startBootAnim()。该函数代码如下:

[html]  view plain  copy
  1. void SurfaceFlinger::startBootAnim() {  
  2.     // start boot animation  
  3.     property_set("service.bootanim.exit", "0");  
  4.     property_set("ctl.start", "bootanim");  
  5. }  

可见,将系统属性ctl.start的值设置为"bootanim"。

回到init进程的init.c的main函数中:

[cpp]  view plain  copy
  1. int main(int argc, char **argv) {  
  2.   
  3. .......  
  4.   
  5. for(;;) {  
  6.   
  7. .......  
  8.   
  9.         nr = poll(ufds, fd_count, timeout);  
  10.         if (nr <= 0)  
  11.             continue;  
  12.   
  13.         for (i = 0; i < fd_count; i++) {  
  14.             if (ufds[i].revents & POLLIN) {  
  15.                 if (ufds[i].fd == get_property_set_fd())  
  16.                     handle_property_set_fd();  
  17.                 else if (ufds[i].fd == get_keychord_fd())  
  18.                     handle_keychord();  
  19.                 else if (ufds[i].fd == get_signal_fd())  
  20.                     handle_signal();  
  21.             }  
  22.         }  
  23.     }  
  24. }  
可以看到,init进程会使用poll机制来轮询事件,其中一个事件是系统属性值被修改。得到该事件后,会执行handle_property_set_fd(),代码如下:

[html]  view plain  copy
  1. if(memcmp(msg.name,"ctl.",4) == 0) {  
  2.             // Keep the old close-socket-early behavior when handling  
  3.             // ctl.* properties.  
  4.             close(s);  
  5.             if (check_control_mac_perms(msg.value, source_ctx)) {  
  6.                 handle_control_message((char*) msg.name + 4, (char*) msg.value);  
  7.             } else {  
  8.                 ERROR("sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n",  
  9.                         msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid);  
  10.             }  
  11.         }   

该函数会进一步执行handle_control_message(),传入的参数msg.name=ctl.start,msg.value=bootanim。

[html]  view plain  copy
  1. void handle_control_message(const char *msg, const char *arg)  
  2. {  
  3.     if (!strcmp(msg,"start")) {  
  4.         msg_start(arg);  
  5.     } else if (!strcmp(msg,"stop")) {  
  6.         msg_stop(arg);  
  7.     } else if (!strcmp(msg,"restart")) {  
  8.         msg_restart(arg);  
  9.     } else {  
  10.         ERROR("unknown control msg '%s'\n", msg);  
  11.     }  
  12. }  
由于msg == "start",handle_control_message进一步执行msg_start(),且传入的arg参数等于bootanim。msg_start代码如下:

[html]  view plain  copy
  1. static void msg_start(const char *name)  
  2. {  
  3.     struct service *svc = NULL;  
  4.     char *tmp = NULL;  
  5.     char *args = NULL;  
  6.   
  7.     if (!strchr(name, ':'))  
  8.         svc = service_find_by_name(name);  
  9.     else {  
  10.         tmp = strdup(name);  
  11.         if (tmp) {  
  12.             args = strchr(tmp, ':');  
  13.             *args = '\0';  
  14.             args++;  
  15.   
  16.             svc = service_find_by_name(tmp);  
  17.         }  
  18.     }  
  19.   
  20.     if (svc) {  
  21.         service_start(svc, args);  
  22.     } else {  
  23.         ERROR("no such service '%s'\n", name);  
  24.     }  
  25.     if (tmp)  
  26.         free(tmp);  
  27. }  
该函数首先调用service_find_by_name(),从service_list中查询要启动的服务是否有存在,若存在,返回服务的相关信息。因为init.rc中有bootanimation的定义,因此在init进程执行parse_config()时,会将该服务添加到service_list中,所以bootanimation应用是存在的。然后,如果找到了该服务,就调用service_start启动服务。

到此,bootanimation应用就启动了。

2. 开机动画的显示过程

下面,开始分析bootanimation是如何绘制并在屏幕上显示开机动画的。

代码路径为:frameworks\base\cmds\bootanimation。包括以下几个文件:

Android.mk -----------  编译文件   

BootAnimation.cpp ----------- BootAnimation类的定义和实现

BootAnimation.h ----------- BootAnimation的声明

BootAnimation_main.cpp ----------- 程序入口

AudioPlayer.cpp ----------- 视频播放类的定义和实现

AudioPlayer.h ----------- 视频播放类的声明

先看一下bootanimation的入口,BootAnimation_main.cpp中的main函数:

[html]  view plain  copy
  1. int main(int argc, char** argv)  
  2. {  
  3. #if defined(HAVE_PTHREADS)  
  4.     setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);  
  5. #endif  
  6.   
  7.     char value[PROPERTY_VALUE_MAX];  
  8.     property_get("debug.sf.nobootanimation", value, "0");  
  9.     int noBootAnimation = atoi(value);  
  10.     ALOGI_IF(noBootAnimation,  "boot animation disabled");  
  11.     if (!noBootAnimation) {  
  12.         sp<ProcessState> proc(ProcessState::self());  
  13.         ProcessState::self()->startThreadPool();  
  14.           
  15.         sp<BootAnimation> boota = new BootAnimation();       
  16.   
  17.         IPCThreadState::self()->joinThreadPool();  
  18.     }  
  19.     return 0;  
  20. }  

Main函数首先获取属性"debug.sf.nobootanimation"的值并判断,如果为1,函数退出,开机动画就不会显示了。如果为0,会开启一个binder线程池,用来在开机动画的过程中,与SurfaceFlinger通信,接着创建一个BootAnimation对象,该对象就是用来显示开机动画的。下面看一下BootAnimation类的声明:

[html]  view plain  copy
  1. class BootAnimation : public Thread, public IBinder::DeathRecipient  
  2. {  
  3. public:  
  4.                 BootAnimation();  
  5.     virtual     ~BootAnimation();    
  6.     .......  
  7. private:  
  8.     virtual bool        threadLoop();  
  9.     virtual status_t    readyToRun();  
  10.     virtual void        onFirstRef();  
  11.     virtual void        binderDied(const wp<IBinder>& who);  
  12.    
  13.     status_t initTexture(Texture* texture, AssetManager& asset, const char* name);  
  14.     status_t initTexture(const Animation::Frame& frame);  
  15.     bool android();  
  16.     bool readFile(const char* name, String8& outString);  
  17.     bool movie();  
  18.     ......  
  19. };  
可见,BootAnimation类继承了Thread类和IBinder::DeathRecipient类,其中几个重写函数的说明如下:
onFirstRef() ----- 属于其父类RefBase,该函数在强引用sp新增引用计数時调用,就是当有sp包装的类初始化的时候调用;
binderDied() ----- 当对象死掉或者其他情况导致该Binder结束时,就会回调binderDied()方法;
readyToRun() ----- Thread执行前的初始化工作;
threadLoop() ----- 每个线程类都要实现的,在这里定义thread的执行内容。这个函数如果返回true,且没有requestExist()没有被调用,则该函数会再次执行;如果返回false,则threadloop中的内容仅仅执行一次,线程就会退出。

其他主要函数的说明如下:

android() ----- 显示系统默认的开机画面;

movie() ----- 显示用户自定义的开机动画。

BootAnimationL类的构造函数:

[html]  view plain  copy
  1. BootAnimation::BootAnimation() : Thread(false), mZip(NULL),mfd(-1)   
  2. {  
  3.     mSession = new <span style="font-family: Arial, Helvetica, sans-serif;">SurfaceComposerClient</span>();  
  4. }  

主要是new一个SurfaceComposerClient对象,用来和SurfaceFlinger进行binder进程间通信。

由于在BootAnimation_main.cpp的main函数创建BootAnimation对象时,使用了智能指针引用,因此还会调用onFirstRef()函数:

[html]  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. }  
该函数启动了一个BootAnimation线程,用于显示开机动画。由于BootAnimation继承了Thread类,当调用父类的run()时,会在在这个线程运行前,调用readyToRun(),进行一些初始化工作。
[html]  view plain  copy
  1. status_t BootAnimation::readyToRun() {  
  2.     mAssets.addDefaultAssets();  
  3.   
  4.     sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(  
  5.             ISurfaceComposer::eDisplayIdMain));  
  6.     DisplayInfo dinfo;  
  7.     status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);  
  8.     if (status)  
  9.         return -1;      
  10.     sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),  
  11.            dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);     
  12.     sp<Surface> s = control->getSurface();  
  13.   
  14.     // initialize opengl and egl  
  15.     const EGLint attribs[] = {  
  16.             EGL_RED_SIZE,   8,  
  17.             EGL_GREEN_SIZE, 8,  
  18.             EGL_BLUE_SIZE,  8,  
  19.             EGL_DEPTH_SIZE, 0,  
  20.             EGL_NONE  
  21.     };  
  22.     EGLint w, h, dummy;  
  23.     EGLint numConfigs;  
  24.     EGLConfig config;  
  25.     EGLSurface surface;  
  26.     EGLContext context;  
  27.   
  28.     EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);  
  29.   
  30.     eglInitialize(display, 0, 0);  
  31.     eglChooseConfig(display, attribs, &config, 1, &numConfigs);  
  32.     surface = eglCreateWindowSurface(display, config, s.get(), NULL);  
  33.     context = eglCreateContext(display, config, NULL, NULL);  
  34.     eglQuerySurface(display, surface, EGL_WIDTH, &w);  
  35.     eglQuerySurface(display, surface, EGL_HEIGHT, &h);  
  36.   
  37.     if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)  
  38.         return NO_INIT;  
  39.   
  40.     mDisplay = display;  
  41.     mContext = context;  
  42.     mSurface = surface;  
  43.     mWidth = w;  
  44.     mHeight = h;  
  45.     mFlingerSurfaceControl = control;  
  46.     mFlingerSurface = s;  
  47.   
  48.     // If the device has encryption turned on or is in process  
  49.     // of being encrypted we show the encrypted boot animation.  
  50.     char decrypt[PROPERTY_VALUE_MAX];  
  51.     property_get("vold.decrypt", decrypt, "");  
  52.   
  53.     bool encryptedAnimation = atoi(decrypt) != 0 || !strcmp("trigger_restart_min_framework", decrypt);  
  54.   
  55.     ZipFileRO* zipFile = NULL;  
  56.     if ((encryptedAnimation &&  
  57.             (access(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE, R_OK) == 0) &&  
  58.             ((zipFile = ZipFileRO::open(SYSTEM_ENCRYPTED_BOOTANIMATION_FILE)) != NULL)) ||              
  59.             ((access(OEM_BOOTANIMATION_FILE, R_OK) == 0) &&  
  60.             ((zipFile = ZipFileRO::open(OEM_BOOTANIMATION_FILE)) != NULL)) ||  
  61.             ((access(SYSTEM_BOOTANIMATION_FILE, R_OK) == 0) &&  
  62.             ((zipFile = ZipFileRO::open(SYSTEM_BOOTANIMATION_FILE)) != NULL))) {  
  63.         mZip = zipFile;  
  64.     }  
  65.     return NO_ERROR;  
  66. }  

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绘图上下文。

第三,读取动画文件。动画文件的读取是按顺序进行的,如果读取成功,则不再读取后续的文件,如果失败,则读取下一个文件。顺序如下:

1--如果设备的加密功能已经开启,或者设备正在进行加密,则读取加密开机动画文件,路径为

[html]  view plain  copy
  1. #define SYSTEM_ENCRYPTED_BOOTANIMATION_FILE "/system/media/bootanimation-encrypted.zip"  

2--OEM厂商指定的开机动画,路径为:

[html]  view plain  copy
  1. #define OEM_BOOTANIMATION_FILE "/oem/media/bootanimation.zip"  
3--系统开机动画,路径为:

[html]  view plain  copy
  1. #define SYSTEM_BOOTANIMATION_FILE "/system/media/bootanimation.zip"  

线程的初始化工作完成后,就要进入线程的主体函数,完成开机动画的绘制和显示。具体函数为threadLoop():

[html]  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.   
  12.     eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);  
  13.     eglDestroyContext(mDispl</span><span style="font-size:14px;">ay, mSurface);  
  14.     mFlingerSurface.clear();  
  15.     mFlingerSurfaceControl.clear();  
  16.     eglTerminate(mDisplay);  
  17.     IPCThreadState::self()->stopProcess();  
  18.     return r;  
  19. }  

这个函数流程比较简单,首先判断自定义的开机动画文件mZip是否存在,如果存在就调用movie()完成自定义开机画面的显示;如果不存在,调用android()完成系统默认开机画面的显示。然后进行开机动画显示后的销毁、释放工作,主要就是readyToRun中初始化的一些EGL对象。最后终止线程,并return。注意,movie()和android()的返回值都是false,因此线程结束也会返回false。threadLoop()函数如果返回值为false,则该函数中的内容只会执行一次;如果返回true,则会不停的执行。这里返回false,因此只会执行一次。

 android()代码如下:

[html]  view plain  copy
  1. bool BootAnimation::android()  
  2. {  
  3.     initTexture(&mAndroid[0], mAssets, "images/android-logo-mask.png");  
  4.     initTexture(&mAndroid[1], mAssets, "images/android-logo-shine.png");  
  5.       
  6.     // clear screen  
  7.     glShadeModel(GL_FLAT);  
  8.     glDisable(GL_DITHER);  
  9.     glDisable(GL_SCISSOR_TEST);  
  10.     glClearColor(0,0,0,1);  
  11.     glClear(GL_COLOR_BUFFER_BIT);  
  12.     eglSwapBuffers(mDisplay, mSurface);  
  13.   
  14.     glEnable(GL_TEXTURE_2D);  
  15.     glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);  
  16.   
  17.     const GLint xc = (mWidth  - mAndroid[0].w) / 2;  
  18.     const GLint yc = (mHeight - mAndroid[0].h) / 2;  
  19.     const Rect updateRect(xc, yc, xc + mAndroid[0].w, yc + mAndroid[0].h);  
  20.   
  21.     glScissor(updateRect.left, mHeight - updateRect.bottom, updateRect.width(),  
  22.             updateRect.height());  
  23.   
  24.     // Blend state  
  25.     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);  
  26.     glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);  
  27.   
  28.     const nsecs_t startTime = systemTime();  
  29.     do {  
  30.         nsecs_t now = systemTime();  
  31.         double time = now - startTime;  
  32.         float t = 4.0f * float(time / us2ns(16667)) / mAndroid[1].w;  
  33.         GLint offset = (1 - (t - floorf(t))) * mAndroid[1].w;  
  34.         GLint x = xc - offset;  
  35.   
  36.         glDisable(GL_SCISSOR_TEST);  
  37.         glClear(GL_COLOR_BUFFER_BIT);  
  38.   
  39.         glEnable(GL_SCISSOR_TEST);  
  40.         glDisable(GL_BLEND);  
  41.         glBindTexture(GL_TEXTURE_2D, mAndroid[1].name);  
  42.         glDrawTexiOES(x,                 yc, 0, mAndroid[1].w, mAndroid[1].h);  
  43.         glDrawTexiOES(x + mAndroid[1].w, yc, 0, mAndroid[1].w, mAndroid[1].h);  
  44.   
  45.         glEnable(GL_BLEND);  
  46.         glBindTexture(GL_TEXTURE_2D, mAndroid[0].name);  
  47.         glDrawTexiOES(xc, yc, 0, mAndroid[0].w, mAndroid[0].h);  
  48.   
  49.         EGLBoolean res = eglSwapBuffers(mDisplay, mSurface);  
  50.         if (res == EGL_FALSE)  
  51.             break;  
  52.   
  53.         // 12fps: don't animate too fast to preserve CPU  
  54.         const nsecs_t sleepTime = 83333 - ns2us(systemTime() - now);  
  55.         if (sleepTime > 0)  
  56.             usleep(sleepTime);  
  57.   
  58.         checkExit();  
  59.     } while (!exitPending());  
  60.   
  61.     glDeleteTextures(1, &mAndroid[0].name);  
  62.     glDeleteTextures(1, &mAndroid[1].name);  
  63.     return false;  
  64. }  

首先,调用initTexture读取android系统默认的开机动画图片,生成纹理对象,并放在mAndroid数组中。开机动画图片共两张:android-logo-mask.png和android-logo-shine.png,存放在frameworks\base\core\res\assets\images路径下。其中android-logo-mask.png是黑底白字“android”字样;android-logo-shine.png是黑白渐变的显示背景。接着,clear screen。然后,在do...while循环中渲染两个纹理对象,从而生成开机动画。在循环语句最后会执行checkExit()函数。

[html]  view plain  copy
  1. void BootAnimation::checkExit() {  
  2.     // Allow surface flinger to gracefully request shutdown  
  3.     char value[PROPERTY_VALUE_MAX];  
  4.     property_get(EXIT_PROP_NAME, value, "0");  
  5.     int exitnow = atoi(value);  
  6.     if (exitnow) {  
  7.         requestExit();  
  8.         if (mAudioPlayer != NULL) {  
  9.             mAudioPlayer->requestExit();  
  10.         }  
  11.     }  
  12. }  

首先调用property_get获取属性EXIT_PROP_NAME的值。

[html]  view plain  copy
  1. #define EXIT_PROP_NAME "service.bootanim.exit"  

然后判断该值,如果为1,则调用requestExit()要求退出当前线程,该函数是异步的。

回到android()代码:

[html]  view plain  copy
  1. while (!exitPending());  
调用exitPending(),改函数判断requestExit()是否被调用过,如果调用过则返回true,否则为false。

这样,当属性“service.bootanim.exit”值被设为"1"时,android()就会调用requestExit(),exitPending()返回值为true。于是do...while()循环就会退出,开机动画绘制就会结束。

至于什么时候是哪个服务将属性“service.bootanim.exit”的值设置为1的,我们后面讲开机动画的停止的时候会提到。

下面分析一下movie()函数的具体实现。由于函数比较长,所以这里分段分析。

[html]  view plain  copy
  1. String8 desString;  
  2.   
  3. if (!readFile("desc.txt", desString)) {  
  4.     return false;  
  5. }  
  6. char const* s = desString.string();  
  7.   
  8. // Create and initialize an AudioPlayer if we have an audio_conf.txt file  
  9. String8 audioConf;  
  10. if (readFile("audio_conf.txt", audioConf)) {  
  11.     mAudioPlayer = new AudioPlayer;  
  12.     if (!mAudioPlayer->init(audioConf.string())) {  
  13.         ALOGE("mAudioPlayer.init failed");  
  14.         mAudioPlayer = NULL;  
  15.     }  
  16. }  
这段代码作用是读取开机动画文件mZip中的描述文件“desc.txt”。每个动画文件压缩包中必须要包含一个desc.txt,该文件用来描述开机动画如何显示。下面以一个示例来分析一下该文件:
[html]  view plain  copy
  1. 480 800 30  
  2. p 1 0  folder0   
  3. p 0 10 folder1  

第一行:前两个是开机动画在屏幕上显示的像素大小,分别为宽度和高度。第三个数字为每秒显示的帧数。

下面两行描述开机动画文件及显示的次数和间隔时间,每一行为一个片段。第一项类型,如p或c;第二项为该动画文件显示的次数,“0”表示重复显示;第三项为两次显示的间隔时间;第四项为动画文件。比如,"p 1 0 folder0"表示folder0文件夹中的动画只显示一次;”p 0 10 folder1“表示folder1中的动画文件重复显示,且两次显示的间隔时间为10秒。

[html]  view plain  copy
  1. // Parse the description file  
  2. for (;;) {  
  3.     const char* endl = strstr(s, "\n");  
  4.     if (!endl) break;  
  5.     String8 line(s, endl - s);  
  6.     const char* l = line.string();  
  7.     int fps, width, height, count, pause;  
  8.     char path[ANIM_ENTRY_NAME_MAX];  
  9.     char color[7] = "000000"; // default to black if unspecified  
  10.   
  11.     char pathType;  
  12.     if (sscanf(l, "%d %d %d %d", &width, &height, &fps, &flg) >= 3) {  
  13.         //ALOGD("> w=%d, h=%d, fps=%d, flg=%d", width, height, fps, flg);  
  14.         animation.width = width;  
  15.         animation.height = height;  
  16.         animation.fps = fps;  
  17.     }  
  18.     else if (sscanf(l, " %c %d %d %s #%6s", &pathType, &count, &pause, path, color) >= 4) {  
  19.         // ALOGD("> type=%c, count=%d, pause=%d, path=%s, color=%s", pathType, count, pause, path, color);  
  20.         Animation::Part part;  
  21.         part.playUntilComplete = pathType == 'c';  
  22.         part.count = count;  
  23.         part.pause = pause;  
  24.         part.path = path;  
  25.         part.audioFile = NULL;  
  26.         if (!parseColor(color, part.backgroundColor)) {  
  27.             ALOGE("> invalid color '#%s'", color);  
  28.             part.backgroundColor[0] = 0.0f;  
  29.             part.backgroundColor[1] = 0.0f;  
  30.             part.backgroundColor[2] = 0.0f;  
  31.         }  
  32.         animation.parts.add(part);  
  33.     }  
  34.   
  35.     s = ++endl;  
  36. }  

上面这段代码主要是解析上面读取的desc.txt,并将相关参数保存到Animation对象animation中。

[html]  view plain  copy
  1. // read all the data structures  
  2. const size_t pcount = animation.parts.size();  
  3. void *cookie = NULL;  
  4. if (!mZip->startIteration(&cookie)) {  
  5.     return false;  
  6. }  
  7.   
  8. ZipEntryRO entry;  
  9. char name[ANIM_ENTRY_NAME_MAX];  
  10. while ((entry = mZip->nextEntry(cookie)) != NULL) {  
  11.     const int foundEntryName = mZip->getEntryFileName(entry, name, ANIM_ENTRY_NAME_MAX);  
  12.     if (foundEntryName > ANIM_ENTRY_NAME_MAX || foundEntryName == -1) {  
  13.         ALOGE("Error fetching entry file name");  
  14.         continue;  
  15.     }  
  16.   
  17.     const String8 entryName(name);  
  18.     const String8 path(entryName.getPathDir());  
  19.     const String8 leaf(entryName.getPathLeaf());  
  20.     if (leaf.size() > 0) {  
  21.         for (size_t j=0 ; j<pcount ; j++) {  
  22.             if (path == animation.parts[j].path) {  
  23.                 int method;  
  24.                 // supports only stored png files  
  25.                 if (mZip->getEntryInfo(entry, &method, NULL, NULL, NULL, NULL, NULL)) {  
  26.                     if (method == ZipFileRO::kCompressStored) {  
  27.                         FileMap* map = mZip->createEntryFileMap(entry);  
  28.                         if (map) {  
  29.                             Animation::Part& part(animation.parts.editItemAt(j));  
  30.                             if (leaf == "audio.wav") {  
  31.                                 // a part may have at most one audio file  
  32.                                 part.audioFile = map;  
  33.                             } else {  
  34.                                 Animation::Frame frame;  
  35.                                 frame.name = leaf;  
  36.                                 frame.map = map;  
  37.                                 part.frames.add(frame);  
  38.                             }  
  39.                         }  
  40.                     }  
  41.                 }  
  42.             }  
  43.         }  
  44.     }  
  45. }  
  46.   
  47. mZip->endIteration(cookie);  

上面这段代码是读取每个片段中的png图片,并保存在animation.parts.frames中。下面就开始将开机动画绘制到屏幕上,代码如下:

[html]  view plain  copy
  1. // clear screen  
  2. glShadeModel(GL_FLAT);  
  3. glDisable(GL_DITHER);  
  4. glDisable(GL_SCISSOR_TEST);  
  5. glDisable(GL_BLEND);  
  6. glClear(GL_COLOR_BUFFER_BIT);  
  7.   
  8. eglSwapBuffers(mDisplay, mSurface);  
  9.   
  10. glBindTexture(GL_TEXTURE_2D, 0);  
  11. glEnable(GL_TEXTURE_2D);  
  12. glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);  
  13. glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);  
  14. glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);  
  15. glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  
  16. glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
  17.   
  18. const int xc = (mWidth - animation.width) / 2;  
  19. const int yc = ((mHeight - animation.height) / 2);  
  20. nsecs_t lastFrame = systemTime();  
  21. nsecs_t frameDuration = s2ns(1) / animation.fps;  
  22.   
  23. Region clearReg(Rect(mWidth, mHeight));  
  24. clearReg.subtractSelf(Rect(xc, yc, xc+animation.width, yc+animation.height));  
  25.   
  26. for (size_t i=0 ; i<pcount ; i++) {  
  27.     const Animation::Part& part(animation.parts[i]);  
  28.     const size_t fcount = part.frames.size();  
  29.     glBindTexture(GL_TEXTURE_2D, 0);  
  30.   
  31.     for (int r=0 ; !part.count || r<part.count ; r++) {  
  32.         // Exit any non playuntil complete parts immediately  
  33.         if(exitPending() && !part.playUntilComplete)  
  34.             break;  
  35.   
  36.         // only play audio file the first time we animate the part  
  37.         if (r == 0 && mAudioPlayer != NULL && part.audioFile) {  
  38.             mAudioPlayer->playFile(part.audioFile);  
  39.         }<span style="white-space:pre">       </span>          
  40.   
  41.         for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {  
  42.             const Animation::Frame& frame(part.frames[j]);  
  43.             nsecs_t lastFrame = systemTime();  
  44.   
  45.             if (r > 0) {  
  46.                 glBindTexture(GL_TEXTURE_2D, frame.tid);  
  47.             } else {  
  48.                 if (part.count != 1) {  
  49.                     glGenTextures(1, &frame.tid);  
  50.                     glBindTexture(GL_TEXTURE_2D, frame.tid);  
  51.                     glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  
  52.                     glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
  53.                 }  
  54.                 initTexture(frame);  
  55.             }  
  56.   
  57.             if (!clearReg.isEmpty()) {  
  58.                 Region::const_iterator head(clearReg.begin());  
  59.                 Region::const_iterator tail(clearReg.end());  
  60.                 glEnable(GL_SCISSOR_TEST);  
  61.                 while (head != tail) {  
  62.                     const Rect& r(*head++);  
  63.                     glScissor(r.left, mHeight - r.bottom,  
  64.                             r.width(), r.height());  
  65.                     glClear(GL_COLOR_BUFFER_BIT);  
  66.                 }  
  67.                 glDisable(GL_SCISSOR_TEST);  
  68.             }  
  69.             glDrawTexiOES(xc, yc, 0, animation.width, animation.height);  
  70.             eglSwapBuffers(mDisplay, mSurface);  
  71.   
  72.             nsecs_t now = systemTime();  
  73.             nsecs_t delay = frameDuration - (now - lastFrame);  
  74.             //ALOGD("%lld, %lld", ns2ms(now - lastFrame), ns2ms(delay));  
  75.             lastFrame = now;  
  76.   
  77.             if (delay > 0) {  
  78.                 struct timespec spec;  
  79.                 spec.tv_sec  = (now + delay) / 1000000000;  
  80.                 spec.tv_nsec = (now + delay) % 1000000000;  
  81.                 int err;  
  82.                 do {  
  83.                     err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &spec, NULL);  
  84.                 } while (err<0 && errno == EINTR);  
  85.             }  
  86.   
  87.             checkExit();  
  88.         }  
  89.   
  90.         usleep(part.pause * ns2us(frameDuration));  
  91.   
  92.         // For infinite parts, we've now played them at least once, so perhaps exit  
  93.         if(exitPending() && !part.count)  
  94.             break;  
  95.     }  
  96.   
  97.     // free the textures for this part  
  98.     if (part.count != 1) {  
  99.         for (size_t j=0 ; j<fcount ; j++) {  
  100.             const Animation::Frame& frame(part.frames[j]);  
  101.             glDeleteTextures(1, &frame.tid);  
  102.         }  
  103.     }  
  104. }  
  105.   
  106. return false;  
首先,调用一堆EGL-api清理屏幕。然后执行for循环完成动画的显示。按照desc.txt中定义的片段的顺序进行动画绘制。每个片段中的png文件逐个显示。我们注意到,上面的代码中也出现了checkExit()和exitPending()这对函数,具体主要在讲android()时已经说明了。

到此,开机动画的显示流程就完成了。下面,分析一下开机动画是怎么停止的。

3 开机动画的停止

当SystemServer将系统中的关键服务启动完成后,会启动桌面启动器Launcher。Launcher启动后,会向ActivityManagerService发送一个Activity组件空闲通知,AMS收到该通知后,就会调用成员函数enableScreenAfterBoot()停止开机动画,以便让屏幕显示桌面。enableScreenAfterBoot()代码如下:

[html]  view plain  copy
  1. void enableScreenAfterBoot() {  
  2.         EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_ENABLE_SCREEN,  
  3.                 SystemClock.uptimeMillis());  
  4.         mWindowManager.enableScreenAfterBoot();  
  5.   
  6.         synchronized (this) {  
  7.             updateEventDispatchingLocked();  
  8.         }  
  9.     }  
可见,该函数进一步调用WindowManagerService对象mWindowManager的成员函数enableScreenAfterBoot。该函数代码如下:

[html]  view plain  copy
  1. public void enableScreenAfterBoot() {  
  2.         synchronized(mWindowMap) {  
  3.             ......  
  4.             if (mSystemBooted) {  
  5.                 return;  
  6.             }  
  7.             mSystemBooted = true;  
  8.             hideBootMessagesLocked();  
  9.             // If the screen still doesn't come up after 30 seconds, give  
  10.             // up and turn it on.  
  11.             mH.sendEmptyMessageDelayed(H.BOOT_TIMEOUT, 30*1000);  
  12.         }  
  13.   
  14.         mPolicy.systemBooted();  
  15.   
  16.         performEnableScreen();  
  17.     }  

成员变量mSystemBooted用来标识系统是否已经启动,true代表启动,false代表未启动。这里,应该是false。因此,程序会继续往下执行,先把mSystemBooted设为true,然后调用performEnableScreen。代码如下:

[html]  view plain  copy
  1. public void performEnableScreen() {  
  2.         synchronized(mWindowMap) {  
  3.             ......  
  4.             if (mDisplayEnabled) {  
  5.                 return;  
  6.             }  
  7.             if (!mSystemBooted && !mShowingBootMessages) {  
  8.                 return;  
  9.             }  
  10.   
  11.             // Don't enable the screen until all existing windows have been drawn.  
  12.             if (!mForceDisplayEnabled && checkWaitingForWindowsLocked()) {  
  13.                 return;  
  14.             }  
  15.   
  16.             if (!mBootAnimationStopped) {  
  17.                 // Do this one time.  
  18.                 try {  
  19.                     IBinder surfaceFlinger = ServiceManager.getService("SurfaceFlinger");  
  20.                     if (surfaceFlinger != null) {  
  21.                         //Slog.i(TAG, "******* TELLING SURFACE FLINGER WE ARE BOOTED!");  
  22.                         Parcel data = Parcel.obtain();  
  23.                         data.writeInterfaceToken("android.ui.ISurfaceComposer");  
  24.                         surfaceFlinger.transact(IBinder.FIRST_CALL_TRANSACTION, // BOOT_FINISHED  
  25.                                 data, null, 0);  
  26.                         data.recycle();  
  27.                     }  
  28.                 } catch (RemoteException ex) {  
  29.                     Slog.e(TAG, "Boot completed: SurfaceFlinger is dead!");  
  30.                 }  
  31.                 mBootAnimationStopped = true;  
  32.             }  
  33.   
  34.         ......  
  35.     }  
可以看到,通过Binder机制,向SurfaceFlinger服务发送一个“IBinder.FIRST_CALL_TRANSACTION”请求,通知SurfaceFlinger停止开机动画。

SurfaceFlinger中处理该请求的函数为bootFinished。代码如下:

[html]  view plain  copy
  1. void SurfaceFlinger::bootFinished()  
  2. {  
  3.     const nsecs_t now = systemTime();  
  4.     const nsecs_t duration = now - mBootTime;  
  5.     ALOGI("Boot is finished (%ld ms)", long(ns2ms(duration)) );  
  6.     mBootFinished = true;  
  7.   
  8.     // wait patiently for the window manager death  
  9.     const String16 name("window");  
  10.     sp<IBinder> window(defaultServiceManager()->getService(name));  
  11.     if (window != 0) {  
  12.         window->linkToDeath(static_cast<IBinder::DeathRecipient*>(this));  
  13.     }  
  14.   
  15.     // stop boot animation  
  16.     // formerly we would just kill the process, but we now ask it to exit so it  
  17.     // can choose where to stop the animation.  
  18.     property_set("service.bootanim.exit", "1");  
  19. }  
可以看到,该函数将属性“service.bootanim.exit”设置为"1"。在第2节分析android()代码的时候,我们讲到:当属性“service.bootanim.exit”值被设为"1"时,android()就会退出,开机动画显示自然也就结束了。由于android()退出且返回值为false,BootAnimation::threadLoop()线程也就结束了。再回到BootAnimation.cpp的main()函数中,threadLoop()线程结束,main函数也就结束,至此,bootanimaiton进程就自行结束,开机动画的显示完成了。

这里注意:在android之前版本中,bootFinished不是设置属性“service.bootanim.exit”,而是调用:

[html]  view plain  copy
  1. property_set("ctl.stop", "bootanim");  

这样,init进程会直接kill掉bootanimation进程,从而结束开机动画的显示。


4 开机音乐的实现

使用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.     mp->setDataSource(mfd, 0, 0x7ffffffffffffffLL);  
  10.     mp->setAudioStreamType(/*AUDIO_STREAM_MUSIC*/AUDIO_STREAM_SYSTEM);  
  11.     mp->prepare();  
  12.     mp->start();  
  13. }  
  14.   
  15. bool BootAnimation::soundstop()  
  16. {  
  17.     if (mp != NULL)   
  18.         mp->stop();  
  19. }  
在movie()中,播放开机动画前调用soundplay,结束时调用soundstop。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值