1. 程式人生 > >Android專案重構之路:實現篇 讀後思考

Android專案重構之路:實現篇 讀後思考

由於現在所在的公司比較小,android studio是沒有使用的,不過在看著那篇文章之後,對於其中的模組原理有了一個基礎的瞭解,原本對於網上分享的一些在android studio專案中介面特效的程式碼我是無法一下子理解的,現在,我理解了,一邊實踐一邊學習的確是一個很有幫助的方式

還是以上次的那張結構圖來展開總結


介面層(app)、核心層(core)、介面層(api)、模型層(model)

介面層依賴核心層與模型層,核心層依賴介面層與模型層,介面層依賴模型層

先遵循文章的順序來進行深入思考

一、模型層的解讀與思考

首先是模型層(model),就文章中的模型層來說,由於為了看得人更容易的理解,所以邏輯比較簡單,只是一個擁有一些資料的獎券物件,並沒有什麼需要深究的地方。

有一點我不是很贊同的是bean物件他實現了Serializable而不是Parcelable,因為在我們程式實際過程中一般都需要

進行資料恢復,在程式進入後臺時很有可能會被回收,這時候需要恢復,而android針對Serializable恢復起來很麻煩,而且對程式的負擔比Parcelable大

但是,在我們實際的專案中,可能情況就會不這麼簡單,因為很多時候這個物件本身可能也會包含一些邏輯功能。

比如,如果這是一個使用者物件bean類,如果圍繞這個使用者有兩個需求

1、獲取頭像

2、獲取他有哪些優惠券

那麼這兩個方法呼叫的地方應該如何分配,這裡有兩種思維的思路:

①、如果以面向物件的思維,頭像是使用者自己的,應該可以直接從bean獲取,優惠券也是,這個使用者物件裡面應該就要有一個集合存放他有的優惠券,就像一個人,他口袋裡有很多優惠券,理應可以從口袋裡立刻取出來,並且一直都在

②、這個使用者物件就只是一個純粹的資料儲存的地方,它是一個殘缺的模擬的物件,他不具備獲取自己頭像bitmap的功能,因為實際生活中不可能存在一個人要去獲取自己長相檢視的活動,除非是生成照片交給別人或自己,那麼這個時候按照這種寫法我們就會需要有一個專門處理獲取頭像的管理中心,然後要有一個獲取使用者優惠券的管理中心,這對於呼叫的人來說是否方便,能否很好的理解?

而我在上次讀了結構篇之後得出的一個結論是對於功能區分一下,獲取頭像可以放在物件bean裡面,而獲取優惠券這個行為則放在一個管理中心之中

在今天經過反覆的糾結與思考之後我得出的結論是:

那麼這種區分規則其實應該並沒有什麼關鍵點,這種關鍵點就是自己給自己程式碼的風格的一種界定,而這種界定的關鍵就是介面呼叫的方便+容易理解+類的讀取方便+類的修改方便

所以我給自己定下的規則是儘量保持bean物件內部不包含其他物件,不同的物件應由不同的核心層來管理,當一定要包含其他類的一些資料時,只儲存對應的id,比如這個使用者所屬的公司,只儲存該公司的id,而不儲存該公司的物件或者公司名等資料,在網路下載下來的資料包含兩種資料時,也在解析時分離儲存,交給資料管理中心

所以對於上面那兩種資料的處理是,頭像可以通過User物件直接獲取,而他所有的優惠券則通過資料管理中心來獲取

二、介面層的解讀與思考

<span style="font-size:14px;">public class ApiResponse<T> {
    private String event;    // 返回碼,0為成功
    private String msg;      // 返回資訊
    private T obj;           // 單個物件
    private T objList;       // 陣列物件
    private int currentPage; // 當前頁數
    private int pageSize;    // 每頁顯示數量
    private int maxCount;    // 總條數
    private int maxPage;     // 總頁數

    // 建構函式,初始化code和msg
    public ApiResponse(String event, String msg) {
        this.event = event;
        this.msg = msg;
    }

    // 判斷結果是否成功
    public boolean isSuccess() {
        return event.equals("0");
    }

    // TODO 所有屬性的getter和setter
}</span>
這一段程式碼我覺得很有意思,之前對於介面層我瞭解的不夠清晰,因為以我之前的程式碼經驗來說,從來沒有寫過這樣的介面,現在覺得頗有所得(結合原文接下來的內容之後),只是這種寫法可能還需要實際磨練一下方可熟練

這種寫法將返回的結果封裝在了一個介面層定義的物件中,包括網路的訪問狀態和資訊也封裝在內。

再往下看介面的定義:

public interface Api {
    // 傳送驗證碼
    public final static String SEND_SMS_CODE = "service.sendSmsCode4Register";
    // 註冊
    public final static String REGISTER = "customer.registerByPhone";
    // 登入
    public final static String LOGIN = "customer.loginByApp";
    // 券列表
    public final static String LIST_COUPON = "issue.listNewCoupon";

    /**
     * 傳送驗證碼
     *
     * @param phoneNum 手機號碼
     * @return 成功時返回:{ "event": "0", "msg":"success" }
     */
    public ApiResponse<Void> sendSmsCode4Register(String phoneNum);

    /**
     * 註冊
     *
     * @param phoneNum 手機號碼
     * @param code     驗證碼
     * @param password MD5加密的密碼
     * @return 成功時返回:{ "event": "0", "msg":"success" }
     */
    public ApiResponse<Void> registerByPhone(String phoneNum, String code, String password);

    /**
     * 登入
     *
     * @param loginName 登入名(手機號)
     * @param password  MD5加密的密碼
     * @param imei      手機IMEI串號
     * @param loginOS   Android為1
     * @return 成功時返回:{ "event": "0", "msg":"success" }
     */
    public ApiResponse<Void> loginByApp(String loginName, String password, String imei, int loginOS);

    /**
     * 券列表
     *
     * @param currentPage 當前頁數
     * @param pageSize    每頁顯示數量
     * @return 成功時返回:{ "event": "0", "msg":"success", "objList":[...] }
     */
    public ApiResponse<List<CouponBO>> listNewCoupon(int currentPage, int pageSize);
}

可以看到,在他的程式碼中,在這些地方定義了介面,而我在實際的工作中,除了在需要回調的時候定義了介面以外,在其他地方很少使用介面,這一點需要我自我反思。

介面的定義就涉及到什麼時候需要定義介面,針對哪些方法、功能應該定義介面,關於這一點,我以前是很懵懂的,但是,現在經過對程式碼的反覆檢視和思考之後我得出了結論

層與層之間,針對層與層之間呼叫的方法,需要定義介面,而弄清楚並寫下介面的前提,就是將專案整體的結構定好,然後在定好的結構之內,關鍵的功能呼叫處寫下介面

繼續往下看,則是Api的實現類:ApiImpl ,這個類裡面我只拿一個方法來進行分析,因為其實每一個方法都是一樣的模式

public ApiResponse<Void> sendSmsCode4Register(String phoneNum) {
        Map<String, String> paramMap = new HashMap<String, String>();
        paramMap.put("appKey", APP_KEY);
        paramMap.put("method", SEND_SMS_CODE);
        paramMap.put("phoneNum", phoneNum);

        Type type = new TypeToken<ApiResponse<Void>>(){}.getType();
        try {
            return httpEngine.postHandle(paramMap, type);
        } catch (IOException e) {
            return new ApiResponse(TIME_OUT_EVENT, TIME_OUT_EVENT_MSG);
        }
    }

這是一個請求驗證碼的方法,內部的實現原理先不去說,但是對比我自己之前寫的程式碼來說,我通常都是這個方法是直接線上程中執行的,就是說,我在寫的時候已經將需要線上程內執行寫進去了,然而我與這個方法對比之後我認為是我寫錯了,因為是否要線上程內執行不應該是這個網路工具類所需要考慮的,這是呼叫它的地方決定的,這個原則同樣適用於其他方法,實現的底層必須是做單一事件的,上層的實現應儘量用組合的方式

接下來去看下載器的實現部分,因為我可以看到在這裡當下載報錯時,唯一的反饋是“伺服器連線失敗”,錯誤碼也只有time_out,這一點來說應該是由於作者希望以最簡單的實現,但是,我在思考時,則需要聯絡實際來進行思考

從使用者的角度來說,當資料加載出錯時,其實並不需要知道是什麼原因,或者說,最終我們應該展示給使用者的,應該只有兩種情況,一種是沒有網路,另一種是網路訊號差,請重新整理,對於使用者來說我們的程式碼不應該由於伺服器或客戶端的BUG產生無法使用的情況。這是我們所希望的情況,因為我們的程式碼應該不會出任何問題

從我們開發者的角度來說,我們會希望知道,出錯時出了什麼錯,這時候我們會希望知道具體是什麼原因引起的,對於我們除錯來說比較有幫助,對於我們開發者來說,錯誤有幾種:1、無網路。2、網路訊號差下載失敗(連線超時)。3、解析時出現問題(這裡包括不是我們想要的內容(cmcc)、本地與伺服器關鍵字對接問題(某個關鍵字沒有或標籤錯誤)).4、伺服器訪問不正常(404等)

那麼針對我之前在參考下載器的沒有線上程內實現時總結到的最小功能獨立原則,博主的程式碼應該將網路連線與解析分離開來,而不是都寫在

httpEngine.postHandle(paramMap, type)

這個方法中,然後在

sendSmsCode4Register(String phoneNum)

這個方法中新增各種在下載和解析時報錯的處理,然後將簡化到是沒有網路還是一切需要使用者重新整理的處理結果返回

然後,在檢視下載器時,我又發現一點,那就是所有的資料下載都是使用post方式來請求,而我在實際工作過程中,則會出現有的介面需要post方式,有的需要get方式,就我以前的認知而言,方式的選擇取決於是否需要資料保密,但是,又有很多請求時,get方式內也會出現使用者的id,這種時候為什麼是使用get呢?於是我針對post和get方式進行一番理解之後,我總結出一個結果

get方式用於固定頁面的獲取,不需要前置資料操作條件即可訪問,例如我應該開啟瀏覽器在沒有進入任何網站的時候可以直接通過網址訪問我的部落格主頁,或者我的某一篇部落格,這種時候如果希望使用post方式訪問將無法做到,因為我們一開始是沒有前置資料的,以及不需要資料保密並且字尾較少的時候,那麼這種情況使用get方式

post方式用於訪問需要在有前置資料的支援下才可訪問,以及需要保密上傳的資料的時候,這種方式在網頁端通常只能在某個頁面載入完畢,在資料準備好之後才能訪問,而在手機端,則需要在本地有所需要的必要資料時才能使用

三、核心層的解讀與思考

核心層的程式碼主要就是將各個工具組合起來,然後就像我之前發現的那樣,在一個層的主要呼叫方法都定義介面

public interface AppAction {
    // 傳送手機驗證碼
    public void sendSmsCode(String phoneNum, ActionCallbackListener<Void> listener);
    // 註冊
    public void register(String phoneNum, String code, String password, ActionCallbackListener<Void> listener);
    // 登入
    public void login(String loginName, String password, ActionCallbackListener<Void> listener);
    // 按分頁獲取券列表
    public void listCoupon(int currentPage, ActionCallbackListener<List<CouponBO>> listener);
}

介面以外的內容則都是一些方法的組合,可以發現的是,在博主的程式碼中,核心層的每一個方法的程式碼是最多的,而這與我之前寫的方法則不如博主的組合這麼好,我再對比之後的確承認博主的程式碼的確比我的好,在看完博主的這些程式碼之後對我的啟發很大

在很多安卓的文章中,可以發現需要執行緒中執行的程式碼基本都是使用AsyncTask來完成的,而我則一直使用的new Thread然後配合Handler.post來達到類似的效果,官方的推薦方法必定有什麼原因,看來我得找時間熟悉一下AsyncTask。

四、介面層的解讀與思考

這一部分的程式碼不多,可以看到兩點用到了繼承:Activity和Adapter,這種方式再不同的專案之中則是靈活運用,不過可以發現,在Adapter部分,他的資料是由Adapter來進行保存於管理的,這固然使得下載資料部分需要做的事情變少了,不過也存在一點問題:

1、不利於資料恢復,在低端手機介面不顯示時很容易被資料回收,這個時候博主的程式碼資料需要新增資料恢復的方法可能對於介面來說管理比較雜,雖然說不是無法實現,但總比有一個數據管理中心複雜

2、如果是出現在多個介面可能會有部分資料是一樣的,並且資料的狀態隨著操作會改變時,由介面這種方式管理的資料將無法做到同步

還有一部分則是博主提的一些命名規則等規範,這個在不同公司規範都是不同的,不過博主的一些明明方式的確具有一定參考價值

那麼到這裡,思考就基本結束,總的來說收穫很大,博主的程式碼的思維對我的啟發很大,我以後還需要繼續不斷學習,提升程式設計思維以及技術