Android系统的开机画面显示过程分析

http://shyluo.blog.51cto.com/5725845/967046

BootAnimation类的成员函数movie的实现比较长,我们分段来阅读:

 
 
  1. bool BootAnimation::movie()   
  2. {   
  3.     ZipFileRO& zip(mZip);   
  4.    
  5.     size_t numEntries = zip.getNumEntries();   
  6.     ZipEntryRO desc = zip.findEntryByName("desc.txt");   
  7.     FileMap* descMap = zip.createEntryFileMap(desc);   
  8.     LOGE_IF(!descMap, "descMap is null");   
  9.     if (!descMap) {   
  10.         return false;   
  11.     }   
  12.    
  13.     String8 desString((char const*)descMap->getDataPtr(),   
  14.             descMap->getDataLength());   
  15.     char const* s = desString.string();   
  16.    
  17.     Animation animation;   
  18.    
  19.     // Parse the description file   
  20.     for (;;) {   
  21.         const char* endl = strstr(s, "\n");   
  22.         if (!endl) break;   
  23.         String8 line(s, endl - s);   
  24.         const char* l = line.string();   
  25.         int fps, width, height, count, pause;   
  26.         char path[256];   
  27.         if (sscanf(l, "%d %d %d", &width, &height, &fps) == 3) {   
  28.             //LOGD("> w=%d, h=%d, fps=%d", fps, width, height);   
  29.             animation.width = width;   
  30.             animation.height = height;   
  31.             animation.fps = fps;   
  32.         }   
  33.         if (sscanf(l, "p %d %d %s", &count, &pause, path) == 3) {   
  34.             //LOGD("> count=%d, pause=%d, path=%s"count, pause, path);   
  35.             Animation::Part part;   
  36.             part.count = count;   
  37.             part.pause = pause;   
  38.             part.path = path;   
  39.             animation.parts.add(part);   
  40.         }   
  41.         s = ++endl;   
  42.     }   
     从前面BootAnimation类的成员函数readyToRun的实现可以知道,如果目标设备上存在压缩文件/data/local/bootanimation.zip,那么BootAnimation类的成员变量mZip就会指向它,否则的话,就会指向目标设备上的压缩文件/system/media/bootanimation.zip。无论BootAnimation类的成员变量mZip指向的是哪一个压缩文件,这个压缩文件都必须包含有一个名称为“desc.txt”的文件,用来描述用户自定义的开机动画是如何显示的。
 
文件desc.txt的内容格式如下面的例子所示:
 
 
  1. 600 480 24   
  2. p   1   0   part1   
  3. p   0   10  part2   
        第一行的三个数字分别表示开机动画在屏幕中的显示宽度、高度以及帧速(fps)。剩余的每一行都用来描述一个动画片断,这些行必须要以字符“p”来开头,后面紧跟着两个数字以及一个文件目录路径名称。第一个数字表示一个片断的循环显示次数,如果它的值等于0,那么就表示无限循环地显示该动画片断。第二个数字表示每一个片断在两次循环显示之间的时间间隔。这个时间间隔是以一个帧的时间为单位的。文件目录下面保存的是一系列png文件,这些png文件会被依次显示在屏幕中。
 
       以上面这个desct.txt文件的内容为例,它描述了一个大小为600 x 480的开机动画,动画的显示速度为24帧每秒。这个开机动画包含有两个片断part1和part2。片断part1只显示一次,它对应的png图片保存在目录part1中。片断part2无限循环地显示,其中,每两次循环显示的时间间隔为10 x (1 / 24)秒,它对应的png图片保存在目录part2中。
       上面的for循环语句分析完成desc.txt文件的内容后,就得到了开机动画的显示大小、速度以及片断信息。这些信息都保存在Animation对象animation中,其中,每一个动画片断都使用一个Animation::Part对象来描述,并且保存在Animation对象animation的成员变量parts所描述的一个片断列表中。
       接下来,BootAnimation类的成员函数movie再断续将每一个片断所对应的png图片读取出来,如下所示:
 
 
  1. // read all the data structures   
  2. const size_t pcount = animation.parts.size();   
  3. for (size_t i=0 ; i<numEntries ; i++) {   
  4.     char name[256];   
  5.     ZipEntryRO entry = zip.findEntryByIndex(i);   
  6.     if (zip.getEntryFileName(entry, name, 256) == 0) {   
  7.         const String8 entryName(name);   
  8.         const String8 path(entryName.getPathDir());   
  9.         const String8 leaf(entryName.getPathLeaf());   
  10.         if (leaf.size() > 0) {   
  11.             for (int j=0 ; j<pcount ; j++) {   
  12.                 if (path == animation.parts[j].path) {   
  13.                     int method;   
  14.                     // supports only stored png files   
  15.                     if (zip.getEntryInfo(entry, &method, 0, 0, 0, 0, 0)) {   
  16.                         if (method == ZipFileRO::kCompressStored) {   
  17.                             FileMap* map = zip.createEntryFileMap(entry);   
  18.                             if (map) {   
  19.                                 Animation::Frame frame;   
  20.                                 frame.name = leaf;   
  21.                                 frame.map = map;   
  22.                                 Animation::Part& part(animation.parts.editItemAt(j));   
  23.                                 part.frames.add(frame);   
  24.                             }   
  25.                         }   
  26.                     }   
  27.                 }   
  28.             }   
  29.         }   
  30.     }   
  31. }   
      每一个png图片都表示一个动画帧,使用一个Animation::Frame对象来描述,并且保存在对应的Animation::Part对象的成员变量frames所描述的一个帧列表中。
 
        获得了开机动画的所有信息之后,接下来BootAnimation类的成员函数movie就准备开始显示开机动画了,如下所示:
 
 
  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));   

       前面的一系列gl函数首先用来清理屏幕,接下来的一系列gl函数用来设置OpenGL的纹理显示方式。
 
       变量xc和yc的值用来描述开机动画的显示位置,即需要在屏幕中间显示开机动画,另外一个变量frameDuration的值用来描述每一帧的显示时间,它是以纳秒为单位的。
       Region对象clearReg用来描述屏幕中除了开机动画之外的其它区域,它是用整个屏幕区域减去开机动画所点据的区域来得到的。
       准备好开机动画的显示参数之后,最后就可以执行显示开机动画的操作了,如下所示:
 
 
  1.     for (int i=0 ; i<pcount && !exitPending() ; i++) {   
  2.         const Animation::Part& part(animation.parts[i]);   
  3.         const size_t fcount = part.frames.size();   
  4.         glBindTexture(GL_TEXTURE_2D, 0);   
  5.    
  6.         for (int r=0 ; !part.count || r<part.count ; r++) {   
  7.             for (int j=0 ; j<fcount && !exitPending(); j++) {   
  8.                 const Animation::Frame& frame(part.frames[j]);   
  9.    
  10.                 if (r > 0) {   
  11.                     glBindTexture(GL_TEXTURE_2D, frame.tid);   
  12.                 } else {   
  13.                     if (part.count != 1) {   
  14.                         glGenTextures(1, &frame.tid);   
  15.                         glBindTexture(GL_TEXTURE_2D, frame.tid);   
  16.                         glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);   
  17.                         glTexParameterx(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);   
  18.                     }   
  19.                     initTexture(   
  20.                             frame.map->getDataPtr(),   
  21.                             frame.map->getDataLength());   
  22.                 }   
  23.    
  24.                 if (!clearReg.isEmpty()) {   
  25.                     Region::const_iterator head(clearReg.begin());   
  26.                     Region::const_iterator tail(clearReg.end());   
  27.                     glEnable(GL_SCISSOR_TEST);   
  28.                     while (head != tail) {   
  29.                         const Rect& r(*head++);   
  30.                         glScissor(r.left, mHeight - r.bottom,   
  31.                                 r.width(), r.height());   
  32.                         glClear(GL_COLOR_BUFFER_BIT);   
  33.                     }   
  34.                     glDisable(GL_SCISSOR_TEST);   
  35.                 }   
  36.                 glDrawTexiOES(xc, yc, 0, animation.width, animation.height);   
  37.                 eglSwapBuffers(mDisplay, mSurface);   
  38.    
  39.                 nsecs_t now = systemTime();   
  40.                 nsecs_t delay = frameDuration - (now - lastFrame);   
  41.                 lastFrame = now;   
  42.                 long wait = ns2us(frameDuration);   
  43.                 if (wait > 0)   
  44.                     usleep(wait);   
  45.             }   
  46.             usleep(part.pause * ns2us(frameDuration));   
  47.         }   
  48.    
  49.         // free the textures for this part   
  50.         if (part.count != 1) {   
  51.             for (int j=0 ; j<fcount ; j++) {   
  52.                 const Animation::Frame& frame(part.frames[j]);   
  53.                 glDeleteTextures(1, &frame.tid);   
  54.             }   
  55.         }   
  56.     }   
  57.    
  58.     return false;   
  59. }   
        第一层for循环用来显示每一个动画片断,第二层的for循环用来循环显示每一个动画片断,第三层的for循环用来显示每一个动画片断所对应的png图片。这些png图片以纹理的方式来显示在屏幕中。
 
        注意,如果一个动画片断的循环显示次数不等于1,那么就说明这个动画片断中的png图片需要重复地显示在屏幕中。由于每一个png图片都需要转换为一个纹理对象之后才能显示在屏幕中,因此,为了避免重复地为同一个png图片创建纹理对象,第三层的for循环在第一次显示一个png图片的时候,会调用函数glGenTextures来为这个png图片创建一个纹理对象,并且将这个纹理对象的名称保存在对应的Animation::Frame对象的成员变量tid中,这样,下次再显示相同的图片时,就可以使用前面已经创建好了的纹理对象,即调用函数glBindTexture来指定当前要操作的纹理对象。
        如果Region对象clearReg所包含的区域不为空,那么在调用函数glDrawTexiOES和eglSwapBuffers来显示每一个png图片之前,首先要将它所包含的区域裁剪掉,避免开机动画可以显示在指定的位置以及大小中。
        每当显示完成一个png图片之后,都要将变量frameDuration的值从纳秒转换为毫秒。如果转换后的值大小于,那么就需要调用函数usleep函数来让线程睡眠一下,以保证每一个png图片,即每一帧动画都按照预先指定好的速度来显示。注意,函数usleep指定的睡眠时间只能精确到毫秒,因此,如果预先指定的帧显示时间小于1毫秒,那么BootAnimation类的成员函数movie是无法精确地控制地每一帧的显示时间的。
        还有另外一个地方需要注意的是,每当循环显示完成一个片断时,需要调用usleep函数来使得线程睡眠part.pause * ns2us(frameDuration)毫秒,以便可以按照预先设定的节奏来显示开机动画。
        最后一个if语句判断一个动画片断是否是循环显示的,即循环次数不等于1。如果是的话,那么就说明前面为它所对应的每一个png图片都创建过一个纹理对象。现在既然这个片断的显示过程已经结束了,因此,就需要释放前面为它所创建的纹理对象。
        至此,第三个开机画面的显示过程就分析完成了。



 接下来,我们再继续分析第三个开机画面是如何停止显示的。

        从前面 Android系统默认Home应用程序(Launcher)的启动过程源代码分析 一文可以知道,当System进程将系统中的关键服务启动起来之后,就会将应用程序启动器(Launcher)启动起来。从 Android应用程序启动过程源代码分析 一文又可以知道,Android应用程序的启动过程实际上就是它的根Activity组件的启动过程。对于应用程序Launcher来说,它的根Activity组件即为Launcher组件。
        一个Activity组件在启动起来之后,就会被记录起来,等到它所运行在的主线程空闲的时候,这个主线程就会向ActivityManagerService发送一个Activity组件空闲的通知。由于应用程序Launcher是系统中第一个被启动的应用程序,即它的根Activity组件是系统中第一个被启动的Activity组件,因此,当ActivityManagerService接收到它的空闲通知的时候,就可以知道系统是刚刚启动起来的。在这种情况下,ActivityManagerService就会停止显示开机动画,以便可以在屏幕中显示应用程序Lancher的界面。
       从前面 Android应用程序消息处理机制(Looper、Handler)分析 一文可以知道,如果一个线程想要在空闲的时候处理一些事务,那么就必须要向这个线程的消息队列注册一个空闲消息处理器。自定义的空闲消息处理器灯必须要从MessageQueue.IdleHandler类继承下来,并且重写成员函数queueIdle。当一个线程空闲的时候,即消息队列中没有新的消息需要处理的时候,那些注册了的空闲消息处理器的成员函数queueIdle就会被调用。
       应用程序的主线程是通过ActivityThread类来描述的,它实现在文件frameworks/base/core/java/android/app/ActivityThread.java中。每当有一个新的Activity组件启动起来的时候,ActivityThread类都会向它所描述的应用程序主线程的消息队列注册一个类型为Idler的空闲消息处理器。这样一个应用程序的主线程就可以在空闲的时候,向ActivityManagerService发送一个Activity组件空闲通知,相当于是通知ActivityManagerService,一个新的Activity组件已经准备就绪了。
Idler类定义在frameworks/base/core/java/android/app/ActivityThread.java中, 它的成员函数queueIdle的实现如下所示: 
 
 
  1. public final class ActivityThread {   
  2.     ......   
  3.    
  4.     private final class Idler implements MessageQueue.IdleHandler {   
  5.         public final boolean queueIdle() {   
  6.             ActivityClientRecord a = mNewActivities;   
  7.             if (a != null) {   
  8.                 mNewActivities = null;   
  9.                 IActivityManager am = ActivityManagerNative.getDefault();   
  10.                 ActivityClientRecord prev;   
  11.                 do {   
  12.                     ......   
  13.                     if (a.activity != null && !a.activity.mFinished) {   
  14.                         try {   
  15.                             am.activityIdle(a.token, a.createdConfig);   
  16.                             a.createdConfig = null;   
  17.                         } catch (RemoteException ex) {   
  18.                         }   
  19.                     }   
  20.                     prev = a;   
  21.                     a = a.nextIdle;   
  22.                     prev.nextIdle = null;   
  23.                 } while (a != null);   
  24.             }   
  25.             ensureJitEnabled();   
  26.             return false;   
  27.         }   
  28.     }   
  29.    
  30.     ......   
  31. }   
       ActivityThread类有一个类型为ActivityClientRecord的成员变量mNewActivities,用来描述所有在当前应用程序主线程中新启动起来的Activity组件。这些新启动起来的Activity组件通过ActivityClientRecord类的成员变量nextIdle连接在一起。一旦当前应用程序主线程向ActivityManagerService发送了这些新启动的Activity组件的空闲通知之后,这些新启动起来的Activity组件就不会再被保存在ActivityThread类的成员变量mNewActivities中了,即每一个新启动的Activity组件只有一次机会向ActivityManagerService发送一个空闲通知。

 
       向ActivityManagerService发送一个Activity组件空闲通知是通过调用ActivityManagerService代理对象的成员函数activityIdle来实现的,而ActivityManagerService代理对象可以通过调用ActivityManagerNative类的静态成员函数getDefault来获得。
       ActivityManagerService代理对象的类型为ActivityManagerProxy,它的成员函数activityIdle实现在文件frameworks/base/core/java/android/app/ActivityManagerNative.java中,如下所示:
 
 
  1. class ActivityManagerProxy implements IActivityManager   
  2. {   
  3.     ......   
  4.    
  5.     public void activityIdle(IBinder token, Configuration config) throws RemoteException   
  6.     {   
  7.         Parcel data = Parcel.obtain();   
  8.         Parcel reply = Parcel.obtain();   
  9.         data.writeInterfaceToken(IActivityManager.descriptor);   
  10.         data.writeStrongBinder(token);   
  11.         if (config != null) {   
  12.             data.writeInt(1);   
  13.             config.writeToParcel(data, 0);   
  14.         } else {   
  15.             data.writeInt(0);   
  16.         }   
  17.         mRemote.transact(ACTIVITY_IDLE_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY);   
  18.         reply.readException();   
  19.         data.recycle();   
  20.         reply.recycle();   
  21.     }   
  22.    
  23.     ......   
  24. }   
      ActivityManagerProxy类的成员函数activityIdle实际上是向ActivityManagerService发送一个类型为ACTIVITY_IDLE_TRANSACTION的Binder进程间通信请求,其中,参数token用来描述与这个进程间通信请求所关联的一个Activity组件,在我们这个场景中,这个Activity组件即为应用程序Launcher的根Activity组件Launcher。

 
        类型为ACTIVITY_IDLE_TRANSACTION的Binder进程间通信请求是由ActivityManagerService类的成员函数activityIdle来处理的,如下所示:
 
 
  1. public final class ActivityManagerService extends ActivityManagerNative   
  2.         implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {   
  3.     ......   
  4.    
  5.     public final void activityIdle(IBinder token, Configuration config) {   
  6.         final long origId = Binder.clearCallingIdentity();   
  7.         mMainStack.activityIdleInternal(token, false, config);   
  8.         Binder.restoreCallingIdentity(origId);   
  9.     }   
  10.    
  11.     ......   
  12. }   
    ActivityManagerService类有一个类型为ActivityStack的成员变量mMainStack,它用来描述系统的Activity组件堆栈,它的成员函数activityIdleInternal的实现如下所示:
 
 
  1. public class ActivityStack {   
  2.     ......   
  3.    
  4.     final void activityIdleInternal(IBinder token, boolean fromTimeout,   
  5.             Configuration config) {   
  6.         ......   
  7.    
  8.         boolean enableScreen = false;   
  9.    
  10.         synchronized (mService) {   
  11.             ......   
  12.    
  13.             // Get the activity record.   
  14.             int index = indexOfTokenLocked(token);   
  15.             if (index >= 0) {   
  16.                 ActivityRecord r = (ActivityRecord)mHistory.get(index);                   
  17.                 ......   
  18.    
  19.                 if (mMainStack) {   
  20.                     if (!mService.mBooted && !fromTimeout) {   
  21.                         mService.mBooted = true;   
  22.                         enableScreen = true;   
  23.                     }   
  24.                 }   
  25.             }   
  26.    
  27.             ......   
  28.         }   
  29.    
  30.         ......   
  31.    
  32.         if (enableScreen) {   
  33.             mService.enableScreenAfterBoot();   
  34.         }   
  35.     }   
  36.    
  37.     ......   
  38. }           

        参数token用来描述刚刚启动起来的Launcher组件,通过它来调用函数indexOfTokenLocked就可以得到Launcher组件在系统Activity组件堆栈中的位置index。得到了Launcher组件在系统Activity组件堆栈中的位置index之后,就可以在ActivityStack类的成员变量mHistory中得到一个ActivityRecord对象r。这个ActivityRecord对象r同样是用来描述Launcher组件的。
        ActivityStack类的成员变量mMainStack是一个布尔变量,当它的值等于true的时候,就说明当前正在处理的ActivityStack对象是用来描述系统的Activity组件堆栈的。 ActivityStack类的另外一个成员变量mService指向了系统中的ActivityManagerService服务。ActivityManagerService服务有一个类型为布尔值的成员变量mBooted,它的初始值为false,表示系统尚未启动完成。
        从前面的调用过程可以知道,参数fromTimeout的值等于false。在这种情况下,如果ActivityManagerService服务的成员变量mBooted也等于false,那么就说明应用程序已经启动起来了,即说明系统已经启动完成了。这时候ActivityManagerService服务的成员变量mBooted以及变量enableScreen的值就会被设置为true。
        当变量enableScreen的值等于true的时候,ActivityStack类就会调用ActivityManagerService服务的成员函数enableScreenAfterBoot停止显示开机动画,以便可以将屏幕让出来显示应用程序Launcher的界面。
        ActivityManagerService类的成员函数enableScreenAfterBoot的实现如下所示:
 
 
  1. public final class ActivityManagerService extends ActivityManagerNative   
  2.         implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {   
  3.     ......   
  4.    
  5.     void enableScreenAfterBoot() {   
  6.         EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_ENABLE_SCREEN,   
  7.                 SystemClock.uptimeMillis());   
  8.         mWindowManager.enableScreenAfterBoot();   
  9.     }   
  10.    
  11.     ......   
  12. }   
        ActivityManagerService类的成员变量mWindowManager指向了系统中的Window管理服务WindowManagerService,ActivityManagerService服务通过调用它的成员函数enableScreenAfterBoot来停止显示开机动画。

 
       WindowManagerService类的成员函数enableScreenAfterBoot的实现如下所示:
 
 
  1. public class WindowManagerService extends IWindowManager.Stub   
  2.         implements Watchdog.Monitor {   
  3.     ......   
  4.    
  5.     public void enableScreenAfterBoot() {   
  6.         synchronized(mWindowMap) {   
  7.             if (mSystemBooted) {   
  8.                 return;   
  9.             }   
  10.             mSystemBooted = true;   
  11.         }   
  12.    
  13.         performEnableScreen();   
  14.     }   
  15.    
  16.     ......   
  17. }   
       WindowManagerService类的成员变量mSystemBooted用来记录系统是否已经启动完成的。如果已经启动完成的话,那么这个成员变量的值就会等于true,这时候WindowManagerService类的成员函数enableScreenAfterBoot什么也不做就返回了,否则的话,WindowManagerService类的成员函数enableScreenAfterBoot首先将这个成员变量的值设置为true,接着再调用另外一个成员函数performEnableScreen来执行停止显示开机动画的操作。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值