“我該如何設計我的 Android 應用?我該使用哪種 MVC 模式?我該用 event bus 做些什么?”
我們經常會看到 Android 平臺的工程師詢問該如何在他們 app 中使用設計模式和架構設計。答案也許是令人驚訝的,我們往往并沒有強烈的意愿或者真知灼見。
(這里的我們是指 Android Platform 開發小組,并不是指 Goolge 或 Android 開發者。在 Google 內部或外部有很多關于如何編寫 app 的優秀的意見和建議,我不打算去反駁這些。)
你是否該使用 MVC ? 或 MVP ? 或 MVVM ?
這個我不知道。我在學校里了解過 MVC,其他的(MVP/MVVM)是 Google 搜索后才寫到前面的。這可能有點讓人驚訝,因為 Android 感覺像是有強烈的意見指引該如何去寫 app —— 通過其 Java API 以及一些高級的概念,它可能看起來像一個典型的應用框架,指導 app 應該如何工作,但絕大多數情況下并不是這樣。
將核心 Android API 稱為“系統框架”可能會更好。在大多數情況下,我們提供的平臺 API 用于定義 app 和操作系統的交互方式;但對于存在于 app 中的任何內容,這些 API 通常與之無關。也就是說,Android API 通常看起來與期望的操作系統 API 不同(或更高級),從而導致該如何使用它們顯得比較迷惑。
舉例來說,來看看操作系統定義“如何啟動一個 app”。在一個經典的系統中,app 像這樣啟動:
int main(...) {
// My app goes here!
}
操作系統啟動 app,調用它的 main() 方法,app 運行并執行所需的操作,直到它決定完成。這里沒有任何關于 app 需要怎么做以及該如何設計 main() 方法里的操作——這是個純凈的白板。
然而在 Android 中,我們明確的決定不會有一個 main() 方法,因為我們需要讓平臺更好地控制 app 的運行方式。特別是,我們需要設計一個用戶無需考慮開始以及停止 app 的系統,由系統為他們完成這部分工作...所以系統需要有更多的關于每個 app 內部發生的情況的信息,并且能夠在需要時以各種有效的方式啟動 app,即使它們當前沒有運行。
為了做到這一點,我們將 app 典型的主要入口分解成系統可以使用的幾種不同的交互。它們是 Activity, BroadcastReceiver,Service 和 ContentProvider API,Android 開發者可以迅速熟悉它們。
為了說明,讓我們簡單的看看這些不同的 API以及他們對 Android 系統的意義。
Activity
這是 app 與用戶交互的入口。從系統的角度,它與 app 提供的關鍵交互是:
- 追蹤用戶當前關心的內容(屏幕上顯示的內容),以確保托管進程持續運行。
- 獲取之前使用的進程包含的可能返回的 Activity(已停止的 Activities),從而高優先級的保持這些進程。
- 幫助 app 處理其進程被 kill 的情況,以便于用戶可以返回先前狀態的 Activity。
- 為 app 之間提供一種實現用戶流的方式,由系統協調。(經典的比如分享場景)。
我們沒有關注的地方:
當我們進入你的 UI 入口后,我們不需要關注如何組織內部的流程。使用單一的 Activity 更改它的 views,或者使用 fragment 和其他框架,或者將其分解為額外的 activities。或者根據需求以上三條都做,只要你保持和 Activity 的上層做連接(它在適當的狀態下啟動,保存/恢復當前的狀態),這和系統沒有關系。
BroadcastReceiver
這是系統將事件傳遞到普通用戶流之外的應用程序的機制。最重要的是,因為這是另一個明確定義的 app 入口,系統可以傳遞 broadcast 到 app 即使它當前不在運行。舉例來說,app 可以安排鬧鐘來發布通知,告訴用戶待辦事件...通過將該鬧鐘的 broadcast 傳遞到 app 內的 BroadcastReceiver,無需該應用保持運行直到鬧鐘關閉。
我們沒有關注的地方:
App 內的 event 調度是一件完全不同的事,是否你使用一些 event bus 框架,實現你自己的回調系統,以及其他...并沒有理由使用系統的廣播機制,因為你沒有跨app 調度 event(實際上有很多理由不這樣做——在 app 的內部實現使用全局的廣播機制會有很多不必要的開銷和許多潛在的安全問題。)我們確實提供了 LocalBroadcastManager 類,它實現了純進程內 intent 調度系統,與系統API具有相似的 API,如果你碰巧喜歡它們:)。但是,沒有其他理由在你的 app 中使用系統的廣播機制。
Service
一個由于各種原因需要保持 app 在后臺運行的通用的入口。實際上有兩條非常獨特語義的 service 告訴系統如何管理應用程序:
Start service 告訴系統,由于某些原因,“讓我保持運行,直到我說完成了。”這可以用來同步一些數據,或者在用戶離開 app 后播放音樂。這也代表了兩種不用類型的 start service,描繪系統該如何處理它們:
- 音樂播放欄是用戶可以直接意識到的,因此 app 告訴系統它想通過成為前臺 notification 告訴用戶音樂正在播放,這時系統明白應該盡量保持該服務進程運行,因為假如它消失了用戶會不開心。
- 常規的后臺服務不是用戶直接意識到運行的,所以系統管理它的進程的自由度更高。如果用戶需要立即關注的內容需要內存,這個服務可以被立即 kill(然后在未來某個時刻重啟服務)。
Bound service 正在運行,因為其他 app (或系統)表示要使用該服務。這基本上是為另一個進程提供 API 的服務。系統知道這些進程之間存在依賴關系,因此如果進程 A 與進程B中的服務綁定,系統需要保持進程 B(以及它的 service)以運行 A。此外,如果進程 A 是用戶所關注的,系統同樣會關注進程 B。
由于其靈活性(好的或壞的),service 已被證明是各種上層系統概念的非常有用的構建基礎。動態壁紙,通知監聽器,屏保程序,輸入程序,服務功能服務,以及許多其他核心系統功能都以 service 構建應用實現,當它們需要運行時與系統的 service 綁定。
我們沒有關注的地方:
Android 不會關心你的 app 內管理使用流程的操作,因此這種場景沒有必要使用 service。例如,你想為 UI 開啟后臺下載任務,為此你不應該使用 service —— 實際上不必告訴系統你的進程需要持續運行,因為它真的不需要。
如果你創建一個簡單的后臺線程(或者任意非 service 機制)下載東西,你將獲得潛在的場景:當用戶處在 app 的 UI 界面,系統將保持你的 app 進程,下載任務不會被打斷。當用戶離開 UI 界面,只要其他地方不需要 RAM,你的進程將被保留(緩存)并將能夠持續下載。
同樣,對于連接同一 app 內的不同模塊,沒有理由在同一進程中使用 bind service,這樣做并不十分有害——系統發現進程依賴于自身,因此不必做比尋常更多的事情,但對于 app 和系統來說卻做了許多無用功。此外,你應該使用單例或者其他的進程內模式連接 app 內的不同模塊。
ContentProvider
ContentProvider 是一個專業的 app 數據 publish 工具。人們通常認為它是數據庫層的抽象,因為它有很多與數據庫相關的支持和 API ...然而從系統設計的角度,并不像那樣。
ContentProvider 是 app 發布數據條目的入口,通過 URI scheme 標識。因此,app 可以決定如何將其包含的數據映射到 URI 命名空間,將這些 URI 發給可以依此訪問數據的其他 entities。系統在管理 app 時可以使用一些特殊的操作:
- 發出 URI 并不需要 app 保持運行,所以這些可以發布到任何 app dead 的地方。只有在有人告訴系統,“嘿,給我這個 URI 的數據”,它需要確保持有該數據的 app 正在運行,并且按照要求檢索和返回數據。
- 這些 URI 還提供了一個重要的細粒度安全模型。例如,app 可以將剪貼板上的圖片映射到 URI,但將其內容提供者鎖住,以免任何人可以自由訪問它。當另一個 app 將該 URI 從剪貼板取出時,系統可以給它提供一個臨時的“URI 權限授權”以便允許它訪問 URI 對應的數據,但在提供數據的 app 中什么也沒有發生。
我們沒有關注的地方:
如何實現 content provider 背后的數據管理并不重要;如果你不需要SQLite 數據庫中的結構化數據,不必使用 SQLite。例如,FileProvider helper class 是通過 content provider 使你 app 內 raw 文件可用的簡單方法。
同樣,如果你沒有從 app 發布數據供其它人使用,則完全沒必要使用 content provider。因為圍繞 content provider 構建的各種 helper class 可以便捷地將數據放入 SQLite 并將其用于填充 UI 組件(如 ListView)。但假如這些工具使你開發更加困難,說明你不需要使用它,而應該為你的 app 訓責