前兩篇文章Android項(xiàng)目重構(gòu)之路:架構(gòu)篇和Android項(xiàng)目重構(gòu)之路:界面篇已經(jīng)講了我的項(xiàng)目開始搭建時(shí)的架構(gòu)設(shè)計(jì)和界面設(shè)計(jì),這篇就講講具體怎么實(shí)現(xiàn)的,以實(shí)現(xiàn)最小化可用產(chǎn)品(MVP)的目標(biāo),用最簡(jiǎn)單的方式來搭建架構(gòu)和實(shí)現(xiàn)代碼。
IDE采用Android Studio,Demo實(shí)現(xiàn)的功能為用戶注冊(cè)、登錄和展示一個(gè)券列表,數(shù)據(jù)采用我們現(xiàn)有項(xiàng)目的測(cè)試數(shù)據(jù),接口也是我們項(xiàng)目中的測(cè)試接口。 項(xiàng)目搭建
根據(jù)架構(gòu)篇所講的,將項(xiàng)目分為了四個(gè)層級(jí):模型層、接口層、核心層、界面層。四個(gè)層級(jí)之間的關(guān)系如下圖所示:
實(shí)現(xiàn)上,在Android Studio分為了相應(yīng)的四個(gè)模塊(Module):model、api、core、app。 model為模型層,api為接口層,core為核心層,app為界面層。 model、api、core這三個(gè)模塊的類型為library,app模塊的類型為application。 四個(gè)模塊之間的依賴設(shè)置為:model沒有任何依賴,接口層依賴了模型層,核心層依賴了模型層和接口層,界面層依賴了核心層和模型層。 項(xiàng)目搭建的步驟如下: 創(chuàng)建新項(xiàng)目,項(xiàng)目名稱為KAndroid,包名為com.keegan.kandroid。默認(rèn)已創(chuàng)建了app模塊,查看下app模塊下的build.gradle,會(huì)看到第一行為: apply plugin: 'com.android.application' 這行表明了app模塊是application類型的。 分別新建模塊model、api、core,Module Type都選為Android Library,在Add an activity to module頁面選擇Add No Activity,這三個(gè)模塊做為庫使用,并不需要界面。創(chuàng)建完之后,查看相應(yīng)模塊的build.gradle,會(huì)看到第一行為: apply plugin: 'com.android.library' 建立模塊之間的依賴關(guān)系。有兩種方法可以設(shè)置: 第一種:通過右鍵模塊,然后Open Module Settings,選擇模塊的Dependencies,點(diǎn)擊左下方的加號(hào),選擇Module dependency,最后選擇要依賴的模塊,下圖為api模塊添加了model依賴;
第二種:直接在模塊的build.gradle設(shè)置。打開build.gradle,在最后的dependencies一項(xiàng)里面添加新的一行:compile project(':ModuleName'),比如app模塊添加對(duì)model模塊和core模塊依賴之后的dependencies如下: dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.0.0' compile project(':model') compile project(':core')} 通過上面兩種方式的任意一種,創(chuàng)建了模塊之間的依賴關(guān)系之后,每個(gè)模塊的build.gradle的dependencies項(xiàng)的結(jié)果將會(huì)如下: model: dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.0.0'} api: dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.0.0' compile project(':model')} core: dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.0.0' compile project(':model') compile project(':api')} app: dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:22.0.0' compile project(':model') compile project(':core')}
創(chuàng)建業(yè)務(wù)對(duì)象模型
業(yè)務(wù)對(duì)象模型統(tǒng)一存放于model模塊,是對(duì)業(yè)務(wù)數(shù)據(jù)的封裝,大部分都是從接口傳過來的對(duì)象,因此,其屬性也與接口傳回的對(duì)象屬性相一致。在這個(gè)Demo里,只有一個(gè)業(yè)務(wù)對(duì)象模型,封裝了券的基本信息,以下是該實(shí)體類的代碼: /** * 券的業(yè)務(wù)模型類,封裝了券的基本信息。 * 券分為了三種類型:現(xiàn)金券、抵扣券、折扣券。 * 現(xiàn)金券是擁有固定面值的券,有固定的售價(jià); * 抵扣券是滿足一定金額后可以抵扣的券,比如滿100減10元; * 折扣券是可以打折的券。 * * @version 1.0 創(chuàng)建時(shí)間:15/6/21 */public class CouponBO implements Serializable { private static final long serialVersionUID = -8022957276104379230L; private int id; // 券id private String name; // 券名稱 private String introduce; // 券簡(jiǎn)介 private int modelType; // 券類型,1為現(xiàn)金券,2為抵扣券,3為折扣券 private double faceValue; // 現(xiàn)金券的面值 private double estimateAmount; // 現(xiàn)金券的售價(jià) private double debitAmount; // 抵扣券的抵扣金額 private double discount; // 折扣券的折扣率(0-100) private double miniAmount; // 抵扣券和折扣券的最小使用金額 // TODO 所有屬性的getter和setter} 接口層的封裝
在這個(gè)Demo里,提供了4個(gè)接口:一個(gè)發(fā)送驗(yàn)證碼的接口、一個(gè)注冊(cè)接口、一個(gè)登錄接口、一個(gè)獲取券列表的接口。這4個(gè)接口具體如下: 發(fā)送驗(yàn)證碼接口 URL:http://uat.b./platform/api 參數(shù): 參數(shù)名 | 描述 | 類型 |
---|
appKey | ANDROID_KCOUPON | String | method | service.sendSmsCode4Register | String | phoneNum | 手機(jī)號(hào)碼 | String |
輸出樣例: { 'event': '0', 'msg': 'success' } 注冊(cè)接口 URL:http://uat.b./platform/api 參數(shù): 參數(shù)名 | 描述 | 類型 |
---|
appKey | ANDROID_KCOUPON | String | method | customer.registerByPhone | String | phoneNum | 手機(jī)號(hào)碼 | String | code | 驗(yàn)證碼 | String | password | MD5加密密碼 | String |
輸出樣例: { 'event': '0', 'msg': 'success' } 登錄接口 URL:http://uat.b./platform/api 其他參數(shù): 參數(shù)名 | 描述 | 類型 |
---|
appKey | ANDROID_KCOUPON | String | method | customer.loginByApp | String | loginName | 登錄名(手機(jī)號(hào)) | String | password | MD5加密密碼 | String | imei | 手機(jī)imei串號(hào) | String | loginOS | 系統(tǒng),android為1 | int |
輸出樣例: { 'event': '0', 'msg': 'success' } 券列表 URL:http://uat.b./platform/api 其他參數(shù): 參數(shù)名 | 描述 | 類型 |
---|
appKey | ANDROID_KCOUPON | String | method | issue.listNewCoupon | String | currentPage | 當(dāng)前頁數(shù) | int | pageSize | 每頁顯示數(shù)量 | int |
輸出樣例:
Api的實(shí)現(xiàn)類則是ApiImpl了,實(shí)現(xiàn)類需要封裝好請(qǐng)求數(shù)據(jù)并向服務(wù)器發(fā)起請(qǐng)求,并將響應(yīng)結(jié)果的數(shù)據(jù)轉(zhuǎn)為ApiResonse返回。而向服務(wù)器發(fā)送請(qǐng)求并將響應(yīng)結(jié)果返回的處理則封裝到http引擎類去處理。另外,這里引用了gson將json轉(zhuǎn)為對(duì)象。ApiImpl的實(shí)現(xiàn)代碼如下:
至此,接口層的封裝就完成了。接下來再往上看看核心層吧。 核心層的邏輯
核心層處于接口層和界面層之間,向下調(diào)用Api,向上提供Action,它的核心任務(wù)就是處理復(fù)雜的業(yè)務(wù)邏輯。先看看我對(duì)Action的定義:
首先,和Api接口對(duì)比就會(huì)發(fā)現(xiàn),參數(shù)并不一致。登錄并沒有iemi和loginOS的參數(shù),獲取券列表的參數(shù)里也少了pageSize。這是因?yàn)?,這幾個(gè)參數(shù),跟界面其實(shí)并沒有直接關(guān)系。Action只要定義好跟界面相關(guān)的就可以了,其他需要的參數(shù),在具體實(shí)現(xiàn)時(shí)再去獲取。 另外,大部分action的處理都是異步的,因此,添加了回調(diào)監(jiān)聽器ActionCallbackListener,回調(diào)監(jiān)聽器的泛型則是返回的對(duì)象數(shù)據(jù)類型,例如獲取券列表,返回的數(shù)據(jù)類型就是List,沒有對(duì)象數(shù)據(jù)時(shí)則為Void?;卣{(diào)監(jiān)聽器只定義了成功和失敗的方法,如下:
簡(jiǎn)單的實(shí)現(xiàn)代碼就是這樣,其實(shí),這還有很多地方可以優(yōu)化,比如,將參數(shù)為空的檢查、手機(jī)號(hào)有效性的檢查、數(shù)字型范圍的檢查等等,都可以抽成獨(dú)立的方法,從而減少重復(fù)代碼的編寫。異步任務(wù)里的代碼也一樣,都是可以通過重構(gòu)優(yōu)化的。另外,需要擴(kuò)展時(shí),比如添加緩存,那就在調(diào)用Api之前處理。 核心層的邏輯就是這樣了。最后就到界面層了。 界面層
在這個(gè)Demo里,只有三個(gè)頁面:登錄頁、注冊(cè)頁、券列表頁。在這里,也會(huì)遵循界面篇提到的三個(gè)基本原則:規(guī)范性、單一性、簡(jiǎn)潔性。 首先,界面層需要調(diào)用核心層的Action,而這會(huì)在整個(gè)應(yīng)用級(jí)別都用到,因此,Action的實(shí)例最好放在Application里。代碼如下:
詳細(xì)代碼請(qǐng)查看原文鏈接 完結(jié)
終于寫完了,代碼也終于放上了github,為了讓人更容易理解,因此很多都比較簡(jiǎn)單,沒有再進(jìn)行擴(kuò)展。 github地址:https://github.com/keeganlee/kandroid
|