Android 四大元件的解讀

zeroXuan發表於2019-04-30

這篇文章是轉載自milter:關於Android四大元件最權威最深刻最準確的解讀,翻譯自Dianne Hackborn發表在google+上的一篇post,她是google資深大牛,2005年就進入Android Framework團隊。即使在google內部,論起對Android系統的理解把握,鮮有出其右者。在文章中,她深刻地闡明瞭Android設計四大元件的初衷,各個元件的目的作用,適用情景。我相信,讀完此文,你會覺得重新認識了Android。如果想閱讀原文,請在google+上搜尋Dianne Hackborn。

“我應該怎樣設計我的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,ServiceContentProvider 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能夠載入自己之前的狀態

    個人理解:很顯然,系統所承諾的這種狀態恢復能力,是依靠ActivityonSaveInstanceStateonRestoreInstanceState方法,也就是說,你在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會讓系統對它們採取不同的管理方式。

Android 四大元件的解讀

  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來說合適的資料模型。

相關文章