在上篇文章《Android5.1開機畫面顯示工作流程分析》中,詳細分析了Android開機動畫顯示的工作流程。其中提到了每個開機動畫壓縮檔案中必須包含一個描述檔案desc.txt,該檔案用來描述開機動畫具體是怎麼樣顯示的。這篇文章就對desc.txt進行一個詳細的解讀。
1 desc.txt檔案格式分析
desc.txt檔案由若幹行組成,每一行代表一種描述。下面以一個具體的例子為例,具體說明
480 640 20
p 1 0 folder1
p 2 20 folder2
c 0 0 folder3
c 1 0 folder4
第1行用來描述開機動畫在螢幕顯示的大小及速度。具體為:開機動畫的寬度為480個像素,高度為640個像素,顯示頻率為每秒20幀,即每幀顯示1/20秒。
下面的每一行代表一個片段,顯示的時候會按照順序從上到下依次顯示。第1個字元為片段類型,有'c'和'p'兩種,兩者的差別後面會結合代碼說明。
第2個數字為該片段重複顯示的次數,如果為‘0’,表示會無限重複顯示;第3個數字為兩次顯示之間的間隔,機關為第一行中定義的每幀顯示的時間;第4個字元串為該片段所在的檔案夾,一個片段可以由多個png圖檔組成,都存放在folder檔案夾中。
“p 1 0 folder1”代表該片段顯示1次,與下一個片段間隔0s,該片段的顯示圖檔路徑為bootanimation.zip/folder1。
“p 2 20 folder2”代表該片段顯示2次,且兩次之間顯示的間隔為20*(1/20)=1s,與下一個片段間隔20*(1/20)=1s,該片段的顯示圖檔路徑為bootanimation.zip/folder2。
“c 0 0 folder3”代表該片段無限循環顯示,且兩次顯示的間隔為0s,與下一個片段間隔0s,該片段的顯示圖路徑為bootanimation.zip/folder3。
“c 1 10 folder4”代表該片段顯示1次,顯示後暫停10*(1/20)=0.5s,該片段的顯示圖路徑為bootanimation.zip/folder4。
2 "p"片段和“c”片段的差別
在早期Android版本中隻有“p”片段,且movie()中的顯示代碼如下:
for (int i=0 ; i<pcount && !exitPending() ; i++) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size();
glBindTexture(GL_TEXTURE_2D, 0);
for (int r=0 ; !part.count || r<part.count ; r++) {
for (int j=0 ; j<fcount && !exitPending(); j++) {
const Animation::Frame& frame(part.frames[j]);
.......
.....
}
裡面的主要參數和函數說嗎如下:
pcount---顯示片段的數量,比如上面的例子,pcount=4
p.count---該片段的重複顯示次數。
fcount---該片段中png圖檔的數量
exitPending()---如果SurfaceFlinger服務通知bootanimation停止顯示動畫,則該函數傳回值為true,否則為false。
第一個for循環用于順序顯示所有片段,第二個for循環用于重複顯示該片段,第三個for循環用于順序顯示該片段中所有的png圖檔。
分析代碼,可知:若exitPending()傳回值為true,即SurfaceFlinger服務要求bootanimation停止顯示動畫,則不管目前顯示到哪個片段或png圖檔,都會導緻退出for循環,進而停止開機動畫的顯示。
在Android5.1中,加入了“c”片段。對與以"c"辨別的片段,即使exitPending()傳回值為true,也會繼續顯示。
我們分析一下源碼,首先看一下movie()中解析desc.txt的代碼:
// Parse the description file
for (;;) {
......
if (sscanf(l, "%d %d %d %d", &width, &height, &fps, &flg) >= 3) {
animation.width = width;
animation.height = height;
animation.fps = fps;
}
else if (sscanf(l, " %c %d %d %s #%6s", &pathType, &count, &pause, path, color) >= 4) {
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;
}
可以看到,如果pathType==‘c’,part.playUntilComplete等于true,否則為false。接着,看一下顯示代碼:
for (size_t i=0 ; i<pcount ; i++) {
const Animation::Part& part(animation.parts[i]);
const size_t fcount = part.frames.size();
glBindTexture(GL_TEXTURE_2D, 0);
for (int r=0 ; !part.count || r<part.count ; r++) {
// Exit any non playuntil complete parts immediately
if(exitPending() && !part.playUntilComplete)
break;
......
for (size_t j=0 ; j<fcount && (!exitPending() || part.playUntilComplete) ; j++) {
......
checkExit();
}
usleep(part.pause * ns2us(frameDuration));
// For infinite parts, we've now played them at least once, so perhaps exit
if(exitPending() && !part.count)
break;
}
......
}
可以看到,如果exitPending()傳回值為true且part.playUntilComplete=false,則會break。即:當SurfaceFlinger服務要求bootanimation停止顯示動畫時,以‘p’辨別的片段會停止,而以'c'辨別的片段會繼續顯示。這就是兩者之間的主要差別。
這裡有個問題:重複循環顯示的'c'辨別片段,會不受任何限制的一直顯示下去,這顯然是不合适的。
于是在第二個for循環體最後,有如下代碼:
// For infinite parts, we've now played them at least once, so perhaps exit
if(exitPending() && !part.count)
break;
意思是,如果檢測到SurfaceFlinger服務要求bootanimation停止顯示,且該片段的顯示次數為'0',即重複循環顯示,則會break停止顯示。
我猜想"c"辨別的意思是continue,即:即使SurfaceFlinger要求bootanimation停止動畫,bootanimation也不會立刻停止動畫,它會等c辨別片段都顯示完畢後,再停止。
這樣,我們可以利用'c'和'p'片段的差別,設計出更靈活的開機動畫。