健壯且可讀的安卓架構設計

zerob13發表於2014-04-30

自接觸Android以來,我一直在尋找一種比較健壯的開發方法。譬如避免在UI執行緒進行IO操作,防止重複的網路請求,對重要資料進行快取並且準確的更新這些快取等等。當然,程式碼結構也要保持儘量清晰。

本文並不是給你提供一個權威精準的解決方案,更多的是去探討在靈活性、可讀性和健壯性之間有著很好平衡的App的一種開發方式。

一些現有的解決方案

在Android的初期版本,許多人處理多工時會選擇 AsyncTask 。大體上來說,AsyncTask非常難用,許多文章也提到了它的問題。後來,Honeycomb(3.0)引入了可配置性更好的 Loaders。到了2012年,基於Android Service的開源專案Robospice問世,帶來了新的解決方案,這裡介紹了 Robospice的工作原理。

Robospice 比起 AsyncTask 的確好太多了,但是依然存在一些問題。比如下面這段常見程式碼,通過Robospice在Activity中發起一個請求的過程。你並不需要細讀,只要有個大概的概念就好:

然後是請求的具體程式碼:

存在的問題

  1. 你需要為每個請求都做上述的處理,程式碼會顯得很臃腫:

– 對於你的每種請求你都需要繼承SpiceRequest寫一個特定的子類。
– 同樣的,對於每種請求你都需要實現一個RequestListener來監聽。
– 如果你的快取過期時間很短,使用者就需要花較長時間等待你的每個請求結束。
RequestListener持有了Activity的隱式引用,那麼是不是還需要記憶體洩露的問題。

綜上,這並不是一個很好的解決方案。

五步,讓程式簡潔而健壯

在我開始開發Candyshop的時候,我嘗試了其他的方法。我試圖通過混合一些擁有有趣特性的庫來構造一個簡單而健壯的解決方案。這是我用到的庫的列表:
* AndroidAnnotations用來處理後臺任務EBean等等……
* Spring RestTemplate用來處理 REST(含狀態傳輸)的網路請求,這個庫和AndroidAnnotations配合的非常好。
* SnappyDB這個庫主要用來將一些 Java 物件快取到本地檔案中。
* EventBus 通過 Event Bus 來解耦處理 App 內部組建間的通訊。

下圖就是我將要詳細講解的整體架構:

article1_global_schema--2-

第一步 一個易於使用的快取系統

你肯定會需要一個持久化的快取系統,保持這個系統儘可能簡單。

第二步 一個符合REST的Client

這裡我通過下面的例子來說明。記得要確保你使用 REST API 放在同一個地方。

第三步 應用級的事件匯流排(Event Bus)

在程式最初的時候就初始化Event bus物件,然後應用的全域性都可以訪問到這個物件。在Android中, Application初始化是一個很好的時機。

第四步 處理那些需要資料的Activity

對於這一類的Activity,我的處理方式和Robospice非常類似,同樣是基於Service解決。不同的是,我的Service並不是Android提供的那個,而是一個常規的單例物件。這個物件可以被App的各處訪問到,具體的程式碼我們會在第五步進行講解,在這一步,我們先看看這種處理Activity程式碼結構是怎麼樣的。因為,這一步可以看到的是我們簡化效果最強烈的部分!

一行程式碼完成對使用者資料的請求,同樣也只需要一行程式碼來解析請求所返回的資料。對於通訊錄等其他資料也可以用一樣的方式來處理,聽起來不錯吧!

第五步——單例版的後臺服務

正如我在上一步說的那樣,這裡使用的Service並不是Android提供的Service類。其實,一開始的時候,我考慮使用Android提供的Services,不過最後還是放棄了,原因還是為了簡化。因為 Android提供的Services通常情況下是為那些在沒有Activity展示情況下但還需要處理的操作提供服務的。另一種情況,你需要提供一些功能給其他的應用。這其實和我的需求並不完全相符,而且用單例來處理我的後臺請求可以讓我避免使用複雜的藉口,譬如:ServiceConnection,Binder等等……
這一部分可以探討的地方就多了。為了方便理解,我們從架構切入展示當Activity呼叫getUser()getContacts()的時候究竟發生了什麼。

你可以把下圖中每個serial當作一個執行緒:

article1_serials--3-

正如你所看到的,這是我非常喜歡的模式。大部分情況下使用者不需要等待,程式的檢視會立刻被快取資料填充。然後,當抓取到了服務端的最新資料,檢視資料會被新資料替代掉。與此對應的是,你需要確保你的Activity可以接受多次同樣型別的資料。在構建Activity的時候記住這一點就沒有任何問題啦。
下面是一些示例程式碼:

似乎每個請求之中的程式碼還是有點多!實際上,這是我為了更好說明才進行了展開。不難發現,這些請求都遵守了類似的模式,所以你可以很容易的構造一個 Helper 來簡化他們。比如 getUser()可以是這樣的:

那麼serial是用來做什麼的? 讓我們看看文件是怎麼說的:
> 預設情況下,所有@Background的匿名方法都是並行執行的。但是如果兩個方法使用了同樣名字的serial則會順序執行在同一個執行緒中,一個接著一個執行。

雖然把網路請求放在一個執行緒中順序執行可能會導致效能下降,但是這使得“先POST然後GET獲得資料”的那類事務處理起來非常容易,這是個特性值得為此犧牲一些效能。退一步講,如果你真的發現效能不可接受,還是可以很容易使用多個serial來解決。現在版本的Candyshop中,我同時使用了四個不同的serial

總結

這裡描述的解決方案是我幾個月前想到的很初級的一個想法。今天,我已經解決掉所有遇到的特殊情況,並且非常享受在這樣的架構下開發。當然,這個方案中還有一些很棒的東西我想要和大家分享,比如:錯誤處理、快取超時機制、POST請求、對無用操作的忽略,但是因為篇幅原因這裡我就不繼續講述了。

那麼,你是否也找到了能讓你享受每天工作的框架?

相關文章