Android項目重構之路:架構篇

去年10月底換到了新公司,作移動研發組的負責人,剛開始接手android項目時,發現該項目真的是一團糟。首先是其架構,是按功能模塊進行劃分的,原本按模塊劃分也挺好的,但是,他卻分得太細,總共分爲了17個模塊,而好幾個模塊也就只有兩三個類而已。但應用自己其實比較簡單,要按功能模塊來分的話,最多五個模塊就夠了。另外,有好多模塊劃分也很模糊,也有不少類按其功能其實能夠屬於多個模塊的,也有些類定義不明確,作了不應作的事。有時候,我要找一個界面的Activity,按照其功能應該屬於A模塊的,但是在A模塊裏卻找不到,因而,我只好去AndroidManifest文件裏找了,找到才發現原來在B模塊裏。也有時候,我要找另外一個界面的Activity,可我看遍了全部模塊,也沒看出這個界面應該屬於哪一個模塊,無法子,又只能去AndroidManifest文件裏找了,找到才發現居然在C模塊裏。代碼也是又亂又臭,致使出現一大堆bug又很差找,改好一個bug又出現另外一個。整個項目從架構到代碼都是又臭又亂,開發人員只是不停地改bug,根本無法作新功能,更別談擴展了。當時,公司已經有爲不一樣客戶定製化app的需求,而現有的架構徹底沒法知足這樣的需求。所以,我決定重構,搭建一個易維護、易擴展、可定製的項目。java

我將項目分爲了四個層級:模型層、接口層、核心層、界面層。模型層定義了全部的模型;接口層封裝了服務器提供的API;核心層處理全部業務邏輯;界面層就處理界面的展現。幾個層級之間的關係以下圖所示:


下面展開說明具體的每一個層次:android

接口層

接口層封裝了網絡底層的API,並提供給核心層調用。剛開始,爲了簡單,該層的核心類我只定義了4個:json

  1. PostEngine,請求引擎類,對請求的發送和響應結果進行處理;數組

  2. Response,響應類,封裝了Http請求返回的數據結構;緩存

  3. Api,接口類,定義了全部接口方法;服務器

  4. ApiImpl,接口實現類,實現全部接口方法。微信

PostEngine將請求封裝好發送到服務器,並對響應結果的json數據轉化爲Response對象返回。Response其實就是響應結果的json數據實體類,json數據是有固定結構的,分爲三類,以下:網絡

{"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基本的定義以下:數據結構

public class Response<T> {    
   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接口類定義了全部的接口方法,方法定義相似以下:

public Response<Void> login(String loginName, String password);
public Response<VersionInfo> getLastVersion();
public Response<List<Coupon>> listNewCoupon(int currentPage, int pageSize);

ApiImpl則實現全部Api接口了,實現代碼相似以下:

@Override
public Response<Void> login(String loginName, String password) {
   try {        String method = Api.LOGIN;        List<NameValuePair> params = new ArrayList<NameValuePair>();        params.add(new BasicNameValuePair("loginName", loginName));        params.add(new BasicNameValuePair("password", EncryptUtil.makeMD5(password)));        TypeToken<Response<Void>> typeToken = new TypeToken<Response<Void>>(){};
       return postEngine.specialHandle(method, params, typeToken);    } catch (Exception e) {
       //異常處理
   } }

實現中將請求參數和返回的類型定義好,調用PostEngine對象進行處理。
接口層的核心基本上就是這些了。

核心層

核心層介於接口層和界面層之間,主要處理業務邏輯,集中作數據處理。向上,給界面層提供數據處理的接口,稱爲Action;向下,調用接口層向服務器請求數據。向上的Action中定義的方法相似以下:

public void getCustomer(String loginName, CallbackListener<Customer> callbackListener);

這是一個獲取用戶信息的方法,由於須要向接口層請求服務器Api數據,因此添加了callback監聽器,在callback裏對返回的數據結果進行操做。CallbackListener就定義了一個成功和一個失敗的方法,代碼以下:

public interface CallbackListener<T> {
   /**    * 請求的響應結果爲成功時調用    * @param data  返回的數據    */
   public void onSuccess(T data);

   /**    * 請求的響應結果爲失敗時調用    * @param errorEvent 錯誤碼    * @param message    錯誤信息    */
   public void onFailure(String errorEvent, String message); }

接口的實現基本分爲兩步:

  1. 參數檢查,檢查參數的合法性,包括非空檢查、邊界檢查、有效性檢查等;

  2. 使用異步任務調用接口層的Api,返回響應結果。

須要注意的是,Action是面向界面的,界面上的數據可能須要根據不一樣狀況調用不一樣的Api。
後續擴展能夠在這裏添加緩存,但也要視不一樣狀況而定,好比有些變化太快的數據,添加緩存就不太適合了。

界面層

界面層處於最上層,其核心就是負責界面的展現。
由於公司有爲不一樣商戶定製不一樣app的需求,所以,這裏就須要創建多個app的界面,這是一個很麻煩的事情,還好,Android Studio提供了很方便的方法能夠大大減小工做量,主要經過設置Gradle,不一樣app能夠添加不一樣的productFlavors。
界面層package的定義我也並不按照舊版的功能模塊劃分,而根據不一樣類型劃分,主要分爲如下幾個包:

其中,activity、adapter、fragment各自都有一個基類,作統一的處理,好比定義了一些共用的常量、對象和方法等。
界面層是最複雜,最容易變得混亂不堪,最容易出問題的層級。因此,從架構到代碼,不少東西都須要設計好,以及規範好,才能保證程序易維護、易擴展。後續的文章裏將會詳細分享下我在這方面的經驗。

模型層

模型層橫跨全部層級,封裝了全部數據實體類,基本上也是跟json的obj數據一致的,在接口層會將obj轉化爲相應的實體類,再經過Action傳到界面層。另外,模型層還定義了一些常量,好比用戶狀態、支付狀態等。在Api裏返回的是用一、二、3這樣定義的,而我則用枚舉類定義了這些狀態。用枚舉類定義,就能夠避免了邊界的檢查,同時也更明瞭,誰會記得那麼多一、二、3都表明什麼狀態呢。然而用枚舉類定義的話,就必須能將一、二、3轉化爲相應的枚舉常量。這裏,我提供兩種實現方式:
1.使用gson的@SerializedName標籤,好比0爲FALSE,1爲TRUE,則能夠以下定義:

public enum BooleanType {
   @SerializedName("0")    FALSE,
   @SerializedName("1")    TRUE }

2.經過定義一個value,以下:

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。

結束

以上就是最基本的架構了,講得比較簡單,只列了幾個核心的東西。並無進一步去擴展,擴展是下一步的事情了,後續的文章裏會慢慢展開。

本文分享自微信公衆號 - Keegan小鋼(keeganlee_me)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索