android 開機動畫和開機視頻流程分析基於android4.4

   android開機畫面是由應用程序bootanimation來負責顯示的,主要類BootAnimation.cpp直接繼承於android中定義的一個Thread,接地繼承了RefBase類,並且重寫了RefBase類的成員函數onFirstRef,因此,當一個BootAnimation對象第一次被引用的時,這個BootAnimation對象的成員函數onFirstRef就會被調用。
    當應用程序啓動時,是先從main開始,具體代碼如下:
        sp<BootAnimation> 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<BootAnimation> 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了,代碼架構也更改非常大,但是一些基礎的地方還是相同的,還是發出來作爲參考


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章