Android專案重構之路:架構篇
Android專案重構之路:介面篇
Android專案重構之路:實現篇
去年10月底換到了新公司,做移動研發組的負責人,剛開始接手android專案時,發現該專案真的是一團糟。
首先是其架構,是按功能模組進行劃分的,本來按模組劃分也挺好的,可是他卻分得太細,總共分為了17個模組,而好幾個模組也就只有兩三個類而已。但應用本身其實比較簡單,要按功能模組來分的話,最多五個模組就夠了。
另外,有好多模組劃分也很模糊,也有很多類按其功能其實可以屬於多個模組的,也有些類定義不明確,做了不該做的事。有時候,我要找一個介面的Activity,按照其功能應該屬於A模組的,可是在A模組裡卻找不到,於是,我只好去AndroidManifest檔案裡找了,找到才發現原來在B模組裡。
也有時候,我要找另一個介面的Activity,可我看遍了所有模組,也沒看出這個介面應該屬於哪個模組,沒法子,又只能去AndroidManifest檔案裡找了,找到才發現竟然在C模組裡。程式碼也是又亂又臭,導致出現一大堆bug又不好找,改好一個bug又出現另一個。整個專案從架構到程式碼都是又臭又亂,開發人員只是不停地改bug,根本沒法做新功能,更別談擴充套件了。
當時,公司已經有為不同客戶定製化app的需求,而現有的架構完全無法滿足這樣的需求。因此,我決定重構,搭建一個易維護、易擴充套件、可定製的專案。
我將專案分為了四個層級:模型層、介面層、核心層、介面層。模型層定義了所有的模型;介面層封裝了伺服器提供的API;核心層處理所有業務邏輯;介面層就處理介面的展示。幾個層級之間的關係如下圖所示:
下面展開說明具體的每個層次:
介面層
介面層封裝了網路底層的API,並提供給核心層呼叫。剛開始,為了簡單,該層的核心類我只定義了4個:
- PostEngine,請求引擎類,對請求的傳送和響應結果進行處理;
- Response,響應類,封裝了Http請求返回的資料結構;
- Api,介面類,定義了所有介面方法;
- ApiImpl,介面實現類,實現所有介面方法。
PostEngine將請求封裝好傳送到伺服器,並對響應結果的json資料轉化為Response物件返回。Response其實就是響應結果的json資料實體類,json資料是有固定結構的,分為三類,如下:
1 2 3 |
{"event": "0", "msg": "success"} {"event": "0", "msg": "success", "obj":{...}} {"event": "0", "msg": "success", "objList":[{...}, {...}], "currentPage": 1, "pageSize": 20, "maxCount": 2, "maxPage": 1} |
event為返回碼,0表示成功,msg則是返回的資訊,obj是返回的單個資料物件,objList是返回的資料物件陣列,currentPage表示當前頁,pageSize則表示當前頁最多物件數量,maxCount表示物件資料總量,maxPage表示總共有多少頁。根據此結構,Response基本的定義如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class ResponseT> { private String event; private String msg; private T obj; private T objList; private int currentPage; private int pageSize; private int maxCount; private int maxPage; //getter和setter方法 ... } |
每個屬性名稱都要與json資料對應的名稱相一致,否則無法轉化。obj和objList用泛型則可以轉化為相應的具體物件了。
Api介面類定義了所有的介面方法,方法定義類似如下:
1 2 3 |
public ResponseVoid> login(String loginName, String password); public ResponseVersionInfo> getLastVersion(); public ResponseListCoupon>> listNewCoupon(int currentPage, int pageSize); |
ApiImpl則實現所有Api介面了,實現程式碼類似如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Override public ResponseVoid> login(String loginName, String password) { try { String method = Api.LOGIN; ListNameValuePair> params = new ArrayListNameValuePair>(); params.add(new BasicNameValuePair("loginName", loginName)); params.add(new BasicNameValuePair("password", EncryptUtil.makeMD5(password))); TypeTokenResponseVoid>> typeToken = new TypeTokenResponseVoid>>(){}; return postEngine.specialHandle(method, params, typeToken); } catch (Exception e) { //異常處理 } } |
實現中將請求引數和返回的型別定義好,呼叫PostEngine物件進行處理。
介面層的核心基本上就是這些了。
核心層
核心層介於介面層和介面層之間,主要處理業務邏輯,集中做資料處理。向上,給介面層提供資料處理的介面,稱為Action;向下,呼叫介面層向伺服器請求資料。向上的Action中定義的方法類似如下:
1 |
public void getCustomer(String loginName, CallbackListenerCustomer> callbackListener); |
這是一個獲取使用者資訊的方法,因為需要向介面層請求伺服器Api資料,所以新增了callback監聽器,在callback裡對返回的資料結果進行操作。CallbackListener就定義了一個成功和一個失敗的方法,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public interface CallbackListenerT> { /** * 請求的響應結果為成功時呼叫 * @param data 返回的資料 */ public void onSuccess(T data); /** * 請求的響應結果為失敗時呼叫 * @param errorEvent 錯誤碼 * @param message 錯誤資訊 */ public void onFailure(String errorEvent, String message); } |
介面的實現基本分為兩步:
- 引數檢查,檢查引數的合法性,包括非空檢查、邊界檢查、有效性檢查等;
- 使用非同步任務呼叫介面層的Api,返回響應結果。
需要注意的是,Action是面向介面的,介面上的資料可能需要根據不同情況呼叫不同的Api。
後續擴充套件可以在這裡新增快取,但也要視不同情況而定,比如有些變化太快的資料,新增快取就不太適合了。
介面層
介面層處於最上層,其核心就是負責介面的展示。
因為公司有為不同商戶定製不同app的需求,因此,這裡就需要建立多個app的介面,這是一個很麻煩的事情,還好,Android Studio提供了很方便的方法可以大大減少工作量,主要通過設定Gradle,不同app可以新增不同的productFlavors。
介面層package的定義我也並不按照舊版的功能模組劃分,而根據不同型別劃分,主要分為以下幾個包:
其中,activity、adapter、fragment各自都有一個基類,做統一的處理,比如定義了一些共用的常量、物件和方法等。
介面層是最複雜,最容易變得混亂不堪,最容易出問題的層級。所以,從架構到程式碼,很多東西都需要設計好,以及規範好,才能保證程式易維護、易擴充套件。後續的文章裡將會詳細分享下我在這方面的經驗。
模型層
模型層橫跨所有層級,封裝了所有資料實體類,基本上也是跟json的obj資料一致的,在介面層會將obj轉化為相應的實體類,再通過Action傳到介面層。另外,模型層還定義了一些常量,比如使用者狀態、支付狀態等。在Api裡返回的是用1、2、3這樣定義的,而我則用列舉類定義了這些狀態。用列舉類定義,就可以避免了邊界的檢查,同時也更明瞭,誰會記得那麼多1、2、3都代表什麼狀態呢。然而用列舉類定義的話,就必須能將1、2、3轉化為相應的列舉常量。這裡,我提供兩種實現方式:
- 使用gson的@SerializedName標籤,比如0為FALSE,1為TRUE,則可以如下定義:
1 2 3 4 5 6 |
public enum BooleanType { @SerializedName("0") FALSE, @SerializedName("1") TRUE } |
- 通過定義一個value,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public enum BooleanType { FALSE("0"), TRUE("1"); private String value; BooleanType(String value) { this.value = value; } public String getValue() { return value; } } |
通過gson的方式,直接訪問TRUE或FALSE就會自動序列化為1或0;如果通過第二種方式,因為沒有序列化,則需要通過getValue方式獲取1或0。
結束
以上就是最基本的架構了,講得比較簡單,只列了幾個核心的東西。並沒有進一步去擴充套件,擴充套件是下一步的事情了,後續的文章裡會慢慢展開。