關于Android四大組件最權威最深刻最準確的解讀

MeredithCon 8年前發布 | 8K 次閱讀 安卓開發 Android開發 移動開發

“我應該怎樣設計我的APP?我應該采用什么樣的架構模式?我需要使用event bus嗎?”

我們經常看到Android平臺開發者詢問在APP中采用什么設計模式和架構之類的問題。但是答案很可能會令你驚訝,那就是,我們(我們指的是Android Platform Team)對此并沒有一個明確的觀點,甚至可以說我們壓根就沒有觀點。

你應該使用MVC還是MVP還是MVVM?我不知道。實際上,我在學校時只知道MVC,其他的架構模式是我臨時google搜索后才寫在這里的。

這也許令人吃驚,因為Android給人的感覺是,它對應當怎么寫APP有著自己很強烈的看法,這種看法體現在它的Java APIs和四大組件等一些高級概念上,這些東西看上去組成了一個典型的應用開發框架,告訴我們開發者,你應當這樣去實現你的功能。但實際情況是,根本不是這樣。

我們可以將Android核心APIs(core APIs)叫做“系統框架”(system framework),而平臺APIs(platform APIs)最主要的功能是定義APP應如何與操作系統交互,與APP內部運行邏輯毫不相關。

個人理解:可以簡單地將core APIs看做操作系統內核,而將platform APIs看做我們常說的Android Framework。

這就是說,對于platform APIs,從開發者角度與從操作系統角度看其功能作用經常是不一致的,這很容易讓人們在使用中感到困惑。

舉例來說,我們來考慮一下操作系統是怎樣定義“怎樣運行一個APP”的。在一個經典的系統里,最基本的就是要求APP包含一個main方法,里面定義了自己要怎樣運行:

int main(...){
//我的APP從這里開始運行
}

個人理解:本文的核心思想就是說明,所謂的四大組件,只是讓你的APP告訴操作系統,自己要怎樣運行而已,跟怎樣設計自己的APP,壓根沒有關系。傳統的應用通過一個main方法,告訴操作系統:“嘿哥們,main方法就是我的入口,請從這個方法開始運行我。”而Android卻給了你四個選擇,每一個組件都是讓操作系統運行你的APP的一種入口。

好了,操作系統要運行你的APP了,于是它調用你的main方法,然后你的應用就開始運行了,你可以做任何你想做的事情,直到你認為自己完成任務為止。請注意,這里要求你定義main方法,并不是要求你去做什么事,或是完成一個叫做main的功能,main方法所有的作用僅僅是提供一個APP運行入口而已。

但是在Android的世界,我們決定,我們不要一個明確的main方法作為APP的入口。因為我們需要讓系統對APP怎樣運行有更多的控制權。尤其是,我們希望構建一個這樣的系統,在該系統中,用戶永遠不需要考慮開啟和停止一個APP,而把這些事交給系統去管理。所以,系統需要知道更多的每個APP的內部運行情況,以便能夠在需要的時候,以定義好的方式啟動APP,即使該APP當時并不在運行。

個人理解:這個系統所需要了解的每個APP的內部運行情況,其實就是Manifest.xml文件中的內容。

為了達到這一點,我們將一個APP的main方法分解成幾種系統可以與之交互的形式。這幾種形式就是Activity,BroadcastReceiver,Service和ContentProvider APIs,廣大的Android開發者都很熟悉它們。

這些類好像在告訴你,你的APP內部應當怎樣工作,但這是一種誤解!事實上,這些類只是定義你的APP需要怎樣與系統交互(以及系統怎樣協調你的APP與其他APP進行交互)。這種與系統的交互一旦開始,系統就不再關心你的APP內部是怎樣運行了。

為了更好地說明這一點,讓我們簡要地看看這些APIs對于Android系統來說到底意味著什么。

Activity

這是一個APP與用戶交互的入口。從系統的角度看,系統為Activity提供的關鍵交互動作是:

  1. 持續跟蹤用戶當前正在關心的(也就是顯示在屏幕上的東西),以確保當前進程保持運行。

    個人理解:這里,作者實際上的含義是,當你的應用被系統從Activity啟動時,在Activity的start與stop狀態之間,系統會確保這個Activity始終占據著設備的屏幕,并且確保你的應用絕不會被系統殺死。這是你從Activity啟動自己的APP時,系統給予你的APP的一種承諾(just a promise)。

  2. 知道那些之前使用過的進程,這些進程包含著用戶可能會返回獲取的東西(stopped activities),并因此給予這些進程更高的優先級。

  3. 幫助應用處理進程被殺死的情況,以便用戶能夠返回到之前的activities,并且這些activities能夠加載自己之前的狀態

    個人理解:很顯然,系統所承諾的這種狀態恢復能力,是依靠Activity的 onSaveInstanceState和onRestoreInstanceState方法,也就是說,你在Save方法中保存好你想在進程被殺死時想要保存的Activity狀態,然后你就可以在Restore方法中獲取這些狀態,已恢復Activity。當你把這些做完后,剩下的就是系統的事情了,系統會承諾,如果由于內存壓力殺死了你的Activity所在的進程,那么當你返回時,系統會重建你的應用進程,并幫助你恢復之前Activity的狀態。

  4. 提供一種在不同應用之間的用戶流(user flow)的方式,當然這要靠系統來協調。最經典的例子就是分享功能的實現。

對于Activity來說,系統并不關心的是:

一旦系統從Activity入口進入到你的APP UI之中,系統將不再關心Activity內部邏輯的組織。你可以將所有的應用邏輯全放入這一個Activity中,比如你可以手動地改變它的views,使用fragments或者其他框架,你也可以把你的應用邏輯分拆成額外的內部activities。你也可以三者同時使用(指的是改變views,使用fragemnts,分拆成額外的activities)。這些事情系統是毫不關心的,只要你遵循Activity與系統之間的約定(在適當的狀態下啟動它,正確地保存/恢復它的狀態)。

BroadcastReceiver

這是一種讓系統在正常的用戶流(user flow)之外,傳遞事件給APP的機制。最重要的是,因為這是另一個被精心定義的APP的入口,即使APP當前并不在運行,系統也可以將broadcasts傳遞給APP。所以,舉例來說,一個APP可以提前調度一個alarm,以便通知用戶一個馬上到來的事件,通過將這個alarm傳遞給該APP的一個BroadcastReceiver,在alarm發生之前,APP都沒必要運行。

對于BroadcastReceiver來說,系統并不關心的是:

在APP內部分發事件是一個與BroadcastReceiver接收事件完全不同的事,不管你是使用一些eventbus 框架,實現你自己的回調系統,還是任何其他方法...你都沒有理由使用系統的廣播機制,因為你并不是在App之間分發事件。(事實上,不使用系統的廣播機制還有一個很好的原因,這會帶來許多不必要的負擔,而且使用全局廣播機制來實現APP內部的事件分發會引發許多安全問題)。當然,我們也提供了一個LocalBroadcastManager便利類,它實現了一個純粹的進程內的intent分發系統,而且它的API與系統BroadcastReceiver API很相似,如果你喜歡當然也可以使用。但再次強調,你沒有理由在僅僅發生在APP中的事情上使用BroadcastReceiver機制。

Service

當由于各種各樣的原因需要APP在后臺運行時,Service就是一個這樣的入口。有兩種語義上截然不同的Services(一種是Started Service,一種是Bound Service)來告訴系統怎樣管理一個APP。

Started Service就相當于因為某種原因你的APP告訴系統:“系統大哥,我有事要干,請讓我一直運行,直到我告訴你我干完了。”(這里的我相當于APP,因為此時的Service就代表了APP,而系統是只跟APP對話的)這里的“事”可能是在后臺同步數據或者在用戶離開APP后播放音樂。

同時,Started Service又有兩種,一種是用戶可感知的,一種是用戶無法感知的。這兩種不同的Started Service會讓系統對它們采取不同的管理方式。

Service
                               |   
                       —————————————————— 
                       |                |
                   Started          Bound   
                     |
              ————————————————
             |               | 
      user awareness    user unawareness

(此圖為本人草畫,方便大家理解。)

  1. 播放音樂的Service是用戶可以直接感知的,所以Service會對系統說:“我想成為前臺(foreground),并且在通知欄掛一個通知,讓用戶能夠感到我的存在。”這種情況下,系統知道,必須使出吃奶的勁保證這個service進程的運行,因為如果這個進程宕掉,用戶會不高興。

  2. 另一種后臺Service是用戶無法直接感知的,所以系統可以更加靈活地處理這個Service的進程。在系統急需RAM以保證用戶眼前的事情正常運轉時,系統可能會允許該進程被殺死(然后可以在之后有能力時再啟動該Service)。

Bound Service 之所以會運行,是因為其他APP或者系統要使用它。通常情況該Service都會給其他進程提供一個API。在這種情況下,系統知道這兩個進程之間存在一個依賴關系。所以,如果進程A綁定了進程B中的一個Service,系統就會知道,它要為進程A保證進程B和它里面的Service正常運行。進一步講,如果進程A是用戶當前正在關心的進程,系統將知道把進程B也當作用戶正在關心的進程。

由于Service的靈活性(有好也有壞),Service已經成為各種類型的系統中一個非常有用的構建塊(building block)。實時墻紙,通知監聽器和許多其他的系統核心特性都被構建為Service,當它們需要運行時,系統再綁定它們。

對于Service,系統不關心的是:

Android不關心你的APP中那些不影響它怎樣對待你的進程的事,所以這些情況下,是沒有理由使用Service的。舉例來說,如果你想在后臺為你的UI下載數據,你不應該使用Service來做這件事----做這些事時,不告訴系統保持你的進程運行真的是很重要的,因為確實沒有必要!!這樣做也讓系統有更多的自由去管理你的進程,以便與用戶正在做的事情相協調( 注:可以讓系統在內存緊急的情況下,殺死你的進程,優先保證用戶正在做的事情,這里忍不住吐槽一句:每個APP肯定都會覺得自己是最重要的哈,Google開發Android的人也是典型的理想主義! )

如果你只是簡單地開啟了一個后臺線程來做數據下載(或者其他不是Service的辦法),你將會得到你想要的結果:如果用戶在你的UI里,系統將會確保你的進程運行,所以下載絕不會被中斷。當用戶離開了你的UI,你的進程仍將被保持(緩存)因而可以繼續下載數據,只要RAM不告急就行。

同樣,為了將你的APP的不同部分連接起來,你也沒有理由去綁定同一個進程中的Service。這樣做倒是沒有什么明顯的害處,因為系統會看到,該進程對自己有一個依賴,它將會忽略這個依賴,仍將你的APP當作普通進程看待。但是這樣做對你和系統實際上都是沒有必要的。作為替代,你可以使用單例或者其他進程內的模式來將你的APP的各部分連接到一起。

ContentProvider

最后,ContentProvider是一個專用的辦法,用來將你的APP的數據公開到其他地方。人們通常會將它們當作對數據庫的抽象,因為有許多的API和支持庫就是這樣使用ContentProvider的。但是從系統設計的角度,這并不是ContentProvider的初衷。

對于系統來說,ContentProvider實際上是一個入口,用于獲取一個APP內部的公開的被命名的數據項(data items),每個數據項都被一個URI scheme所標識。這樣,APP就可以決定怎樣將自己的數據項映射到一個URI scheme,怎樣將這個URI scheme公開給其他APP或者系統,好讓APP或者系統使用這個URI scheme來獲取自己內部的數據。這將讓系統能夠用一些很獨特的方式來管理你的APP:

  1. 將URI scheme公開出去并不要求你的APP一直保持運行,所以即使你的APP沒有運行,這些URI scheme也可以公開給任何APP和系統。只有當某個人告訴系統:“請把這個URI代表的數據拿給我。”時,系統將會讓你的APP運行起來向你索要對應于URI的數據項并返回給請求者。
  2. 這些URIs也提供了一個很重要的細粒度的安全模型。比如,你的APP可以將代表一張你的APP內的圖片的URI放在剪貼板上,但是讓它的ContendProvider 保持在鎖定狀態,所以沒有人能夠自由地獲取它。當其他APP從剪貼板上獲取了這個URI,并向系統請求獲取對應的圖片時,系統可以給它一個臨時的“URI許可”,以便讓它僅能獲取該URI所對應的圖片,你的APP的其他內容都是安全的。

對于ContentProvider,系統不關心的是:

在一個ContentProvider背后,你的APP如何管理你的數據,系統毫不關心;如果你不需要SQLite database中的結構化數據,你可以不使用SQLite。比如,FileProvider幫助類可以讓你的APP內的原始文件輕松通過一個ContentProvider獲取。

同樣,如果你不打算公開你的APP中的數據給其他人使用,你也可以不實現ContentProvider。是這樣的,因為圍繞著ContentProvider,有許多便利的方法,這些方法讓你很容易地將數據存入SQLite database,并用這些數據填充UI元素如ListView。但是如果這些方法讓你覺得實現自己的想法有許多困難,你可以不使用它們,請自由地選擇一個對你的APP來說合適的數據模型。

好了,文章翻譯完了,為了避免直譯的生硬晦澀,我花費了整整一天的時間,盡可能在不違背原文意思的情況下,加入了一些個性化的語句表達,目的就是方便大家的理解。

 

來自:http://www.jianshu.com/p/07b87084337f

 

 本文由用戶 MeredithCon 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!