[譯] Android 架構(gòu):Part 1 —— 那些年我們犯過的錯(cuò)

本系列文章旨在概述我們搭建 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)該做到:

  1. 滿足眾多利益相關(guān)者的需求
  2. 支持關(guān)注點(diǎn)分離
  3. 逃離現(xiàn)實(shí)世界(Android、DB、Internet...)
  4. 使你的組件可測試

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 工作流的建議?或者你遇到了什么問題?

原文

閱讀第二部分

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,555評論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,516評論 2 379

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