Activity, BroadcastReceiver, Service 和 ContentProvider

譯自: DianneHackborn Post

“我該如何設(shè)計(jì)我的 Android 應(yīng)用?我該使用哪種 MVC 模式?我該用 event bus 做些什么?”

TL;DR :)

我們經(jīng)常會(huì)看到 Android 平臺(tái)的工程師詢問該如何在他們 app 中使用設(shè)計(jì)模式和架構(gòu)設(shè)計(jì)。答案也許是令人驚訝的,我們往往并沒有強(qiáng)烈的意愿或者真知灼見。

(這里的我們是指 Android Platform 開發(fā)小組,并不是指 Goolge 或 Android 開發(fā)者。在 Google 內(nèi)部或外部有很多關(guān)于如何編寫 app 的優(yōu)秀的意見和建議,我不打算去反駁這些。)

你是否該使用 MVC ? 或 MVP ? 或 MVVM ?
這個(gè)我不知道。我在學(xué)校里了解過 MVC,其他的(MVP/MVVM)是 Google 搜索后才寫到前面的。這可能有點(diǎn)讓人驚訝,因?yàn)?Android 感覺像是有強(qiáng)烈的意見指引該如何去寫 app —— 通過其 Java API 以及一些高級(jí)的概念,它可能看起來像一個(gè)典型的應(yīng)用框架,指導(dǎo) app 應(yīng)該如何工作,但絕大多數(shù)情況下并不是這樣。

將核心 Android API 稱為“系統(tǒng)框架”可能會(huì)更好。在大多數(shù)情況下,我們提供的平臺(tái) API 用于定義 app 和操作系統(tǒng)的交互方式;但對(duì)于存在于 app 中的任何內(nèi)容,這些 API 通常與之無關(guān)。也就是說,Android API 通常看起來與期望的操作系統(tǒng) API 不同(或更高級(jí)),從而導(dǎo)致該如何使用它們顯得比較迷惑。
舉例來說,來看看操作系統(tǒng)定義“如何啟動(dòng)一個(gè) app”。在一個(gè)經(jīng)典的系統(tǒng)中,app 像這樣啟動(dòng):

int main(...) {
  // My app goes here!
}

操作系統(tǒng)啟動(dòng) app,調(diào)用它的 main() 方法,app 運(yùn)行并執(zhí)行所需的操作,直到它決定完成。這里沒有任何關(guān)于 app 需要怎么做以及該如何設(shè)計(jì) main() 方法里的操作——這是個(gè)純凈的白板。
然而在 Android 中,我們明確的決定不會(huì)有一個(gè) main() 方法,因?yàn)槲覀冃枰屍脚_(tái)更好地控制 app 的運(yùn)行方式。特別是,我們需要設(shè)計(jì)一個(gè)用戶無需考慮開始以及停止 app 的系統(tǒng),由系統(tǒng)為他們完成這部分工作...所以系統(tǒng)需要有更多的關(guān)于每個(gè) app 內(nèi)部發(fā)生的情況的信息,并且能夠在需要時(shí)以各種有效的方式啟動(dòng) app,即使它們當(dāng)前沒有運(yùn)行。
為了做到這一點(diǎn),我們將 app 典型的主要入口分解成系統(tǒng)可以使用的幾種不同的交互。它們是 Activity, BroadcastReceiver,ServiceContentProvider API,Android 開發(fā)者可以迅速熟悉它們。
為了說明,讓我們簡單的看看這些不同的 API以及他們對(duì) Android 系統(tǒng)的意義。

Activity

這是 app 與用戶交互的入口。從系統(tǒng)的角度,它與 app 提供的關(guān)鍵交互是:

  • 追蹤用戶當(dāng)前關(guān)心的內(nèi)容(屏幕上顯示的內(nèi)容),以確保托管進(jìn)程持續(xù)運(yùn)行。
  • 獲取之前使用的進(jìn)程包含的可能返回的 Activity(已停止的 Activities),從而高優(yōu)先級(jí)的保持這些進(jìn)程。
  • 幫助 app 處理其進(jìn)程被 kill 的情況,以便于用戶可以返回先前狀態(tài)的 Activity。
  • 為 app 之間提供一種實(shí)現(xiàn)用戶流的方式,由系統(tǒng)協(xié)調(diào)。(經(jīng)典的比如分享場景)。

我們沒有關(guān)注的地方:
當(dāng)我們進(jìn)入你的 UI 入口后,我們不需要關(guān)注如何組織內(nèi)部的流程。使用單一的 Activity 更改它的 views,或者使用 fragment 和其他框架,或者將其分解為額外的 activities?;蛘吒鶕?jù)需求以上三條都做,只要你保持和 Activity 的上層做連接(它在適當(dāng)?shù)臓顟B(tài)下啟動(dòng),保存/恢復(fù)當(dāng)前的狀態(tài)),這和系統(tǒng)沒有關(guān)系。

BroadcastReceiver

這是系統(tǒng)將事件傳遞到普通用戶流之外的應(yīng)用程序的機(jī)制。最重要的是,因?yàn)檫@是另一個(gè)明確定義的 app 入口,系統(tǒng)可以傳遞 broadcast 到 app 即使它當(dāng)前不在運(yùn)行。舉例來說,app 可以安排鬧鐘來發(fā)布通知,告訴用戶待辦事件...通過將該鬧鐘的 broadcast 傳遞到 app 內(nèi)的 BroadcastReceiver,無需該應(yīng)用保持運(yùn)行直到鬧鐘關(guān)閉。

我們沒有關(guān)注的地方:
App 內(nèi)的 event 調(diào)度是一件完全不同的事,是否你使用一些 event bus 框架,實(shí)現(xiàn)你自己的回調(diào)系統(tǒng),以及其他...并沒有理由使用系統(tǒng)的廣播機(jī)制,因?yàn)槟銢]有跨app 調(diào)度 event(實(shí)際上有很多理由不這樣做——在 app 的內(nèi)部實(shí)現(xiàn)使用全局的廣播機(jī)制會(huì)有很多不必要的開銷和許多潛在的安全問題。)我們確實(shí)提供了 LocalBroadcastManager 類,它實(shí)現(xiàn)了純進(jìn)程內(nèi) intent 調(diào)度系統(tǒng),與系統(tǒng)API具有相似的 API,如果你碰巧喜歡它們:)。但是,沒有其他理由在你的 app 中使用系統(tǒng)的廣播機(jī)制。

Service

一個(gè)由于各種原因需要保持 app 在后臺(tái)運(yùn)行的通用的入口。實(shí)際上有兩條非常獨(dú)特語義的 service 告訴系統(tǒng)如何管理應(yīng)用程序:

Start service 告訴系統(tǒng),由于某些原因,“讓我保持運(yùn)行,直到我說完成了。”這可以用來同步一些數(shù)據(jù),或者在用戶離開 app 后播放音樂。這也代表了兩種不用類型的 start service,描繪系統(tǒng)該如何處理它們:

  • 音樂播放欄是用戶可以直接意識(shí)到的,因此 app 告訴系統(tǒng)它想通過成為前臺(tái) notification 告訴用戶音樂正在播放,這時(shí)系統(tǒng)明白應(yīng)該盡量保持該服務(wù)進(jìn)程運(yùn)行,因?yàn)榧偃缢Я擞脩魰?huì)不開心。
  • 常規(guī)的后臺(tái)服務(wù)不是用戶直接意識(shí)到運(yùn)行的,所以系統(tǒng)管理它的進(jìn)程的自由度更高。如果用戶需要立即關(guān)注的內(nèi)容需要內(nèi)存,這個(gè)服務(wù)可以被立即 kill(然后在未來某個(gè)時(shí)刻重啟服務(wù))。

Bound service 正在運(yùn)行,因?yàn)槠渌?app (或系統(tǒng))表示要使用該服務(wù)。這基本上是為另一個(gè)進(jìn)程提供 API 的服務(wù)。系統(tǒng)知道這些進(jìn)程之間存在依賴關(guān)系,因此如果進(jìn)程 A 與進(jìn)程B中的服務(wù)綁定,系統(tǒng)需要保持進(jìn)程 B(以及它的 service)以運(yùn)行 A。此外,如果進(jìn)程 A 是用戶所關(guān)注的,系統(tǒng)同樣會(huì)關(guān)注進(jìn)程 B。
由于其靈活性(好的或壞的),service 已被證明是各種上層系統(tǒng)概念的非常有用的構(gòu)建基礎(chǔ)。動(dòng)態(tài)壁紙,通知監(jiān)聽器,屏保程序,輸入程序,服務(wù)功能服務(wù),以及許多其他核心系統(tǒng)功能都以 service 構(gòu)建應(yīng)用實(shí)現(xiàn),當(dāng)它們需要運(yùn)行時(shí)與系統(tǒng)的 service 綁定。

我們沒有關(guān)注的地方:
Android 不會(huì)關(guān)心你的 app 內(nèi)管理使用流程的操作,因此這種場景沒有必要使用 service。例如,你想為 UI 開啟后臺(tái)下載任務(wù),為此你不應(yīng)該使用 service —— 實(shí)際上不必告訴系統(tǒng)你的進(jìn)程需要持續(xù)運(yùn)行,因?yàn)樗娴牟恍枰?br> 如果你創(chuàng)建一個(gè)簡單的后臺(tái)線程(或者任意非 service 機(jī)制)下載東西,你將獲得潛在的場景:當(dāng)用戶處在 app 的 UI 界面,系統(tǒng)將保持你的 app 進(jìn)程,下載任務(wù)不會(huì)被打斷。當(dāng)用戶離開 UI 界面,只要其他地方不需要 RAM,你的進(jìn)程將被保留(緩存)并將能夠持續(xù)下載。
同樣,對(duì)于連接同一 app 內(nèi)的不同模塊,沒有理由在同一進(jìn)程中使用 bind service,這樣做并不十分有害——系統(tǒng)發(fā)現(xiàn)進(jìn)程依賴于自身,因此不必做比尋常更多的事情,但對(duì)于 app 和系統(tǒng)來說卻做了許多無用功。此外,你應(yīng)該使用單例或者其他的進(jìn)程內(nèi)模式連接 app 內(nèi)的不同模塊。

ContentProvider

ContentProvider 是一個(gè)專業(yè)的 app 數(shù)據(jù) publish 工具。人們通常認(rèn)為它是數(shù)據(jù)庫層的抽象,因?yàn)樗泻芏嗯c數(shù)據(jù)庫相關(guān)的支持和 API ...然而從系統(tǒng)設(shè)計(jì)的角度,并不像那樣。
ContentProvider 是 app 發(fā)布數(shù)據(jù)條目的入口,通過 URI scheme 標(biāo)識(shí)。因此,app 可以決定如何將其包含的數(shù)據(jù)映射到 URI 命名空間,將這些 URI 發(fā)給可以依此訪問數(shù)據(jù)的其他 entities。系統(tǒng)在管理 app 時(shí)可以使用一些特殊的操作:

  • 發(fā)出 URI 并不需要 app 保持運(yùn)行,所以這些可以發(fā)布到任何 app dead 的地方。只有在有人告訴系統(tǒng),“嘿,給我這個(gè) URI 的數(shù)據(jù)”,它需要確保持有該數(shù)據(jù)的 app 正在運(yùn)行,并且按照要求檢索和返回?cái)?shù)據(jù)。
  • 這些 URI 還提供了一個(gè)重要的細(xì)粒度安全模型。例如,app 可以將剪貼板上的圖片映射到 URI,但將其內(nèi)容提供者鎖住,以免任何人可以自由訪問它。當(dāng)另一個(gè) app 將該 URI 從剪貼板取出時(shí),系統(tǒng)可以給它提供一個(gè)臨時(shí)的“URI 權(quán)限授權(quán)”以便允許它訪問 URI 對(duì)應(yīng)的數(shù)據(jù),但在提供數(shù)據(jù)的 app 中什么也沒有發(fā)生。

我們沒有關(guān)注的地方:
如何實(shí)現(xiàn) content provider 背后的數(shù)據(jù)管理并不重要;如果你不需要SQLite 數(shù)據(jù)庫中的結(jié)構(gòu)化數(shù)據(jù),不必使用 SQLite。例如,F(xiàn)ileProvider helper class 是通過 content provider 使你 app 內(nèi) raw 文件可用的簡單方法。
同樣,如果你沒有從 app 發(fā)布數(shù)據(jù)供其它人使用,則完全沒必要使用 content provider。因?yàn)閲@ content provider 構(gòu)建的各種 helper class 可以便捷地將數(shù)據(jù)放入 SQLite 并將其用于填充 UI 組件(如 ListView)。但假如這些工具使你開發(fā)更加困難,說明你不需要使用它,而應(yīng)該為你的 app 訓(xùn)責(zé)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容