文章核心內(nèi)容來源自Keegan小鋼枢步,編輯為自己的版本.
原文鏈接:http://keeganlee.me/post/android/20150605
今年4月底換到了新公司渐尿,做Android端負(fù)責(zé)人接手之前的項(xiàng)目,因?yàn)轫?xiàng)目之前經(jīng)過了2個(gè)團(tuán)隊(duì)的手矾瑰,因此接手時(shí)一頭霧水砖茸。
首先,模塊劃分過細(xì)過多(10個(gè)以上)脯倚,項(xiàng)目本身比較簡單渔彰,要按功能模塊來分的話,最多6個(gè)模塊就足矣推正。其次恍涂,模塊的劃分模糊,類的從屬模塊略混亂植榕,比如公用類沒有單獨(dú)抽取、類的定義不明確尊残。 有時(shí)候炒瘸,我要找一個(gè)界面的Activity,按照其功能應(yīng)該屬于A模塊的寝衫,可是在A模塊里卻找不到顷扩,于是,我只好去AndroidManifest文件里找了慰毅,找到才發(fā)現(xiàn)原來在B模塊里隘截。也有時(shí)候,我要找另一個(gè)界面的Activity汹胃,可我看遍了所有模塊婶芭,也沒看出這個(gè)界面應(yīng)該屬于哪個(gè)模塊,沒法子着饥,又只能去AndroidManifest文件里找了犀农,找到才發(fā)現(xiàn)竟然在C模塊里。最后宰掉,代碼也比較混亂呵哨,導(dǎo)致出現(xiàn)一大堆bug又不好找,改好一個(gè)bug又出現(xiàn)另一個(gè)轨奄。
整個(gè)項(xiàng)目從架構(gòu)到代碼都是比混亂孟害,開發(fā)人員只是不停地改bug,根本沒法做新功能戚绕,更別談擴(kuò)展。當(dāng)時(shí)枝冀,公司正在對(duì)app風(fēng)格做全面修改舞丛,而現(xiàn)有的架構(gòu)完全無法滿足這樣的需求耘子。因此,我決定重構(gòu)球切,搭建一個(gè)易維護(hù)谷誓、易擴(kuò)展、可定制的項(xiàng)目吨凑。
項(xiàng)目分層
-
將項(xiàng)目分為了四個(gè)層級(jí):
- 模型層: 定義了所有的模型
- 接口層: 封裝了服務(wù)器提供的API
- 核心層: 處理所有業(yè)務(wù)邏輯
- 界面層: 處理界面的展示
下面展開說明具體的每個(gè)層次:
-
接口層
接口層封裝了網(wǎng)絡(luò)底層的API捍歪,并提供給核心層調(diào)用。剛開始鸵钝,為了簡單糙臼,該層的核心類我只定義了4個(gè):
Request: 請求引擎類,對(duì)請求的發(fā)送和響應(yīng)結(jié)果進(jìn)行處理恩商;
Response: 響應(yīng)類变逃,封裝了Http請求返回的數(shù)據(jù)結(jié)構(gòu);
Api: 接口類怠堪,定義了所有接口方法揽乱;
ApiImpl: 接口實(shí)現(xiàn)類,實(shí)現(xiàn)所有接口方法粟矿。
Request將請求封裝好發(fā)送到服務(wù)器凰棉,并對(duì)響應(yīng)結(jié)果的json數(shù)據(jù)轉(zhuǎn)化為Response對(duì)象返回。Response其實(shí)就是響應(yīng)結(jié)果的json數(shù)據(jù)實(shí)體類陌粹,json數(shù)據(jù)是有固定結(jié)構(gòu)的撒犀,分為三類,如下:
{"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是返回的單個(gè)數(shù)據(jù)對(duì)象哗讥,objList是返回的數(shù)據(jù)對(duì)象數(shù)組嚷那,currentPage表示當(dāng)前頁,pageSize則表示當(dāng)前頁最多對(duì)象數(shù)量杆煞,maxCount表示對(duì)象數(shù)據(jù)總量魏宽,maxPage表示總共有多少頁。
根據(jù)此結(jié)構(gòu)决乎,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方法 ...
}```
每個(gè)屬性名稱都要與json數(shù)據(jù)對(duì)應(yīng)的名稱相一致队询,否則無法轉(zhuǎn)化。obj和objList用泛型則可以轉(zhuǎn)化為相應(yīng)的具體對(duì)象了构诚。
Api接口類定義了所有的接口方法蚌斩,方法定義類似如下:
public Response<Void> login(String loginName, String password);
public Response<VersionInfo> getLastVersion();
public Response<List<Coupon>> listNewCoupon(int currentPage, int pageSize);
ApiImpl則實(shí)現(xiàn)所有Api接口了,實(shí)現(xiàn)代碼類似如下:
@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) {
//異常處理
}
}
實(shí)現(xiàn)中將請求參數(shù)和返回的類型定義好范嘱,調(diào)用PostEngine對(duì)象進(jìn)行處理送膳。接口層的核心基本上就是這些了员魏。
- #####核心層
核心層介于接口層和界面層之間,主要處理業(yè)務(wù)邏輯叠聋,集中做數(shù)據(jù)處理撕阎。
向上,給界面層提供數(shù)據(jù)處理的接口碌补,稱為Action虏束;
向下,調(diào)用接口層向服務(wù)器請求數(shù)據(jù)厦章。
向上的Action中定義的方法類似如下:
public void getCustomer(String loginName, CallbackListener<Customer> callbackListener);
這是一個(gè)獲取用戶信息的方法镇匀,因?yàn)樾枰蚪涌趯诱埱蠓?wù)器Api數(shù)據(jù),所以添加了callback監(jiān)聽器闷袒,在callback里對(duì)返回的數(shù)據(jù)結(jié)果進(jìn)行操作坑律。CallbackListener就定義了一個(gè)成功和一個(gè)失敗的方法,代碼如下:
public interface CallbackListener<T> {
/**
- 請求的響應(yīng)結(jié)果為成功時(shí)調(diào)用
- @param data 返回的數(shù)據(jù)
*/
public void onSuccess(T data);
/** * 請求的響應(yīng)結(jié)果為失敗時(shí)調(diào)用
- @param errorEvent 錯(cuò)誤碼
- @param message 錯(cuò)誤信息
*/
public void onFailure(String errorEvent, String message);
}
接口的實(shí)現(xiàn)基本分為兩步:
step1.參數(shù)檢查囊骤,檢查參數(shù)的合法性晃择,包括非空檢查、邊界檢查也物、有效性檢查等宫屠;
step2.使用異步任務(wù)調(diào)用接口層的Api,返回響應(yīng)結(jié)果滑蚯。
需要注意的是浪蹂,Action是面向界面的,界面上的數(shù)據(jù)可能需要根據(jù)不同情況調(diào)用不同的Api告材。后續(xù)擴(kuò)展可以在這里添加緩存坤次,但也要視不同情況而定,比如有些變化太快的數(shù)據(jù)斥赋,添加緩存就不太適合了缰猴。
- #####界面層
界面層處于最上層,其核心就是負(fù)責(zé)界面的展示疤剑。因?yàn)楣居袨椴煌虘舳ㄖ撇煌琣pp的需求滑绒,因此,這里就需要建立多個(gè)app的界面隘膘,這是一個(gè)很麻煩的事情疑故,還好,Android Studio提供了很方便的方法可以大大減少工作量弯菊,主要通過設(shè)置Gradle纵势,不同app可以添加不同的productFlavors。界面層package的定義我也并不按照舊版的功能模塊劃分,而根據(jù)不同類型劃分钦铁,主要分為以下幾個(gè)包:activity扫茅、adapter、fragment育瓜。
如何通過productflavors實(shí)現(xiàn)一套代碼打包為多個(gè)版本
每個(gè)包各自都有一個(gè)基類,做統(tǒng)一的處理栽烂,比如定義了一些共用的常量躏仇、對(duì)象和方法等。界面層是最復(fù)雜腺办,最容易變得混亂不堪焰手,最容易出問題的層級(jí)。所以怀喉,從架構(gòu)到代碼书妻,很多東西都需要設(shè)計(jì)好,以及規(guī)范好躬拢,才能保證程序易維護(hù)躲履、易擴(kuò)展。
- #####模型層
模型層橫跨所有層級(jí)聊闯,封裝了所有數(shù)據(jù)實(shí)體類工猜,基本上也是跟json的obj數(shù)據(jù)一致的,在接口層會(huì)將obj轉(zhuǎn)化為相應(yīng)的實(shí)體類菱蔬,再通過Action傳到界面層篷帅。
此外,模型層還定義了一些常量拴泌,比如用戶狀態(tài)魏身、支付狀態(tài)等。
在Api里返回的狀態(tài)是用數(shù)字1蚪腐、2箭昵、3定義的,而我則用枚舉類定義了這些狀態(tài)削茁。用枚舉類定義宙枷,就可以避免了邊界的檢查,同時(shí)也更明了茧跋,誰會(huì)記得那么多1慰丛、2、3都代表什么狀態(tài)呢瘾杭。
不過诅病,用枚舉類定義的話,就必須能將1、2贤笆、3轉(zhuǎn)化為相應(yīng)的枚舉常量蝇棉。
這里我提供兩種實(shí)現(xiàn)方式:
1.使用Gson的 `@SerializedName` 標(biāo)簽,比如0為`FALSE`芥永,1為`TRUE`篡殷,則可以如下定義:
public enum BooleanType {
@SerializedName("0") FALSE, @SerializedName("1") TRUE
}
2.通過定義一個(gè)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就會(huì)自動(dòng)序列化為1或0板辽;如果通過第二種方式,因?yàn)闆]有序列化棘催,則需要通過getValue方式獲取1或0劲弦。
結(jié)束
以上就是最基本的架構(gòu)了,講得比較簡單醇坝,只列了幾個(gè)核心的東西邑跪。并沒有進(jìn)一步去擴(kuò)展,擴(kuò)展是下一步的事情了呼猪,[Keegan小鋼](http://keeganlee.me/)后續(xù)的文章里會(huì)慢慢展開画畅。