一種更清晰的Android架構,android架構
一種更清晰的Android架構
- 原文連結 : Architecting Android…The clean way?
- 譯者 : Mr.Simple & Sophie.Ping
過去幾個月以來,通過在Tuenti網站上與@pedro_g_s和@flipper83(安卓開發兩位大牛)進行友好討論之後,我決定寫這篇關於架構安卓應用的文章。
我寫這篇文章的目的是想把我在過去幾個月體悟到的小方法以及在調查和應用中學到的有用的東西分享給大家。
入門指南
大家都知道要寫一款精品軟體是有難度且很複雜的:不僅要滿足特定要求,而且軟體還必須具有穩健性,可維護、可測試性強,並且能夠靈活適應各種發展與變化。這時候,“清晰架構”就應運而生了,這一架構在開發任何軟體應用的時候用起來非常順手。
這個思路很簡單:簡潔架構 意味著產品系統中遵循一系列的習慣原則:
- 架構獨立性
- 可測試
- UI獨立性
- 資料庫獨立性
- 任何外部代理模組的獨立性
我們並不要求一定要用四環結構(),這隻是一個樣本圖解,但是要考慮的是依賴項規則:源碼依賴項只能向內指向,內環裡的所有項不能瞭解外環所發生的東西。
以下是更好地理解和熟悉本方法的一些相關詞彙:
- Entities:是指一款應用的業務對象
- Use cases:是指結合資料流和實體中的用例,也稱為Interactor
- Interface Adapters: 這一組適配器,是負責以最合理的格式轉換用例(use cases)和實體(entities)之間的資料,表現層(Presenters )和控制層(Controllers ),就屬於這一塊的。
- Frameworks and Drivers: 這裡是所有具體的實現了:比如:UI,工具類,基礎架構,等等。
想要更具體,更生動豐富的解釋,可以參考這篇文章或者這個視頻。
情境
我會設定一個簡單的情境來開始:建立一個簡單的小app,app中顯示從雲端擷取的一個朋友或使用者列表。當點擊其中任何一個時,會開啟一個新的視窗,顯示該使用者的詳細資料。這裡我放了一段視頻,大家看看這個視頻 (需翻牆)大概就可以對我所描述的東西瞭解個大概了。
Android應用架構
這一對象遵循關注分離原則,也就是通過商務規則讓內環操作對外環事物一無所知,這樣一來,在測試時它們就不會依賴任何的外部元素了。
要達到這個目的,我的建議就是把一個項目分成三個層次,每個層次擁有自己的目的並且各自獨立於堆放運作。
值得一提的是,每一層次使用其自有的資料模型以達到獨立性的目的(大家可以看到,在代碼中需要一個資料對應器來完成資料轉換。如果你不想把你的模型和整個應用交叉使用,這是你要付出的代價)。
以下是圖解,大家感受下:
註:我並沒有使用任何的外部庫(除了用於json資料句法分析的gson和用於測試的junit, mockito, robolectric和espresso以外)。原因是它可以使這個樣本更清晰。總之,在儲存磁碟資料時,記得加上ORM、依賴注入架構或者你熟悉的任何工具或庫,這些都會帶來很大協助。(記住:重複製造輪子可不是明智的選擇)
表現層 (Presentation Layer)
表現層在此,表現的是與視圖和動畫相關的邏輯。這裡僅用了一個Model View Presenter(下文簡稱MVP),但是大家也可以用MVC或MVVM等模式。這裡我不再贅述細節,但是需要強調的是,這裡的fragment和activity都是View,其內部除了UI邏輯以外沒有其他邏輯,這也是所有渲染的東西發生的地方。
本層次的Presenter由多個interactor(用例)組成,用於完成Android UI線程以外的新線程的工作,並藉助渲染到view中的資料callback函數來返回。
如果你需要一個使用MVP和MVVM的Effective Android UI典型案例,可以參考我朋友Pedro Gómez的文章。
領域層 (Domain Layer)
這裡的商務規則是指所有在本層發生的邏輯。對於Android項目來說,大家還可以看到所有的interactor(用例)實施。這一層是純粹的java模組,沒有任何的Android依賴性。當涉及到業務對象時,所有的外部組件都使用介面。
資料層 (Data Layer)
應用所需的所有資料都來自這一層中的UserRepository實現(介面在領域層)。這一實現採用了Repository Pattern,主要策略是通過一個工廠根據一定的條件選取不同的資料來源。
比如,通過ID擷取一個使用者時,如果這個使用者在緩衝中已經存在,則硬碟快取資料源會被選中,否則會通過向雲端發起請求擷取資料,然後儲存到硬碟緩衝。
這一切背後的原理是由於未經處理資料對於用戶端是透明的,用戶端並不關心資料是來源於記憶體、硬碟還是雲端,它需要關心的是資料可以正確地擷取到。
註:在代碼方面,出於學習目的,我通過檔案系統和Android preference實現了一個簡單、原始的硬碟緩衝。請記住,如果已經存在了能夠完成這些工作的庫,就不要重複製造輪子。
錯誤處理
這是一個長期待解決的討論話題,如果大家能夠分享各自的解決方案,那真真是極好的。
我的策略是使用回調,這樣的話,如果資料倉儲發生了變化,回調有兩個方法:onResponse()和onError(). onError方法將異常資訊封裝到一個ErrorBundle對象中: 這種方法的痛點在於這其中會存在一環扣一環的回調鏈,錯誤會沿著這條回調鏈到達展示層。因此會犧牲一點代碼的可讀性。另外,如果出現錯誤,我本來可以通過事件匯流排系統拋出事件,但是這種實現方式類似於使用C語言的goto文法。在我看來,當你訂閱多個事件時,如果不能很好的控制,你可能會被弄得暈頭轉向。
測試
關於測試方面,我根據不同的層來選擇不同的方法:
- 展示層 ( Presentation Layer) : 使用android instrumentation和 espresso進行整合和功能測試
- 領域層 ( Domain Layer) : 使用JUnit和Mockito進行單元測試;
- 資料層 ( Data Layer) : 使用Robolectric ( 因為依賴於Android SDK中的類 )進行整合測試和單元測試。
代碼展示
我猜你現在在想,扯了那麼久的淡,代碼究竟在哪裡呢? 好吧,這就是你可以找到上述解決方案的github連結。還要提一點,在檔案夾結構方面,不同的層是通過以下不同的模組反應的:
- presentation: 展示層的Android模組
- domain: 一個沒有android依賴的java模組
- data: 一個資料擷取來源的android模組。
- data-test: 資料層測試,由於使用Robolectric 存在一些限制,所以我得再獨立的java模組中使用。
結論
正如 Bob大叔 所說:“Architecture is About Intent, not Frameworks” ,我非常同意這個說法,當然了,有很多不同的方法做不同的事情(不同的實現方法),我很確定,你每天(像我一樣)會面臨很多挑戰,但是遵循這些方法,可以確保你的應用會:
- 易維護 Easy to maintain
- 易測試 Easy to tes.
- 高內聚 Very cohesive.
- 低耦合 Decoupled.
最後,我強烈推薦你去實踐一下,並且分享你的經驗。也許你會找到更好的解決方案:我們都知道,不斷提升自己是一件件非常好的事。我希望這篇文章對你有所協助,歡迎拍磚。
## 參考資料Android開發技術前線,翻譯國外Android優質的技術文章,請關注訂閱號.