如何看UML圖? UML能給我們帶來什么? 這是本文要尋找的答案。UML圖有很多類型,我們這里只討論最重要也最常用的兩種 - 類圖和時序圖。
1. 類圖
通過類圖,我們可以很容易的了解代碼架構,理清模塊之間的關系, 包括繼承(Inheritance),實現(realization),依賴(dependency),組合(Composition), 聚合(Aggregation), 關聯 (Association) 等等。
下面就圖中給出的7種關系一一解讀。
1.1 Composition
Compostion 是一種 Association 關系,但它更強調兩個類之間整體和局部關系,它暗示兩個類之間有着相同的生命周期,比如說圖中的三個1.
- W 是 ViewRootImpl的成員變量之一,ViewRootImpl 對象的構造函數里也構造了W,因此,當ViewRootImpl 析構時,W也被析構,他們的生命周期是一致的。代碼如下:
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { ... final W mWindow; ... mWindow = new W(this); //互相引用,所以當一個銷毀時,另外一個也無法存在。 }
- 同樣類似的關系存在於 WindowManagerService 和 WindwoState 之間。
1.2 Realization
Realization就是實現,在Java中體現為implements 一個接口類interface), 在標准的C++中沒有明確的接口概念,但抽象類實際上起着和接口類似的功能,因為C++的Realization可以體現為繼承一個抽象類。在Android 的C++代碼中,有一個特殊的抽象類IInterface, 定義了PC接口類的一些基本方法, 關於它的細節可以參考 圖解Android - Binder 和 Service。
1.3 Association
有接口就會有引用,在UML中一根最普通的單向箭頭即是引用(關聯)關系。它的含義是,某個對象用到了一個其他對象的接口或屬性。通常,Assocation 通過兩種方式獲取
- 依賴注入,通過構造函數或SetXXX()接口,比如說 WindowState 通過構造時傳入的參數獲取了對IWindow對象的引用
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a, int viewVisibility, final DisplayContent displayContent)
- 間接獲取,通過調用其他對象方法返回。比如
mActivityManager = ActivityManagerNative.getDefault();
和具有組合關系(Composition)的對象相比,Associated對象的生命周期管理要復雜一些,因為他有可能被若干個對象所應用,如果不小心干掉一個還在使用的對象,就會造成不可知的后果。Java中通過GC完全去除了析構方法, 交由系統來決定何時對象應該被析構。C++沒有類似GC的功能,所以Android 在C++中引入的智能指針(會有專門文章介紹)來實現類似GC(實現的差異還是很大的)的功能,從而能夠減輕開發者的負擔,並能夠提高代碼的穩定性。
1.4 Android 的 IPC interface.
C++和Java 的接口都只支持進程內的調用。為了支持專門用於跨進程的接口調用,Android 專門做了一些規定。凡是以IXXXX 定義的接口類均可以支持IPC(當然,進程內也可以調用IXXXX定義的接口)。當我們在圖中看到一個IXXX 接口類,我們便可以認為這是一個進程的邊界,他的實現端和調用端對象運行在不同的進程里(至少是不同的線程)。如下圖中,IWindow的兩端W對象 和 WindowState 對象就用不同的顏色來標明它們分屬於不同的進程,W運行在應用程序的進程里,而WinState存在與System Server 進程里。
1.5 Aggregation
聚合表達了兩個類的從屬關系,但和Composition不同,他們的生命周期並不一樣。一個經典的例子就是工廠和車子,車子是工廠造出來的,工廠倒閉了,車子可以繼續開,反之亦然。圖中PolicyManager 和 PhoneWindow就是類似的關系。
1.6 Inherritance
就是最常見的繼承關系了。復雜的繼承關系很難閱讀和記憶,通過UML圖則方便很多,你可以清楚到看出繼承關系,同時能夠理解繼承的設計思想。比如說圖中右上角,PhoneWindow 繼承了Window 類,Activity 引用的是其基類Window的對象,但背后真正干事的是PhoneWindow對象, 因為Acitivy 用的Window類對象是PolicyManager構造出來的。通過這種方式,PhoneWindow的實現細節被PolicyManager 和 Window 基類隱藏起來,從而大大降低了應用程序(Activity) 改變的幾率。這個正是設計模式里有名的工廠模式之一。
1.7 依賴
依賴不同與引用,依賴者和被依賴着之間沒有直接的對象引用,通常是常量或靜態方法的使用。比如圖中,Activity使用了PolicyManager類的靜態方法 makeNewWindow() 創建了PhoneWindow對象,我們說Acitivy 依賴PolicyManager 這個模塊,但它並沒有引用PoclicyManager的對象。依賴通常用一根單向虛線箭頭表示。
2. 時序圖
通過時序圖,我們可以了解代碼的調用流程, 並可以檢查調用過程中可能產成的潛在問題,如死鎖等。時序圖可以從兩個方向去看,縱向和橫向。縱向描述了一個對象在時間軸上所做的事情,一個方塊通過代表一個函數的調用。而橫向則描述了各個對象之間的調用關系, 包括同步調用,異步調用,返回等等。此外,在時序圖中,我們可以給執行塊賦予不同的顏色,代表了他們分別運行在不同的進程或線程里。下面就是一張時序圖的例子,它描述了Android中一個System server 進程啟動的過程,(這里只是局部,更多信息請訪問 圖解Android - Zygote 和 System Server 啟動分析 , 圖中的粉紅色注釋列舉了從圖中我們可以獲知的一些信息。
你剛才不是說從時序圖中能看出死鎖? 就這樣一樣圖?誇張了吧! 沒錯,看看下面一個例子吧!
這是一個很簡單的例子,圖中有兩個線程,綠色是app線程,它通過調用MediaPlayer 對象的函數來控制播放器,這里它做了兩件事,Start() 然后 Stop (). 而粉色部分代表Driver 線程,它通過回調函數告知Mediaplayer 一下底層的事件。也就是說Mediaplayer 是一個被兩個線程同時引用的對象,是一個共享的資源。想當然的,我們用了一把鎖來保護它,防止他被同時使用產生沖突。所以,圖中,綠色的app在Stop()時候首先拿到了鎖。這是問題發生了,Stop()的過程可能會比較長,中途來了一個事件, 圖中黃色注釋的右方顯示了這個情況,兩個顏色的長條重疊在一起。這表明有資源沖突發生,也意味着潛在的死鎖風險。我們假設Stop()的最終目的就是要析構VideoDecoder 對象,但此時,VideoDecoder調用的eventHandler() 在另外一個線程還沒返回,理所當然的我們需要等待它。不幸的是,這個時候死鎖發生了,如圖中紅色注釋所示。
通過簡單的畫這么一個圖,我們可以很輕易的分析出一個死鎖的情況。那怎么解決它的,盡可以的避免圖中不同顏色的條塊重疊在一起。看看下面的解決方案,
這回,我們取消了鎖的操作,通過添加一個新的線程(變成3個線程,3種顏色)Thread,將同步的調用變成異步,,交由Thread做后台處理。因此圖中不再有顏色塊重疊(異步調用產生的重疊不算), Stop()和EventHandler都很快返回(異步調用), 從而消除了死鎖的存在。這也是為什么Andriod 設計了Looper, MessageQueue 和 Handler 的異步消息處理機制,並在Framework 中大量的使用,因為Android 是個極其復雜的多線程/多進程應用環境(想想任意安裝的應用程序和各種各樣難以預測的用戶操作吧),基於鎖的同步調用機制是難於保證完全避免死鎖的發生。關於Android 異步消息處理機制,可以參考 圖解Android - Looper, Handler 和 MessageQueue 一文。
這下同意了吧,時序圖對分析多線程的編程分析有很大的幫助。我們應該在設計階段盡可能的用類圖和時序圖來幫助我們避免一些常見的問題,幫助我們得出一個盡可能好的設計。
在圖解Andrid 系列博文中,我們將會大量的使用UML圖來幫助代碼的理解和記憶,相信它會比單純的解析代碼更加清晰。
3. 怎樣畫Android UML 圖?
工具!必須依賴工具,市面上有太多的UML工具,你只需要找一款支持逆向工程的,即將代碼轉換成UML的數據結構,然后將類圖或時序圖一步步的繪制出來。
本文用的工具是 bouml,一個Linux上的免費工具(Ubuntu12.10開始就不免費了,所以推薦在12.04上安裝使用)。如果你不想從頭開始,請訪問 https://github.com/samchen2009/android_uml, 那里有一份reverse過的Android 4.3, 以及本系列里面所有的UML圖。