本系列文章旨在概述我們搭建 Android 應(yīng)用程序架構(gòu)時(shí)可能會(huì)碰到的問題。我意識(shí)到,無論實(shí)現(xiàn) Android app 架構(gòu)的過程多么困難,結(jié)果證明這些一定是完成每一個(gè)好的 app 的基礎(chǔ)。
每種技術(shù)都有其自然的進(jìn)化。或者更確切地說,它的社區(qū)經(jīng)歷了進(jìn)化的過程。一個(gè)新的計(jì)算機(jī)語言或框架的早期采用者是愛好者,他們只是希望掌握技術(shù),并盡快完成一些工作。通常,新社區(qū)規(guī)模小,在開發(fā)人員之間的知識(shí)傳遞潛力有限,也就是說,每個(gè)人都從自己的錯(cuò)誤中學(xué)習(xí),因?yàn)闆]有架構(gòu)指南可用。
早期 Android 們的痛點(diǎn):谷歌是否關(guān)心?
你可以說,有很多資深的家伙在不同的技術(shù)上有很多的經(jīng)驗(yàn),但誰也沒有時(shí)間提出標(biāo)準(zhǔn)。嗯,不一定。如果技術(shù)背后有一個(gè)強(qiáng)大的公司指望賺錢,他們的技術(shù)傳播者會(huì)告訴你這個(gè)新語言多么酷,可以做很多事情,容易學(xué)習(xí),可擴(kuò)展,并且可以滿足數(shù)以百萬計(jì)的用戶。
微軟就經(jīng)常用它的技術(shù)干這樣的事情。另一方面,當(dāng)谷歌收購了 Android, 我真的以為他們只是把它當(dāng)作一個(gè)無關(guān)緊要的項(xiàng)目。 如果你從 Android 誕生之時(shí)就進(jìn)入 Android 的世界,你一定記得 Google 根本不在乎你的那種沮喪感。 那些有額外的經(jīng)驗(yàn),能力和幫助社區(qū)的意愿的少數(shù)幾個(gè)人現(xiàn)在是 Android 的超級明星或大神 —— 譬如 Jake Wharton。
“When Google bought Android, I honestly think they treated it just as some other side project.”
可能你會(huì)說,你不必考慮太多架構(gòu)和組織代碼的事情,因?yàn)椋ˋndroid)Framework 幫你做了。Android 強(qiáng)迫你把界面放到 Activity 中,可重用的界面放到 Fragment 中, 后臺(tái)服務(wù)放到 Service 中,并且用 Broadcast Receiver 和其它組件通信,這樣可以使你的生活變得更美好,是這樣嗎?不是的。
首先,有一些很好的實(shí)踐和原則確實(shí)很好,與技術(shù)(平臺(tái))無關(guān)。例如,單一職責(zé)原則,依賴倒置原則,面向接口編程,殺死全局狀態(tài),嘗試消滅所有狀態(tài),等等。
Framework 很少強(qiáng)迫你遵循原則。恰恰相反,它們以最壞的方式侵犯這些最佳實(shí)踐和原則。想想 Context 這個(gè)你到處使用的上帝對象(God object),各種單例管理者(singleton managers),擁有生命周期的 Fragment(那是怎樣的噩夢呢),常常不能正確實(shí)現(xiàn)的 AsyncTask,它們吸著你 app 的血。
一個(gè)缺乏指導(dǎo)的剛?cè)胄械拈_發(fā)者很容易造出一個(gè)怪物而不是一個(gè) app。把它想象成一個(gè)意大利面條般的怪物吧 —— 這是一個(gè)不錯(cuò)的怪物,但不是一個(gè)好的 app。
譯者注:意大利面條般的怪物寓指一團(tuán)亂麻的代碼
最后,技術(shù)(technology)和 Framework 隱藏了 app 的用途。好的,這是一個(gè) Android app,但是一個(gè)什么樣的 Android app 呢?新聞閱讀器?音樂播放器? 語言學(xué)習(xí)程式?天氣應(yīng)用?也許是一個(gè)計(jì)劃表。如果所有的東西都打包到由 Framework 提供的類,你就說不出(這是什么 app)。
正如 Robert Martin,也就是 Uncle Bob 說,“你的架構(gòu)應(yīng)該尖聲喊出(scream)app 是做什么”,更技術(shù)一點(diǎn)說,業(yè)務(wù)邏輯應(yīng)該清晰地分離,獨(dú)立于 Framework。
Android 架構(gòu)的四條黃金法則
我希望已經(jīng)說清楚了,你不應(yīng)當(dāng)依賴 Framework 來使你的代碼整潔有序,尤其是在編寫 Android 代碼的時(shí)候。我們很早以前就意識(shí)到這點(diǎn),可是缺乏拿出牛逼解決方案的經(jīng)驗(yàn)。架構(gòu)失敗要花很多時(shí)間才能表現(xiàn)出來,又不可以在項(xiàng)目中途改變整個(gè)架構(gòu)。也不可能有時(shí)間將舊項(xiàng)目重構(gòu)成新的、酷的、(想成為)最佳的架構(gòu)。因此,我們采取漸進(jìn)的方法,慢慢地從一個(gè)項(xiàng)目到另一個(gè)項(xiàng)目搭建我們的架構(gòu),從我們的錯(cuò)誤中學(xué)習(xí)。我們認(rèn)為我們的架構(gòu)應(yīng)該滿足幾個(gè)目標(biāo),它們是檢驗(yàn)我們的方法的標(biāo)準(zhǔn)。好的架構(gòu)應(yīng)該做到:
- 滿足眾多利益相關(guān)者的需求
- 支持關(guān)注點(diǎn)分離
- 逃離現(xiàn)實(shí)世界(Android、DB、Internet...)
- 使你的組件可測試
I.滿足眾多利益相關(guān)者
利益相關(guān)者(在這篇文章中)是任何對你的 app 開發(fā)感興趣的人。除了開發(fā),還有視覺設(shè)計(jì)師,交互設(shè)計(jì)師,項(xiàng)目經(jīng)理,數(shù)據(jù)庫管理員,測試等等。
當(dāng)然,你不可能以某種方式組織你的代碼,例如,交互設(shè)計(jì)師可以打開項(xiàng)目并立即了解所有內(nèi)容,甚至可以進(jìn)行一些修改。It's a unicorn.
我的意思是,你可以以這樣一種方式組織你的代碼,那個(gè)和交互設(shè)計(jì)師對接的程序員只需要打理和交互相關(guān)的代碼。因此,所有交互被分離到那些負(fù)責(zé)交互的 classes / modules / components / whatever (組件)中,當(dāng)處理 app 的交互部分時(shí),只需要打理那些組件。
譯者注:如果暫時(shí)不能理解利益相關(guān)者,沒關(guān)系的,看完第三部分你就明白了
II.支持關(guān)注點(diǎn)分離
我剛剛所說的就是一個(gè)關(guān)注點(diǎn)分離的例子。我們支持這種特定的方法,因?yàn)樗芎芎玫乇磉_(dá)團(tuán)隊(duì)組織和項(xiàng)目階段的配合,一般來說,你的架構(gòu)也應(yīng)該支持關(guān)注點(diǎn)分離。關(guān)注點(diǎn)分離或者單一職責(zé)原則是指,每一個(gè)組件應(yīng)該只有一個(gè)變化的原因。
III.逃離真實(shí)世界(Android、DB、Internet...)
逃離真實(shí)世界這條規(guī)則,先前已經(jīng)提及。我們曾說我們想要尖聲喊出 app 真正是做什么的,就是那樣。我們想要強(qiáng)調(diào)業(yè)務(wù)邏輯并且隱藏 Framework 的細(xì)節(jié)。這條規(guī)則應(yīng)該更猛烈:不僅要隱藏 Framework 的細(xì)節(jié),而且要隱藏所有與外部世界相關(guān)的細(xì)節(jié)。
所有的骯臟的 Android 的東西,如傳感器、通知機(jī)制、屏幕細(xì)節(jié)、數(shù)據(jù)庫訪問、互聯(lián)網(wǎng)訪問等。
IV.使你的組件可測試
你應(yīng)該盡可能地對你的 app 進(jìn)行單元測試,并且你的架構(gòu)應(yīng)該允許你這樣做。如果你不能測試所有東西,你至少應(yīng)該覆蓋你的業(yè)務(wù)邏輯。與真實(shí)世界分離可以很方便地做到這點(diǎn)。如果你的業(yè)務(wù)邏輯清晰地和 app 其余部分隔離,是很容易測試的。
第一次迭代 —— 上帝 Activity
public final class UsersActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
new ListUsers().execute();
}
private final class ListUsers extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... voids) {
// final SQLiteOpenHelper sqLiteOpenHelper = ...
// JsonObjectRequest jsObjRequest = new JsonObjectRequest
// (Request.Method.GET, url, null, new Response.Listener<JSONObject>() {
// MySingleton.getInstance(this).addToRequestQueue(jsObjRequest);
// showData(user);
return null;
}
}
}
你可能在 “遠(yuǎn)古時(shí)代” 看到過這樣的代碼。如果沒有,說明你很年輕。這是怎么回事?一切!
我們有一個(gè) Activity 操作數(shù)據(jù)庫,訪問網(wǎng)絡(luò),解析數(shù)據(jù),切換線程以及渲染數(shù)據(jù)。所有的利益相關(guān)者都在看這一個(gè)類,沒有關(guān)注點(diǎn)是分離的,它是不可測試的,業(yè)務(wù)邏輯和 Android 的東西混雜在一起。
譯者注:留意上圖左邊紅色的標(biāo)簽。每個(gè)標(biāo)簽分別對應(yīng)一條黃金法則,紅色表示不滿足。SRP 是指單一職責(zé)原則,即分離關(guān)注點(diǎn)。
第二次迭代 —— MVP
第一種方法顯然是不能工作的。我們嘗試過的第一件事情是 MVP,或者說 model-view-presenter。每個(gè)人都熟悉 MVP。它是最受歡迎的架構(gòu)模式之一。看起來像這樣:
這里,我們分離了實(shí)際上是 Android Fragment 的 View,我們擁有代表我們業(yè)務(wù)的(領(lǐng)域)模型,最后我們有協(xié)調(diào)一切的 Presenter。這肯定是更好的。關(guān)注點(diǎn)有了一些分離,利益相關(guān)者不再那么困惑,你也可以寫一些單元測試了。盡管如此,由于 Presenter 直接操作數(shù)據(jù)庫和所有一切,我們?nèi)匀缓驼鎸?shí)世界混雜在一起。Presenter 成了上帝對象。它處理模型,將數(shù)據(jù)發(fā)送到視圖,它擁有業(yè)務(wù)邏輯(業(yè)務(wù)邏輯是那些齒輪 :)),它訪問數(shù)據(jù)庫和網(wǎng)絡(luò),獲取傳感器數(shù)據(jù),等等。所以,是好了些,但可以更好。
譯者注:黃色的標(biāo)簽表示好了些
第三次迭代 —— MVP + managers
當(dāng)政府不知道做什么的時(shí)候它會(huì)做什么?它成立一個(gè)代理機(jī)構(gòu)。當(dāng)開發(fā)不知道做什么的時(shí)候他們會(huì)做什么?他們引入一些 Manager。你不一定把它命名為 “*Manager” 。 這些類有很多名字:uitls、helpers、fooBarBuzz-ator、等等。因此我們引入 Manager。
說實(shí)話,這甚至有點(diǎn)湊效。業(yè)務(wù)邏輯包含在 Manager 中。利益相關(guān)者知道往哪看,關(guān)注點(diǎn)一定程度是分離的但可以做得更好,你可以編寫更多的測試,但你依然直接觸摸 Android ,所以你必須編寫 Android 測試用例,并預(yù)先填寫數(shù)據(jù)庫來測試業(yè)務(wù)邏輯,一個(gè)字:不爽。是的,Manager 有變成巨獸的傾向,很快就變得難以維護(hù)。你可能爭論說它不會(huì)變得更復(fù)雜,你可以通過更簡單的架構(gòu)來更快地提供代碼,但通過這種方法依然會(huì)有很多 BUG,可維護(hù)性也會(huì)遭到破壞。
譯者注:留意 Manager this 和 Manager that 之間的標(biāo)簽
總結(jié)
在本系列的第一部分,我們經(jīng)歷了搭建實(shí)際可用的 Android 架構(gòu)的挑戰(zhàn)。良好的 Android 架構(gòu)應(yīng)該滿足眾多利益相關(guān)者的需求,支持關(guān)注點(diǎn)分離,強(qiáng)調(diào)業(yè)務(wù)邏輯,隱藏 Framework 的細(xì)節(jié),并使你所有的組件都可以測試。在系列的第二部分,我們將向你展示我們?nèi)绾喂芾韺ξ覀冇杏玫墓δ堋T诖酥埃闶欠裼腥绾蝿?chuàng)建合適的 Android 工作流的建議?或者你遇到了什么問題?