Android 項目重構之路:架構篇

jopen 9年前發布 | 31K 次閱讀 Android Android開發 移動開發

去年10月底換到了新公司,做移動研發組的負責人,剛開始接手android項目時,發現該項目真的是一團糟。

首先是其架構,是按功能模塊進行劃分的,本來按模塊劃分也挺好的,可是他卻分得太細,總共分為了17個模塊,而好幾個模塊也就只有兩三個類而已。但應用本身其實比較簡單,要按功能模塊來分的話,最多五個模塊就夠了。

另外,有好多模塊劃分也很模糊,也有很多類按其功能其實可以屬于多個模塊的,也有些類定義不明確,做了不該做的事。有時候,我要找一個界面的Activity,按照其功能應該屬于A模塊的,可是在A模塊里卻找不到,于是,我只好去AndroidManifest文件里找了,找到才發現原來在B模塊里。

也有時候,我要找另一個界面的Activity,可我看遍了所有模塊,也沒看出這個界面應該屬于哪個模塊,沒法子,又只能去AndroidManifest文件里找了,找到才發現竟然在C模塊里。代碼也是又亂又臭,導致出現一大堆bug又不好找,改好一個bug又出現另一個。整個項目從架構到代碼都是又臭又亂,開發人員只是不停地改bug,根本沒法做新功能,更別談擴展了。

當時,公司已經有為不同客戶定制化app的需求,而現有的架構完全無法滿足這樣的需求。因此,我決定重構,搭建一個易維護、易擴展、可定制的項目。

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

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

接口層

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

  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":"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 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方法
    ...    
}
publicclassResponseT>{
    privateStringevent;
    privateStringmsg;
    privateTobj;
    privateTobjList;
    privateintcurrentPage;
    privateintpageSize;
    privateintmaxCount;
    privateintmaxPage;
    //getter和setter方法
    ... 
}

每個屬性名稱都要與json數據對應的名稱相一致,否則無法轉化。obj和objList用泛型則可以轉化為相應的具體對象了。

Api接口類定義了所有的接口方法,方法定義類似如下:

public ResponseVoid> login(String loginName, String password);
public ResponseVersionInfo> getLastVersion();
public ResponseListCoupon>> listNewCoupon(int currentPage, int pageSize);
publicResponseVoid>login(StringloginName,Stringpassword);
publicResponseVersionInfo>getLastVersion();
publicResponseListCoupon>>listNewCoupon(intcurrentPage,intpageSize);

ApiImpl則實現所有Api接口了,實現代碼類似如下:

@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) {
        //異常處理
    }
}
@Override
publicResponseVoid>login(StringloginName,Stringpassword){
    try{
        Stringmethod=Api.LOGIN;
        ListNameValuePair>params=newArrayListNameValuePair>();
        params.add(newBasicNameValuePair("loginName",loginName));
        params.add(newBasicNameValuePair("password",EncryptUtil.makeMD5(password)));
        TypeTokenResponseVoid>>typeToken=newTypeTokenResponseVoid>>(){};
        returnpostEngine.specialHandle(method,params,typeToken);
    }catch(Exceptione){
        //異常處理
    }
}

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

核心層

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

public void getCustomer(String loginName, CallbackListenerCustomer> callbackListener);
publicvoidgetCustomer(StringloginName,CallbackListenerCustomer>callbackListener);

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

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

    /**
     * 請求的響應結果為失敗時調用
     * @param errorEvent 錯誤碼
     * @param message    錯誤信息
     */
    public void onFailure(String errorEvent, String message);
}
publicinterfaceCallbackListenerT>{
    /**
     * 請求的響應結果為成功時調用
     * @param data  返回的數據
     */
    publicvoidonSuccess(Tdata);

    /**
     * 請求的響應結果為失敗時調用
     * @param errorEvent 錯誤碼
     * @param message    錯誤信息
     */
    publicvoidonFailure(StringerrorEvent,Stringmessage);
}

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

  1. 參數檢查,檢查參數的合法性,包括非空檢查、邊界檢查、有效性檢查等;
  2. 使用異步任務調用接口層的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轉化為相應的枚舉常量。這里,我提供兩種實現方式:

  1. 使用gson的@SerializedName標簽,比如0為FALSE,1為TRUE,則可以如下定義:
public enum BooleanType {
    @SerializedName("0")
    FALSE,
    @SerializedName("1")
    TRUE
}
publicenumBooleanType{
    @SerializedName("0")
    FALSE,
    @SerializedName("1")
    TRUE
}
  1. 通過定義一個value,如下:
public enum BooleanType {
    FALSE("0"),
    TRUE("1");

    private String value;

    BooleanType(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}
publicenumBooleanType{
    FALSE("0"),
    TRUE("1");
    privateStringvalue;
    BooleanType(Stringvalue){
        this.value=value;
    }
    publicStringgetValue(){
        returnvalue;
    }
}

通過gson的方式,直接訪問TRUE或FALSE就會自動序列化為1或0;如果通過第二種方式,因為沒有序列化,則需要通過getValue方式獲取1或0。

結束

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

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!