Android項(xiàng)目重構(gòu)之路<一>:架構(gòu)篇

文章核心內(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ù)邏輯
    • 界面層: 處理界面的展示

    層級(jí)之間的關(guān)系如下圖所示:

下面展開說明具體的每個(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ì)慢慢展開画畅。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市宋距,隨后出現(xiàn)的幾起案子夜赵,更是在濱河造成了極大的恐慌,老刑警劉巖乡革,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寇僧,死亡現(xiàn)場離奇詭異,居然都是意外死亡沸版,警方通過查閱死者的電腦和手機(jī)嘁傀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來视粮,“玉大人细办,你說我怎么就攤上這事±倥梗” “怎么了笑撞?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長钓觉。 經(jīng)常有香客問我茴肥,道長,這世上最難降的妖魔是什么荡灾? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任瓤狐,我火速辦了婚禮瞬铸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘础锐。我一直安慰自己嗓节,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布皆警。 她就那樣靜靜地躺著拦宣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪信姓。 梳的紋絲不亂的頭發(fā)上恢着,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音财破,去河邊找鬼。 笑死从诲,一個(gè)胖子當(dāng)著我的面吹牛左痢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播系洛,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼俊性,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了描扯?” 一聲冷哼從身側(cè)響起定页,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎绽诚,沒想到半個(gè)月后典徊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡恩够,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年卒落,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜂桶。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡儡毕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出扑媚,到底是詐尸還是另有隱情腰湾,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布疆股,位于F島的核電站费坊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏旬痹。R本人自食惡果不足惜葵萎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一导犹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧羡忘,春花似錦谎痢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至漫雕,卻和暖如春滨嘱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浸间。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工太雨, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人魁蒜。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓囊扳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親兜看。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锥咸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容