讀古今文學網 > Android程序設計:第2版 > Android GUI架構 >

Android GUI架構

Android環境給Java生態系統增加了另一種圖形用戶界面(GUI)工具,AWT、Swing、SWT、LWUIT等GUI工具箱又新添了一員。如果你使用過其中一種,那Android UI框架看起來會很熟悉。和這些工具類似,Android環境中新增加的這種工具也是單線程的、事件驅動的,是構建在可嵌入組件庫上的。

和其他Java UI框架一樣,Android UI框架採用的也是如圖6-1所示的常見的MVC模式(Model-View-Controller,模型-視圖-控制器)進行組織的。它提供架構和工具來構建控制器以處理用戶的輸入(如按鍵和在屏幕上輸入tab鍵),構建視圖把圖形信息渲染到屏幕上。

模型

模型(Model)是應用的核心,即應用真正要做的事情。例如,它可以是設備上的音樂數據庫和播放音樂的代碼;它也可以是聯繫人列表和給這些聯繫人打電話或發送信息的代碼。模型是本書後面章節的一大主題。

圖6-1:MVC(模型-視圖-控制器)概念

雖然特定應用的視圖和控制器必然反映了它們所基於的模型,但一個模型可能會在多個不同的應用中使用。例如,MP3播放器及把MP3文件轉換成WAV文件的應用。對於這兩種應用,模型都包括MP3文件格式。但是,前一個應用還包含了用戶熟悉的Stop、Start和Pause控制按鈕,還可以控制音調。後一個應用可能不會播放任何聲音。相反,它包含對如比特率這類特徵的控制。數據是模型的全部。

視圖

視圖(View)是模型的可視化形態。更通俗地說,視圖是應用中負責渲染界面、播放音頻、觸摸反饋等功能對應的部分。Android UI框架的圖形部分,在第8章將詳細描述,其組成是一組視圖類的子類,這些類按照樹的形式被組織在了一起。在圖形上,每個對象代表屏幕上的一個矩形區域,完全包含在其父節點的矩形區域中。樹的根節點是應用窗口。

舉個例子,一個MP3播放器可能有一個組件,這個組件會顯示正在播放的音樂的專輯的封面,第二個視圖顯示當前播放的音樂的名字,而第三個視圖則包含多個子視圖,如播放、暫停和停止按鈕。

UI框架通過遍歷視圖樹來渲染屏幕,按照「前序遍歷」的順序逐個訪問所有組件並請求各個組件對自己進行渲染。換句話說,每個視圖都自己執行渲染操作,並要求其子孫也執行同樣的渲染操作。當整棵樹都執行完渲染後,樹的葉子節點,即較小的、嵌套的組件,也是執行順序較為靠後的渲染,會將處在根節點附近的組件壓在底下,因為根節點附近的節點會先渲染,後渲染的組件會覆蓋先渲染的組件。

Android UI框架實際上比上面這種簡單的模式更為高效。如果它確定某個子節點會渲染某塊區域,它就不會再渲染父節點。在不透明對像下面再執行渲染將是浪費的。對視圖中沒有變化的部分重新渲染,也將是浪費的。

控制器

控制器(Controller)是應用中負責響應外部動作的部分:按鍵、屏幕觸摸、來電等。它是使用事件隊列(event queue)實現的。每個外部動作都會被作為一個唯一的事件插入到事件隊列中。框架按序從隊列中取出事件並分發下去,同時也會從隊列中把事件刪除掉。

例如,當用戶在手機上按下一個按鍵,Android系統會生成一個KeyEvent事件並把它加到事件隊列中。最後,在之前已經插入隊列的事件被處理完後,這個KeyEvent事件就會被從隊列中刪除並作為呼叫參數傳遞給當前選中的視圖的dispatchKeyEvent方法。

一旦某個事件被分發到焦點組件,該組件會採取適當的動作改變程序的內部狀態。例如,在MP3播放器應用中,當用戶觸摸屏幕上的Play/Pause按鈕時,該事件會分發給該按鈕對象,處理器方法可以更新模型,恢復播放之前選中的音樂。

本章後面會對如何構建Android應用的控制器進行介紹。

小結

現在,已經介紹了UI系統相關的所有概念。當觸發外部動作時,如用戶執行滾動、拖曳和單擊按鈕動作;來電;MP3播放器到達其播放列表的最後一個,Android系統會在事件隊列中插入表示該動作的事件。最後,事件會被從隊列中刪除,遵循「先進先出」原則,並被分發到相應的事件處理器。事件處理器通常就是應用中的代碼,對事件的響應方式是,通知模型某個狀態發生了某種變化。模型會執行適當的動作。幾乎所有的模形狀態的變化都需要在視圖中也有相應的變化。例如,要響應按鍵,EditText組件必須在插入點顯示剛按下的字符。同樣,在電話簿應用中,單擊某個聯繫方式會高亮顯示該聯繫方式,而之前選中的聯繫方式會被取消高亮顯示。

當模型更新其狀態時,幾乎肯定會改變當前顯示,以顯示模型內部的變化。為了對顯示進行更新,模型必須通知UI框架有些顯示部分已經過期了,必須重新渲染。重新渲染請求實際上就相當於在該框架的事件隊列中插入另一個事件,該事件隊列即之前保存控制器事件的那個隊列。重新渲染事件與其他UI事件被處理的順序一樣。

最後,重新渲染事件會被從隊列中刪除並被分發。重新渲染事件的事件處理器就是視圖。視圖樹會重新渲染;每個視圖負責渲染它當前的狀態。

為了使得以上說明更具體,我們可以以一個MP3播放器應用為例來說明一下應用執行週期:

1.當用戶觸摸屏幕上的Play/Pause按鈕圖標時,Android GUI框架會創建一個新的MotionEvent,它包含該單擊的屏幕坐標。框架會在事件隊列的尾部插入新的事件。

2.正如P165「控制器」一節所描述的,當事件被插入到隊列中後,Android GUI框架會刪除該事件,並把該事件作為葉子節點插入到視圖樹中,該單擊的屏幕坐標位置落在該葉子節點的矩形區域中。

3.因為按鈕組件表示Play/Pause按鈕,故應用按鈕處理代碼會告訴GUI框架核心(即模型)它應該重新播放該音樂。

4.應用模型代碼開始播放選中的音樂。此外,它給UI框架發送重新渲染請求。

5.重新渲染請求被加入到事件隊列中,按照P164「視圖」一節所描述的步驟執行處理。

6.屏幕進行了重新渲染,Play按鈕處於播放狀態,一切都又同步完成。

UI組件對象,如按鈕和文本框,實際上同時實現了視圖和控制器。這是很有意義的。當你給應用的UI添加按鈕時,你希望它在屏幕上顯示,並且當用戶單擊時,能夠執行某些操作。雖然UI的兩個邏輯元素(視圖和控制器)是在同一個對像中實現的,但應該注意的是,它們沒有直接交互。例如,控制器方法永遠都不應該改變顯示方式,而是把改變顯示留給改變狀態並請求重新渲染的代碼,相信在後面調用的渲染方法會使組件顯示新的狀態。這種編碼方式最大限度地減少了同步問題,有助於保持程序健壯性並預防Bug的出現。

在Android UI框架中,還有很重要的一點是:它是單線程的。單線程從事件隊列中刪除事件,執行控制器回調並渲染視圖。這一點非常有意義。單線程UI最直接的影響是,它不需要使用同步機制來協調視圖和控制器之間的狀態。這是一個很有意義的優化。

UI單線程的另一個優點在於,確保在應用的事件隊列中,每個事件都會執行完畢,並且執行順序和其入隊列的順序一致。這個優點不言而喻,它會使得UI編碼簡單得多。當調用UI組件處理事件時,UI框架會確保在該組件執行完畢之前,不會執行任何其他UI組件的處理。這表示如果一個組件多次發送程序狀態變化請求(每個變化都會發送相應的屏幕重新刷新的請求),則會確保在程序完成處理、執行更新並返回結果之前,不會執行屏幕刷新。簡而言之,UI回調是原子性的。

要謹記,負責從UI事件隊列中刪除和分發事件的線程只有一個的第三個原因是,如果你的代碼由於某個原因長期佔用該線程,則會導致UI僵死!如果組件對事件的響應是簡單的,如僅僅改變變量的狀態、創建新的對象等,則在主事件線程上執行該處理是完全沒有問題的。但是,如果處理器必須返回遠程網絡服務的響應或者運行複雜的數據庫查詢,就會造成整個UI響應遲鈍,直到請求結束為止。這絕對不會是一個良好的用戶體驗!長時間運行的任務必須委託給另一個線程,正如P186「高級連接:聚集和線程化」一節所述。