网上有很多关于android开机动画显示的分析,但大部分是针对于android的早期版本。在android5.1中,开机动画显示的工作流程做了一些修改,下面就针对android5.1,分析一下开机动画的启动、显示和停止的整个过程。 1. bootanimation应用的启动过程 Android系统开机动画的显示是由bootanimation应用实现的。 bootanimation在init.rc中的定义如下: service bootanim /system/bin/bootanimation class core user graphics group graphics audio disabled oneshot 可见,由于设置为'disable',该应用在init启动过程中是不会启动的,需要其他地方显示的调用才能启动。那是什么时候启动的呢?当SurfaceFlinger服务启动时,会修改系统属性值ctl.start,通知init进程启动bootanimation。 在早期的Android版本中,SurfaceFlinger服务是由SystemServer启动的。但在Android5.1中,该服务是init进程启动过程中就启动了。在init.rc中能看到对该服务的描述: service surfaceflinger /system/bin/surfaceflinger class core user system group graphics drmrpc onrestart restart zygote SurfaceFlinger服务源码路径为:frameworks\native\services\surfaceflinger 服务的入口在main_surfaceflinger.cpp中,具体为: int main(int, char**) { // When SF is launched in its own process, limit the number of // binder threads to 4. ProcessState::self()->setThreadPoolMaxThreadCount(4); // start the thread pool sp 主要工作是:新建一个SurfaceFlinger对象,然后调用其中的init()方法,最后调用其中的run()方法。下面主要看一下SurfaceFlinger::init()方法,具体代码为: void SurfaceFlinger::init() { ALOGI( 'SurfaceFlinger's main thread ready to run. ' 'Initializing graphics H/W...'); ...... // start boot animation startBootAnim();} 可以看到,最后调用了startBootAnim()。该函数代码如下:void SurfaceFlinger::startBootAnim() { // start boot animation property_set('service.bootanim.exit', '0'); property_set('ctl.start', 'bootanim');} 可见,将系统属性ctl.start的值设置为'bootanim'。 回到init进程的init.c的main函数中: int main(int argc, char **argv) {.......for(;;) {....... nr = poll(ufds, fd_count, timeout); if (nr <= 0) continue; for (i = 0; i < fd_count; i++) { if (ufds[i].revents & POLLIN) { if (ufds[i].fd == get_property_set_fd()) handle_property_set_fd(); else if (ufds[i].fd == get_keychord_fd()) handle_keychord(); else if (ufds[i].fd == get_signal_fd()) handle_signal(); } } }} 可以看到,init进程会使用poll机制来轮询事件,其中一个事件是系统属性值被修改。得到该事件后,会执行handle_property_set_fd(),代码如下:if(memcmp(msg.name,'ctl.',4) == 0) { // Keep the old close-socket-early behavior when handling // ctl.* properties. close(s); if (check_control_mac_perms(msg.value, source_ctx)) { handle_control_message((char*) msg.name + 4, (char*) msg.value); } else { ERROR('sys_prop: Unable to %s service ctl [%s] uid:%d gid:%d pid:%d\n', msg.name + 4, msg.value, cr.uid, cr.gid, cr.pid); } } 该函数会进一步执行handle_control_message(),传入的参数msg.name=ctl.start,msg.value=bootanim。 void handle_control_message(const char *msg, const char *arg){ if (!strcmp(msg,'start')) { msg_start(arg); } else if (!strcmp(msg,'stop')) { msg_stop(arg); } else if (!strcmp(msg,'restart')) { msg_restart(arg); } else { ERROR('unknown control msg '%s'\n', msg); }} 由于msg == 'start',handle_control_message进一步执行msg_start(),且传入的arg参数等于bootanim。msg_start代码如下:static void msg_start(const char *name){ struct service *svc = NULL; char *tmp = NULL; char *args = NULL; if (!strchr(name, ':')) svc = service_find_by_name(name); else { tmp = strdup(name); if (tmp) { args = strchr(tmp, ':'); *args = '\0'; args++; svc = service_find_by_name(tmp); } } if (svc) { service_start(svc, args); } else { ERROR('no such service '%s'\n', name); } if (tmp) free(tmp);} 该函数首先调用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函数: 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 Main函数首先获取属性'debug.sf.nobootanimation'的值并判断,如果为1,函数退出,开机动画就不会显示了。如果为0,会开启一个binder线程池,用来在开机动画的过程中,与SurfaceFlinger通信,接着创建一个BootAnimation对象,该对象就是用来显示开机动画的。下面看一下BootAnimation类的声明: class BootAnimation : public Thread, public IBinder::DeathRecipient{public: BootAnimation(); virtual ~BootAnimation(); .......private: virtual bool threadLoop(); virtual status_t readyToRun(); virtual void onFirstRef(); virtual void binderDied(const wp 可见,BootAnimation类继承了Thread类和IBinder::DeathRecipient类,其中几个重写函数的说明如下:onFirstRef() ----- 属于其父类RefBase,该函数在强引用sp新增引用计数時调用,就是当有sp包装的类初始化的时候调用; binderDied() ----- 当对象死掉或者其他情况导致该Binder结束时,就会回调binderDied()方法; readyToRun() ----- Thread执行前的初始化工作; threadLoop() ----- 每个线程类都要实现的,在这里定义thread的执行内容。这个函数如果返回true,且没有requestExist()没有被调用,则该函数会再次执行;如果返回false,则threadloop中的内容仅仅执行一次,线程就会退出。 其他主要函数的说明如下: android() ----- 显示系统默认的开机画面; movie() ----- 显示用户自定义的开机动画。 BootAnimationL类的构造函数: BootAnimation::BootAnimation() : Thread(false), mZip(NULL),mfd(-1) { mSession = new SurfaceComposerClient();} 主要是new一个SurfaceComposerClient对象,用来和SurfaceFlinger进行binder进程间通信。 由于在BootAnimation_main.cpp的main函数创建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); }} 该函数启动了一个BootAnimation线程,用于显示开机动画。由于BootAnimation继承了Thread类,当调用父类的run()时,会在在这个线程运行前,调用readyToRun(),进行一些初始化工作。status_t BootAnimation::readyToRun() { mAssets.addDefaultAssets(); sp 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--如果设备的加密功能已经开启,或者设备正在进行加密,则读取加密开机动画文件,路径为 #define SYSTEM_ENCRYPTED_BOOTANIMATION_FILE '/system/media/bootanimation-encrypted.zip' 2--OEM厂商指定的开机动画,路径为: #define OEM_BOOTANIMATION_FILE '/oem/media/bootanimation.zip' 3--系统开机动画,路径为:#define SYSTEM_BOOTANIMATION_FILE '/system/media/bootanimation.zip' 线程的初始化工作完成后,就要进入线程的主体函数,完成开机动画的绘制和显示。具体函数为threadLoop(): bool BootAnimation::threadLoop(){ bool r; // We have no bootanimation file, so we use the stock android logo // animation. if (mZip == NULL) { r = android(); } else { r = movie(); } eglMakeCurrent(mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); eglDestroyContext(mDisplay, mSurface); mFlingerSurface.clear(); mFlingerSurfaceControl.clear(); eglTerminate(mDisplay); IPCThreadState::self()->stopProcess(); return r;} 这个函数流程比较简单,首先判断自定义的开机动画文件mZip是否存在,如果存在就调用movie()完成自定义开机画面的显示;如果不存在,调用android()完成系统默认开机画面的显示。然后进行开机动画显示后的销毁、释放工作,主要就是readyToRun中初始化的一些EGL对象。最后终止线程,并return。注意,movie()和android()的返回值都是false,因此线程结束也会返回false。threadLoop()函数如果返回值为false,则该函数中的内容只会执行一次;如果返回true,则会不停的执行。这里返回false,因此只会执行一次。 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;} 首先,调用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()函数。 void BootAnimation::checkExit() { // Allow surface flinger to gracefully request shutdown char value[PROPERTY_VALUE_MAX]; property_get(EXIT_PROP_NAME, value, '0'); int exitnow = atoi(value); if (exitnow) { requestExit(); if (mAudioPlayer != NULL) { mAudioPlayer->requestExit(); } }} 首先调用property_get获取属性EXIT_PROP_NAME的值。 #define EXIT_PROP_NAME 'service.bootanim.exit' 然后判断该值,如果为1,则调用requestExit()要求退出当前线程,该函数是异步的。 回到android()代码: while (!exitPending()); 调用exitPending(),改函数判断requestExit()是否被调用过,如果调用过则返回true,否则为false。这样,当属性“service.bootanim.exit”值被设为'1'时,android()就会调用requestExit(),exitPending()返回值为true。于是do...while()循环就会退出,开机动画绘制就会结束。 至于什么时候是哪个服务将属性“service.bootanim.exit”的值设置为1的,我们后面讲开机动画的停止的时候会提到。 下面分析一下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; } } 这段代码作用是读取开机动画文件mZip中的描述文件“desc.txt”。每个动画文件压缩包中必须要包含一个desc.txt,该文件用来描述开机动画如何显示。下面以一个示例来分析一下该文件:480 800 30p 1 0 folder0 p 0 10 folder1 第一行:前两个是开机动画在屏幕上显示的像素大小,分别为宽度和高度。第三个数字为每秒显示的帧数。 下面两行描述开机动画文件及显示的次数和间隔时间,每一行为一个片段。第一项类型,如p或c;第二项为该动画文件显示的次数,“0”表示重复显示;第三项为两次显示的间隔时间;第四项为动画文件。比如,'p 1 0 folder0'表示folder0文件夹中的动画只显示一次;”p 0 10 folder1“表示folder1中的动画文件重复显示,且两次显示的间隔时间为10秒。 // Parse the description file 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 %d', &width, &height, &fps, &flg) >= 3) { //ALOGD('> w=%d, h=%d, fps=%d, flg=%d', width, height, fps, flg); 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; } 上面这段代码主要是解析上面读取的desc.txt,并将相关参数保存到Animation对象animation中。 // 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 上面这段代码是读取每个片段中的png图片,并保存在animation.parts.frames中。下面就开始将开机动画绘制到屏幕上,代码如下: // clear screen glShadeModel(GL_FLAT); glDisable(GL_DITHER); glDisable(GL_SCISSOR_TEST); glDisable(GL_BLEND); glClear(GL_COLOR_BUFFER_BIT); eglSwapBuffers(mDisplay, mSurface); 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 lastFrame = systemTime(); 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 到此,开机动画的显示流程就完成了。下面,分析一下开机动画是怎么停止的。 3 开机动画的停止 当SystemServer将系统中的关键服务启动完成后,会启动桌面启动器Launcher。Launcher启动后,会向ActivityManagerService发送一个Activity组件空闲通知,AMS收到该通知后,就会调用成员函数enableScreenAfterBoot()停止开机动画,以便让屏幕显示桌面。enableScreenAfterBoot()代码如下: void enableScreenAfterBoot() { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_ENABLE_SCREEN, SystemClock.uptimeMillis()); mWindowManager.enableScreenAfterBoot(); synchronized (this) { updateEventDispatchingLocked(); } } 可见,该函数进一步调用WindowManagerService对象mWindowManager的成员函数enableScreenAfterBoot。该函数代码如下:public void enableScreenAfterBoot() { synchronized(mWindowMap) { ...... if (mSystemBooted) { return; } mSystemBooted = true; hideBootMessagesLocked(); // If the screen still doesn't come up after 30 seconds, give // up and turn it on. mH.sendEmptyMessageDelayed(H.BOOT_TIMEOUT, 30*1000); } mPolicy.systemBooted(); performEnableScreen(); } 成员变量mSystemBooted用来标识系统是否已经启动,true代表启动,false代表未启动。这里,应该是false。因此,程序会继续往下执行,先把mSystemBooted设为true,然后调用performEnableScreen。代码如下: public void performEnableScreen() { synchronized(mWindowMap) { ...... if (mDisplayEnabled) { return; } if (!mSystemBooted && !mShowingBootMessages) { return; } // Don't enable the screen until all existing windows have been drawn. if (!mForceDisplayEnabled && checkWaitingForWindowsLocked()) { return; } 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; } ...... } 可以看到,通过Binder机制,向SurfaceFlinger服务发送一个“IBinder.FIRST_CALL_TRANSACTION”请求,通知SurfaceFlinger停止开机动画。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 可以看到,该函数将属性“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”,而是调用: property_set('ctl.stop', 'bootanim'); 这样,init进程会直接kill掉bootanimation进程,从而结束开机动画的显示。 4 开机音乐的实现 使用MediaPlay实现,思路大致如下: Makefile文件中添加: LOCAL_SHARED_LIBRARIES += \ libmedia #include 在movie()中,播放开机动画前调用soundplay,结束时调用soundstop。 |
|
来自: tongkaibao > 《现代科技》