android 开机动画和开机视频流程分析基于android4.4

   android 开机画面是由应用程序bootanimation来负责显示的,主要类BootAnimation.cpp直接继承于android中定义的一个Thread, 接地继承了RefBase类,并且重写了RefBase类的成员函数onFirstRef,因此,当一个BootAnimation对象第一次被引用的时,这个BootAnimation对象的成员函数onFirstRef就会被调用。
    当应用程序启动时,是先从main开始,具体代码如下:
        sp boot = new BootAnimation();
        //add video boot
        memset(value,0,sizeof(value));
        property_get("service.bootvideo", value, "0");
        gUseBootVideo = (atoi(value) == 1 ? true : false);

        if(gUseBootVideo){
           // boot->setBootVolume();
           /* modify,bootanimation path */
           /*before modify code(property_set("service.bootvideo", "2");)*/
           if (access(USER_BOOTVIDEO_FILE, R_OK) == 0) {
               property_set("service.bootvideo", "2");
           }else{
               gUseBootVideo = false;
           }
           /*modify end */
        }
如果access(USER_BOOTVIDEO_FILE, R_OK) == 0),即bootvideo文件存在则会执行property_set("service.bootvideo", "2"),则启动系统内的视频播放应用程序LibPlayer播放开机视频,同时gUseBootVideo =true代表bootanimation播放的是开机视频。否则播放开机动画。
   所以当开机视频文件bootvideo存在时,应用程序bootanimation会做2件事情:
       一:执行到sp boot = new BootAnimation();时会调用BootAnimation对象的成员函数onFirstRe,在onFirstRe里会执行线程的run方法,然后会调用动画的android或movie方法来显示动画(既然是为了播放开机视频,开机动画就没必要播放,后续在播放动画的方法中必须跳过GLES的绘图逻辑以免出现动画和视频同时出现的BUG)。
      二:执行 到property_set("service.bootvideo", "2");时,则启动系统内的视频播放应用程序LibPlayer播放开机视频。


      之前说到BootAnimation对象的成员函数onFirstRe执行后会经过一系列的调用最终执行到播放开机动画,具体流程如下:
1.在onFirstRe中调用 run("BootAnimation", PRIORITY_DISPLAY);创建线程
2.这个线程在第一次运行之前,会调用BootAnimation类的成员函数readyToRun来执行一些初始化工作,这里我们需要注意mAndroidAnimation这个bool,它是来决定后面的动画走的是原生android还是我们自定义的movie
3.调用BootAnimation类的成员函数htreadLoop来显示第开机动画,这里我们走的是movie。
 
  1. bool BootAnimation::threadLoop()  
  2. {  
  3.     bool r;  
  4.     if (mAndroidAnimation) {  
  5.         r = android();  
  6.     } else {  
  7.         r = movie();  
  8.     }  
  9.    .............
  10.   return r;
  11. }  
4.调用movie来解压bootanimation.zip,并通过解析 desc.txt 显示里面一张张的图片。 desc.txt  是 用来描述用户自定义的开机动画是如何显示的,格式如下:
     
  1. 600 480 24  
  2. p   1   0   part1  
  3. p   0   10  part2 

具体的书写格式j及含义我就不多说了自行谷歌,但是需要大家注意的一点就是无论是什么样的 desc.txt  它的最后一行应该基本都是
  1. p   0   xx  xxxx 
第二个参数0代表的就是part文件夹中的图片是无限循环显示的,我们看到的情况就是屏幕上显示了一张静态的图片。为什么要无限循环显示?
    答案就是 bootanimation在等一个消息,它在等launcher是否已经准备好了,如果launcher准备好了,那么它就停止循环,让launcher显示出来,否则就一直循环播放,直到launcher准备好。而launcher准备好的消息最终是由 SurfaceFlinger::bootFinished()  中的一个prop属性来发出的,而动画在循环播放中会不停的通过BootAnimation的checkExit来读取这个属性值,如果为真则调用其父类 Thread的函数requestExit退出线程,并且break退出for循环,最后返回false结束播放。
    
-----------------------------------------------------分割线--------针对开机动画卡在logo-----------------------------------------------------------------------
    movie中我们只关心动画的能否正常显示(至于其他的绘图呀什么的太难了,表示不懂),以及动画能否正常退出。好了,假如是执行的是原生的android源码并且 bootanimation.zip是正确的,那么一切都好说,显示肯定是没有问题的。但是,万能的ROM厂商不答应啊,开机动画太单调了,还是换个广告植入下吧~于是动不动就推送一个bootanimation.zip给你,所以你每次开机就会看到不同的开机动画。于是问题就来了,广告发的太多万一哪天不小心推送了一个错误的bootanimation.zip给用户,则很有可能导致卡在开机动画那里了,从而会导致launcher起不来。所以客户提出的需求就是:不管bootanimation.zip对不对,你得让launcher起来,动画播不播就别管了。
    下面分析下错误的bootanimation.zip的几种情况:
    ①没有 desc.txt 文件,源码中已经进行了判断,直接返回
    ZipFileRO& zip(mZip);
    size_t numEntries = zip.getNumEntries();
    ZipEntryRO desc = zip.findEntryByName("desc.txt");
    FileMap* descMap = zip.createEntryFileMap(desc);
    ALOGE_IF(!descMap, "descMap is null");
    if (!descMap) {
        return false;
    }
 
   ② desc.txt 文件中定义的图片包名和实际的不匹配(如 定义的是part1 ,但是实际的是part),而movie的for循环语句分析完成desc.txt文件的内容后,就得到了开机动画的显示大小、速度以及片断信息,这些信息都保存在Animation对象animation中,而后面的绘图就是根据这些信息来完成的。而错误的bootanimation.zip会导致 const   size_t  pcount = animation.parts.size() 及const size_t fcount = part.frames.size(); 均为0,而源码中并没有对这种情况进行判断所以会造成movie中的流程不正确导致屏幕一直卡在logo。
    movie中最后有一个三层的for循环,主要工作就是根据之前拿到的animation来进行绘图工作:
    当 pcount =0时,会直接跳过for循环,直接返回false
    当 fcount =0时,无法进入第三个for循环,会在第二个for那里进行无限循环!

  由于我手上的代码并不是原生的android源码,而是项目code,所以关于画面显示的原理可能不太一样。我们的开机视频及开机动画是在Video层播放的,而launcher的显示是在OSD层,所以在视频和动画播放结束后需要将display_mode从Video层切换到OSD层,否则就算launcher正常启动了我们还是会看到屏幕一直卡在开机logo。
    所以之前说的那2种出错的情况不能直接返回,而是需要按照正常流程那样在 SurfaceFlinger::bootFinished()  调用后进行一个setDisPlayMode操作,再返回。这样就可以避免屏幕一直卡在开机logo。
    针对这些情况方法都是一样的:先调用用setDisPlayMode然后通过checkExit循环判断是否已接收到 bootFinished ,若接收到直接返回退出播放。例如:
    int switch_logo_flag = 0;  
    while(!descMap&&!gUseBootVideo){
       if(switch_logo_flag == 0){
          property_set(RUNNING_PROP_NAME,"running");
          switch_logo_flag=1;
       }
       checkExit();
       if(exitPending()){
          ALOGD("======tzr=====descMap is null ,return \n");
          return false;
       }  
    }


------------------------------------------------------------开机视频播放过程中出现黑屏------------------------------------------------------------------------
      如果视频长度超过10s, 在播放开机视频的10后就会出现屏幕一直黑屏,直到launcher显示出来。
     根据log及之前的开机动画分析可知,开机视频的流程与开机动画相比就是增加了用libPlayer播放指定文件夹中的视频文件,而当开机的准备工作完成后会执行 SurfaceFlinger::bootFinished()   会设置service.bootanim.exit=1来通知应用程序bootanimation可以停止播放动画了,然后setDisplayMode以便让launcher显示出来。但是如果播放的是长度较长的视频,在接到 SurfaceFlinger 的通知后不能停止播放,必须在播放结束后才能执行setDisplayMode。
      而播放10s后的黑屏问题就是因为恰好执行了执行 SurfaceFlinger::bootFinished()   然后bootanimation立即调用了setDisplayMode。既然问题的原因已经查明,修改的方法很简单,就是当接收到service.bootanim.exit=1时,不要立即setDisplayMode,而是要等到接收到开机视频播放完成的消息后再去设置。
接收和设置的代码全部在函数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(gUseBootVideo){
// 下面这个while循环就是为了判断是否已经执行了SurfaceFlinger::bootFinished(),如果是则退出循环执行下面的代码,否则就一直循环等待
        do{
            usleep(10*1000);
            memset(value,0,sizeof(value));
            property_get(EXIT_PROP_NAME, value, "0");
            exitnow = atoi(value);
          }while(!exitnow);
    }

    if (exitnow) {

        if(gUseBootVideo){
-               property_set("service.bootvideo.exit", "1"); / /这里是修改前的代码,通过这个属性设置会执行set_display_mode.sh
            do{
                memset(value,0,sizeof(value));
                property_get("init.svc.bootvideo", value, "NULL"); / /这个属性init.svc.bootvideo=stopped代表视频播放结束了
              }while(!strcmp(value,"running"));
+               property_set("service.bootvideo.exit", "1");
        }

        requestExit();
    }
}



这篇文章是之前很早写的基于android4.4版本,现在项目都已经基于android8.1了,代码架构也更改非常大,但是一些基础的地方还是相同的,还是发出来作为参考


你可能感兴趣的:(android,framework)