Android 專案重構之路:實現篇

發表於2015-11-19

Android專案重構之路:架構篇
Android專案重構之路:介面篇
Android專案重構之路:實現篇


前兩篇文章《Android專案重構之路:架構篇》和《Android專案重構之路:介面篇》已經講了我的專案開始搭建時的架構設計和介面設計,這篇就講講具體怎麼實現的,以實現最小化可用產品(MVP)的目標,用最簡單的方式來搭建架構和實現程式碼。

IDE採用Android Studio,Demo實現的功能為使用者註冊、登入和展示一個券列表,資料採用我們現有專案的測試資料,介面也是我們專案中的測試介面。

專案搭建

根據架構篇所講的,將專案分為了四個層級:模型層、介面層、核心層、介面層。四個層級之間的關係如下圖所示:

實現上,在Android Studio分為了相應的四個模組(Module):model、api、core、app
model為模型層,api為介面層,core為核心層,app為介面層。
model、api、core這三個模組的型別為library,app模組的型別為application。
四個模組之間的依賴設定為:model沒有任何依賴,介面層依賴了模型層,核心層依賴了模型層和介面層,介面層依賴了核心層和模型層。
專案搭建的步驟如下:

  1. 建立新專案,專案名稱為KAndroid,包名為com.keegan.kandroid。預設已建立了app模組,檢視下app模組下的build.gradle,會看到第一行為:

    這行表明了app模組是application型別的。
  2. 分別新建模組model、api、core,Module Type都選為Android Library,在Add an activity to module頁面選擇Add No Activity,這三個模組做為庫使用,並不需要介面。建立完之後,檢視相應模組的build.gradle,會看到第一行為:
  3. 建立模組之間的依賴關係。有兩種方法可以設定:
    第一種:通過右鍵模組,然後Open Module Settings,選擇模組的Dependencies,點選左下方的加號,選擇Module dependency,最後選擇要依賴的模組,下圖為api模組新增了model依賴; 

    第二種:直接在模組的build.gradle設定。開啟build.gradle,在最後的dependencies一項裡面新增新的一行:compile project(‘:ModuleName’),比如app模組新增對model模組和core模組依賴之後的dependencies如下:

    通過上面兩種方式的任意一種,建立了模組之間的依賴關係之後,每個模組的build.gradle的dependencies項的結果將會如下:
    model:

    api:

    core:

    app:

建立業務物件模型

業務物件模型統一存放於model模組,是對業務資料的封裝,大部分都是從介面傳過來的物件,因此,其屬性也與介面傳回的物件屬性相一致。在這個Demo裡,只有一個業務物件模型,封裝了券的基本資訊,以下是該實體類的程式碼:

介面層的封裝

在這個Demo裡,提供了4個介面:一個傳送驗證碼的介面、一個註冊介面、一個登入介面、一個獲取券列表的介面。這4個介面具體如下:

  • 傳送驗證碼介面
    URL:http://uat.b.quancome.com/platform/api
    引數: 
    引數名 描述 型別
    appKey ANDROID_KCOUPON String
    method service.sendSmsCode4Register String
    phoneNum 手機號碼 String

    輸出樣例:

  • 註冊介面
    URL:http://uat.b.quancome.com/platform/api
    引數: 
    引數名 描述 型別
    appKey ANDROID_KCOUPON String
    method customer.registerByPhone String
    phoneNum 手機號碼 String
    code 驗證碼 String
    password MD5加密密碼 String

    輸出樣例:

  • 登入介面
    URL:http://uat.b.quancome.com/platform/api
    其他引數: 
    引數名 描述 型別
    appKey ANDROID_KCOUPON String
    method customer.loginByApp String
    loginName 登入名(手機號) String
    password MD5加密密碼 String
    imei 手機imei串號 String
    loginOS 系統,android為1 int

    輸出樣例:

  • 券列表
    URL:http://uat.b.quancome.com/platform/api
    其他引數: 
    引數名 描述 型別
    appKey ANDROID_KCOUPON String
    method issue.listNewCoupon String
    currentPage 當前頁數 int
    pageSize 每頁顯示數量 int

    輸出樣例:

架構篇已經講過,介面返回的json資料有三種固定結構:

因此可以封裝成實體類,程式碼如下:

上面4個介面,URL和appKey都是一樣的,用來區別不同介面的則是method欄位,因此,URL和appKey可以統一定義,method則根據不同介面定義不同常量。而除去appKey和method,剩下的引數才是每個介面需要定義的引數。因此,對上面4個介面的定義如下:

Api的實現類則是ApiImpl了,實現類需要封裝好請求資料並向伺服器發起請求,並將響應結果的資料轉為ApiResonse返回。而向伺服器傳送請求並將響應結果返回的處理則封裝到http引擎類去處理。另外,這裡引用了gson將json轉為物件。ApiImpl的實現程式碼如下:

而http引擎類的實現如下:

至此,介面層的封裝就完成了。接下來再往上看看核心層吧。

核心層的邏輯

核心層處於介面層和介面層之間,向下呼叫Api,向上提供Action,它的核心任務就是處理複雜的業務邏輯。先看看我對Action的定義:

首先,和Api介面對比就會發現,引數並不一致。登入並沒有iemi和loginOS的引數,獲取券列表的引數裡也少了pageSize。這是因為,這幾個引數,跟介面其實並沒有直接關係。Action只要定義好跟介面相關的就可以了,其他需要的引數,在具體實現時再去獲取。
另外,大部分action的處理都是非同步的,因此,新增了回撥監聽器ActionCallbackListener,回撥監聽器的泛型則是返回的物件資料型別,例如獲取券列表,返回的資料型別就是List,沒有物件資料時則為Void。回撥監聽器只定義了成功和失敗的方法,如下:

接下來再看看Action的實現。首先,要獲取imei,那就需要傳入一個Context;另外,還需要loginOS和pageSize,這定義為常量就可以了;還有,要呼叫介面層,所以還需要Api例項。而介面的實現分為兩步,第一步做引數檢查,第二步用非同步任務呼叫Api。具體實現如下:

簡單的實現程式碼就是這樣,其實,這還有很多地方可以優化,比如,將引數為空的檢查、手機號有效性的檢查、數字型範圍的檢查等等,都可以抽成獨立的方法,從而減少重複程式碼的編寫。非同步任務裡的程式碼也一樣,都是可以通過重構優化的。另外,需要擴充套件時,比如新增快取,那就在呼叫Api之前處理。
核心層的邏輯就是這樣了。最後就到介面層了。

介面層

在這個Demo裡,只有三個頁面:登入頁、註冊頁、券列表頁。在這裡,也會遵循介面篇提到的三個基本原則:規範性、單一性、簡潔性。
首先,介面層需要呼叫核心層的Action,而這會在整個應用級別都用到,因此,Action的例項最好放在Application裡。程式碼如下:

另外,一個Activity的基類也是很有必要的,可以減少很多重複的工作。基類的程式碼如下:

再看看登入的Activity:

登入頁的佈局檔案則如下:

可以看到,EditText的id命名統一以edit開頭,而在Activity裡的控制元件變數名則以Edit結尾。按鈕的onClick也統一用toXXX的方式命名,明確表明這是一個將要做的動作。還有,string,dimen也都統一在相應的資原始檔裡按照相應的規範去定義。
註冊頁和登陸頁差不多,這裡就不展示程式碼了。主要再看看券列表頁,因為用到了ListView,ListView需要新增介面卡。實際上,介面卡很多程式碼都是可以複用的,因此,我抽象了一個介面卡的基類,程式碼如下:

這個抽象基類整合了設定資料的方法,每個具體的介面卡類只要再實現各自的getView方法就可以了。本Demo的券列表的介面卡如下:

而券列表的Activity簡單實現如下:

完結

終於寫完了,程式碼也終於放上了github,為了讓人更容易理解,因此很多都比較簡單,沒有再進行擴充套件。
github地址:https://github.com/keeganlee/kandroid

相關文章