Android 面試(三):用廣播 BroadcastReceiver 更新 UI 界面真的好嗎?

這是 面試系列 的第三期算柳。本期我們將來探討一下 Android 四大組件的重要組成部分:廣播 BroadcastReceiver掸茅。

往期內(nèi)容傳遞:
Android 面試:說說 Android 的四種啟動(dòng)模式
Android 面試:如何理解 Activity 的生命周期

前言

BroadcastReceiver 作為 Android 四大組件之一骂因,應(yīng)用場景可謂非常之多方庭。所以我相信任何一個(gè)有一定 Android 開發(fā)經(jīng)驗(yàn)的工程師都不會(huì)在這個(gè)題上栽跟斗翩瓜。但跨扮,某些細(xì)節(jié)序无,或許我們可以注意一下验毡。

實(shí)際上我在面試過程中也遇到了這樣的題。下面請(qǐng)?jiān)试S我用「柳學(xué)兄」的思路帶大家進(jìn)入面試營帝嗡。

BroadcastReceiver 內(nèi)部基本原理是什么晶通?

Android 的廣播 BroadcastReceiver 是一個(gè)全局的監(jiān)聽器,主要用于監(jiān)聽 / 接收應(yīng)用發(fā)出的廣播消息哟玷,并作出響應(yīng)狮辽。其采用了設(shè)計(jì)模式中的 觀察者模式 ,可將廣播基于 消息訂閱者 巢寡、消息發(fā)布者喉脖、消息中心(AMS:即 Activity Manager Service)解耦,通過 Binder 機(jī)制形成訂閱關(guān)系抑月。

圖片來源于網(wǎng)絡(luò)

說說 BroadcastReceiver 的兩種注冊(cè)方式

Android 廣播的兩種注冊(cè)方式肯定難不倒任何人树叽,實(shí)際上我估計(jì)也只有對(duì)少量的 Android 開發(fā)面試者才會(huì)遇到這樣的題,這里不會(huì)有什么特別的谦絮,熟悉的可以直接跳過题诵。

  • 靜態(tài)注冊(cè)
    靜態(tài)注冊(cè)廣播的方式只需要在 AndroidManifest.xml 里通過 <receiver> 標(biāo)簽聲明。下面附上一些屬性說明层皱。
<receiver 
    android:enabled=["true" | "false"]
    //此 broadcastReceiver 能否接收其他 App 發(fā)出的廣播
    //默認(rèn)值是由 receiver 中有無 intent-filter 決定的:如果有 intent-filter性锭,默認(rèn)值為 true,否則為 false
    android:exported=["true" | "false"]
    android:icon="drawable resource"
    android:label="string resource"
    //繼承 BroadcastReceiver 子類的類名
    android:name=".mBroadcastReceiver"
    //具有相應(yīng)權(quán)限的廣播發(fā)送者發(fā)送的廣播才能被此 BroadcastReceiver 所接收叫胖;
    android:permission="string"
    // BroadcastReceiver 運(yùn)行所處的進(jìn)程
    // 默認(rèn)為 App 的進(jìn)程草冈,可以指定獨(dú)立的進(jìn)程
    //注:Android 四大基本組件都可以通過此屬性指定自己的獨(dú)立進(jìn)程
    android:process="string" >

    //用于指定此廣播接收器將接收的廣播類型
    //本示例中給出的是用于接收網(wǎng)絡(luò)狀態(tài)改變時(shí)發(fā)出的廣播
     <intent-filter>
          <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>
  • 動(dòng)態(tài)注冊(cè)
    動(dòng)態(tài)注冊(cè)方式是通過調(diào)用 Context 下面的 registerReceiver() 進(jìn)行注冊(cè),可以調(diào)用 unregisterReceiver() 進(jìn)行注銷臭家。需要注意的是:動(dòng)態(tài)廣播最好在 Activity 的 onResume() 注冊(cè)疲陕,并在 onPause() 進(jìn)行注銷。

為什么建議動(dòng)態(tài)廣播盡量在 onPause() 進(jìn)行注銷钉赁?

我們可以先看看 Activity 的生命周期蹄殃。

圖片來源于網(wǎng)絡(luò)

首先有注冊(cè)就得有注銷,否則一定會(huì)造成內(nèi)存泄漏你踩。注意上面途中紅框圈住的部分诅岩。,閱讀官方源碼發(fā)現(xiàn)带膜,當(dāng)系統(tǒng)因?yàn)閮?nèi)存不足需要回收 Activity 占用的資源時(shí)吩谦,Activity 在執(zhí)行完 onPause() 方法后就可能面臨著被銷毀的危險(xiǎn),有些生命周期方法膝藕,如:onStop()式廷、onDestroy() 根本就不會(huì)執(zhí)行,而 onPause() 由于一定會(huì)調(diào)用的特殊性芭挽,自然是避免內(nèi)存泄漏的好方法滑废。

兩種注冊(cè)方式的區(qū)別也是可以用圖一目了然蝗肪。


圖片來源于網(wǎng)絡(luò)

說說 Android 的常用廣播類型吧

基本在 Android 領(lǐng)域常用的方式就是直接調(diào)用 Context 提供的方法 sendBroadcast()sendOrderBroadcase() 發(fā)送無序廣播和有序廣播。

  • 無序廣播
    無序廣播是完全異步的蠕趁,通過 Context.sendBroadcast() 方法來發(fā)送薛闪,從效率上來看,還算是比較高的俺陋。正如它的名稱一樣豁延,無序廣播對(duì)所有的廣播接收者而言,是無序的腊状。也就是說诱咏,所有接收者無法確定接收時(shí)序的順序,這樣也導(dǎo)致了寿酌,無序廣播無法被停止胰苏。當(dāng)它被發(fā)送出去之后,它將通知所有這條廣播的接收者醇疼,直到?jīng)]有與之匹配的廣播接收者為止硕并。

  • 有序廣播
    有序廣播通過 Context.sendOrderedBroadcast() 方法來發(fā)送。有序廣播和無序廣播最大的不同秧荆,就是它可以允許接收者設(shè)定優(yōu)先級(jí)倔毙,它會(huì)按照接收者設(shè)定的優(yōu)先級(jí)依次傳播。而高優(yōu)先級(jí)的接收者乙濒,可以對(duì)廣播的數(shù)據(jù)進(jìn)行處理或者停止掉此條廣播的繼續(xù)傳播陕赃。廣播會(huì)先發(fā)送給優(yōu)先級(jí)高 (android:priority) 的 Receiver,而且這個(gè) Receiver 有權(quán)決定是繼續(xù)發(fā)送到下一個(gè) Receiver 或者是直接終止廣播颁股。

除了無序廣播和有序廣播么库,還有其他的類型嗎?

可能還是有不少的朋友知道 Sticky 廣播方式甘有。

  • 粘性廣播 Sticky
    Sticky 廣播和它的名字很像诉儒,它是一個(gè)具有粘性的廣播。它被發(fā)出去之后亏掀,會(huì)一直滯留在系統(tǒng)中忱反,直到有與之匹配的接收者,才會(huì)將其發(fā)出去滤愕。它采用 Context.sendStickyBroadcast() 方法進(jìn)行發(fā)送廣播温算。

    從官方文檔上可以看到,如果想要發(fā)送一個(gè) Sticky 廣播间影,需要具有 BROADCAST_STICKY 權(quán)限注竿,這個(gè)可以在 AndroidManifest.xml 中進(jìn)行注冊(cè),而如果沒有此權(quán)限,則會(huì)拋出 SecurityException 異常巩割。

    對(duì)于系統(tǒng)而言胰丁,只會(huì)保留最后一條 Sticky 廣播,并且會(huì)一直保留下去喂分,也就是說,如果我們發(fā)送的 Sticky 廣播不被取消机蔗,當(dāng)有一個(gè)接收者的時(shí)候就會(huì)收到它蒲祈,再來一個(gè)還是能收到。所有我們需要在合適的實(shí)際萝嘁,調(diào)用 removeStickyBoradcast() 方法梆掸,將其取消掉。

    從官方文檔中也可以看到 StickyBroadcast 已經(jīng)被標(biāo)記為 @Deprecated 牙言,出于一些安全的考慮酸钦,已經(jīng)將其標(biāo)記為廢棄,不再推薦使用咱枉。我們作為開發(fā)者卑硫,對(duì)于一些被標(biāo)記為 @Depracated 的方法,使用起來還是需要謹(jǐn)慎的蚕断。

有時(shí)候基于數(shù)據(jù)安全考慮欢伏,我們想發(fā)送廣播只有自己(本進(jìn)程)能接收到,怎么處理亿乳?

首先硝拧,Android 中的廣播可以跨進(jìn)程通信,因?yàn)?exported 對(duì)于有 Intent-filter 的情況下默認(rèn)為 true葛假。所以我們難以有這樣的需求:

  • 對(duì)于某些敏感性的廣播障陶,我們不希望暴露給外部。
  • 其他 App 可能會(huì)發(fā)出和當(dāng)前 App intent-filter 相匹配的廣播聊训,導(dǎo)致 App 不斷進(jìn)行廣播接收和處理抱究。

這真是一個(gè)壞消息,我們必須讓我們的應(yīng)用變得有效率并足夠的安全魔眨。

一般我們能自然地想到在注冊(cè)廣播的時(shí)候把 exported 值設(shè)為 false 并給 App 的廣播增加上權(quán)限媳维,可問題是權(quán)限不夠是一個(gè)字符串,面對(duì)當(dāng)前如此強(qiáng)大的反編譯技術(shù)遏暴,這終究是不安全的侄刽。

為了解決這樣的問題,我們不難想到可以通過往主線程的消息池(Message Queue)里發(fā)送消息朋凉,讓其做到只有主線程的 Handler 可以分發(fā)處理它州丹。或者在發(fā)送廣播的時(shí)候直接通過 Intent.setPackage(packageName) 指定廣播接收器的包名。

要不是我們項(xiàng)目中有個(gè) BroadcastUtil 工具類墓毒,我還之前真不知道 Support V4 包下還有這么一個(gè) LocalBroadcastManager 本地廣播類吓揪。

本地廣播 在 Android Support v4 : 21 版本后加入了我們的大家庭。它使用 LocalBroadcastManager (以下簡稱 LBM)類來管理所计。

LocalBroadcast 的使用非常的簡單柠辞,只需要將 Broadcast 的對(duì)應(yīng) API,替換為 LBM 為我們提供的 API 即可主胧。

LBM 是一個(gè)單例對(duì)象叭首,可以使用 LocalBroadcastManager.getInstance(Context context) 方法獲取到。在 Context 中定義的和 Broadcast 相關(guān)的方法踪栋,在 LBM 中都有對(duì)應(yīng)的 API 焙格。非常有意思的是,LBM 為了區(qū)分異步和同步夷都,使用了 sendBroadcast()sendBroadcastSync() 方法來做為區(qū)分眷唉。

在 Android 中用廣播來更新 UI 界面好嗎?

廢話扯了這么多囤官,終于說到標(biāo)題上的問題了冬阳。

直接回答:可以,為什么不可以呢党饮?在實(shí)際開發(fā)中我們不是經(jīng)常這么用么摩泪?

很好,可以肯定你是一個(gè)真實(shí)的 Android 開發(fā)者了劫谅,不過在認(rèn)證你的「合格」之前见坑,想問問 BroadcastReceiver 的生命周期。

什么捏检?BroadcastReceiver 的生命周期荞驴?糟糕,面試前只復(fù)習(xí)了 Activity 和 Fragment 的生命周期贯城,雜還有人問 BroadcastReceiver 的生命周期熊楼。

所以,你支支吾吾了能犯。

其實(shí)還是有比較多的人了解 BroadcastReceiver 的生命周期的鲫骗。BroadcastReceiver 有生命周期,但比較短踩晶,而且很短执泰。當(dāng)它的 onReceive() 方法執(zhí)行完成后,它的生命周期也就隨之結(jié)束了渡蜻。這時(shí)候由于 BroadcastReceiver 已經(jīng)不處于 active 狀態(tài)术吝,所以極有可能被系統(tǒng)干掉计济。也就是說如果你在 onReceive() 去開線程進(jìn)行異步操作或者打開 Dialog 都有可能在沒達(dá)到你要的結(jié)果時(shí)進(jìn)程就被系統(tǒng)殺掉了。

所以排苍,正確答案是沦寂?

更新 UI 界面這個(gè)定義太廣泛了。實(shí)際開發(fā)中其實(shí)大多數(shù)情況都是可以采用 BroadcastReceiver 來更新 UI淘衙,所以也造成了很多人回答就想上面很肯定和自信的回答可以传藏。

實(shí)際上我們知道 Receiver 也是運(yùn)行在主線程的,不能做耗時(shí)操作彤守。雖然超時(shí)時(shí)間相對(duì)于 Activity 的 5 秒更高漩氨,有足足的 10 秒。但不意味著我們實(shí)際開發(fā)中所有的更新 UI 界面操作時(shí)間都在安全范圍之內(nèi)遗增。

此外,對(duì)于頻繁更新 UI款青,也不推薦這種方式做修。Android 廣播的發(fā)送和接收都包含了一定的代價(jià),它的傳輸都是通過 Binder 進(jìn)程間通信機(jī)制來實(shí)現(xiàn)的抡草,那么系統(tǒng)肯定會(huì)為了廣播能順利傳遞而做一些進(jìn)程間通信的準(zhǔn)備饰及。而且可能會(huì)由于其它因素導(dǎo)致廣播發(fā)送和到達(dá)不準(zhǔn)時(shí)(或者說接收會(huì)延遲)。

這種情況可能嗎康震?

很可能燎含,而且很容易發(fā)生。我們要先了解 Android 的 ActivityManagerService 有一個(gè)專門的消息隊(duì)列來接收發(fā)送出來的廣播腿短,sendBroadcast() 執(zhí)行完后就立即返回屏箍,但這時(shí)發(fā)送來的廣播只是被放入到隊(duì)列,并不一定馬上被處理橘忱。當(dāng)處理到當(dāng)前廣播時(shí)赴魁,又會(huì)把這個(gè)廣播分發(fā)給注冊(cè)的廣播接收分發(fā)器ReceiverDispatcher,ReceiverDispatcher 最后又把廣播交給接 Receiver 所在的線程的消息隊(duì)列去處理(就是你熟悉的 UI 線程的 Message Queue)钝诚。

整個(gè)過程從發(fā)送 ActivityManagerService 到 ReceiverDispatcher 進(jìn)行了兩次 Binder 進(jìn)程間通信颖御,最后還要交到 UI 的消息隊(duì)列,如果基中有一個(gè)消息的處理阻塞了 UI凝颇,當(dāng)然也會(huì)延遲你的 onReceive() 的執(zhí)行潘拱。

BroadcastReceiver 和 EventBus 有啥不同?

EventBus 作為 GitHub 上一個(gè)頗受歡迎的庫拧略,目前也是有著 16.3 k 的星星芦岂,足以見其強(qiáng)大。

所以在不少面試中當(dāng)然會(huì)遇到這樣的提問垫蛆。這不盔腔,筆者在咕咚面試的時(shí)候就被面試官問到了這個(gè)題,又一個(gè)打臉,當(dāng)時(shí)我像被電了一番弛随,答的并不怎么樣瓢喉。

眾所周知,廣播是 Android 的四大組件之一舀透。系統(tǒng)系統(tǒng)級(jí)的事件都是通過廣播來通知的栓票,比如說網(wǎng)絡(luò)的變化、電量的變化愕够、短信接收和發(fā)送狀態(tài)等走贪。所以,如果是和 Android 系統(tǒng)相關(guān)的通知惑芭,我們還得選擇本地廣播坠狡。

但是!K旄逃沿!廣播相對(duì)于其他實(shí)現(xiàn)方式,是很重量級(jí)的幻锁,它消耗的資源較多凯亮。它的優(yōu)勢體現(xiàn)在和 SDK 的緊密聯(lián)系,onReceive() 方法自帶了 Context 和 Intent 參數(shù)哄尔,所以在一定意義上實(shí)現(xiàn)了便捷性假消,但如果對(duì) Context 和 Intent 應(yīng)用很少或者說只做很少的交互的話,使用廣播真的就是一種浪費(fèi)A虢印8晦帧!

那 EventBus 呢鸣戴?

先說說其優(yōu)點(diǎn):

  • 調(diào)度靈活
    要說到優(yōu)點(diǎn)媒峡,這一定是我最先想到的。因?yàn)樗娴氖翘`活了葵擎,在實(shí)際開發(fā)中感覺它就是一個(gè)機(jī)靈鬼谅阿,想去哪就去哪,根本就不需要像廣播一樣關(guān)注 Context 的注入與傳遞酬滤。父類對(duì)于通知的監(jiān)聽和處理還可以直接繼承給子類签餐,可以設(shè)置優(yōu)先級(jí)讓 Subscriber 關(guān)注到優(yōu)先級(jí)更高的通知,其粘滯事件(sticky events)能夠保證通知不會(huì)因 Subscriber 的不在場而忽略盯串。可繼承氯檐、優(yōu)先級(jí)、粘滯体捏,是 EventBus 比之于廣播冠摄、觀察者等方式最大的優(yōu)點(diǎn)糯崎,它們使得創(chuàng)建結(jié)構(gòu)良好組織緊密的通知系統(tǒng)成為可能。

  • 使用簡單
    進(jìn)入到 EventBus 的官網(wǎng)河泳,看一眼 README.md沃呢,簡直不能再簡單,簡簡單單三個(gè)步驟拆挥,再在 build.gradle 中添加一個(gè)依賴薄霜,輕輕松松搞定有木有?如果不想創(chuàng)建 EventBus 的實(shí)例纸兔,還可以直接調(diào)用靜態(tài)方法 EventBus.getDefault() 獲取惰瓜。

  • 快速且輕量
    作為一個(gè) GitHub 的明星項(xiàng)目,性能方面是可以放心的汉矿。

EventBus 這么棒崎坊,那我們有組建通信就用 EventBus 吧。

還真是人無完人洲拇,物無完物奈揍。EventBus 也有著它的致命弱點(diǎn)。EventBus 最大的缺點(diǎn)在于其邏輯性呻待,直接看其代碼,一不小心根本看不通有沒有队腐?另外一個(gè)問題是蚕捉,當(dāng)程序較大后,觀察者獨(dú)有的接口膨脹缺點(diǎn)也會(huì)伴隨著你的項(xiàng)目柴淘,你能想象很多 Event 后綴類的感覺嗎迫淹?

綜上,EventBus 由于其針對(duì)統(tǒng)一進(jìn)程为严,所以在某些復(fù)雜的情況下單純依靠接口回調(diào)不好處理組件通信的時(shí)候敛熬,直接去嘗試 EventBus 吧。

說了這么多第股,在廣播和 EventBus 這個(gè)十字路口猶豫不決的時(shí)候应民,還會(huì)糾結(jié)選擇嗎?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末夕吻,一起剝皮案震驚了整個(gè)濱河市诲锹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌涉馅,老刑警劉巖归园,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異稚矿,居然都是意外死亡庸诱,警方通過查閱死者的電腦和手機(jī)捻浦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桥爽,“玉大人朱灿,你說我怎么就攤上這事【鬯” “怎么了母剥?”我有些...
    開封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長形导。 經(jīng)常有香客問我环疼,道長,這世上最難降的妖魔是什么朵耕? 我笑而不...
    開封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任炫隶,我火速辦了婚禮,結(jié)果婚禮上阎曹,老公的妹妹穿的比我還像新娘伪阶。我一直安慰自己,他們只是感情好处嫌,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開白布栅贴。 她就那樣靜靜地躺著,像睡著了一般熏迹。 火紅的嫁衣襯著肌膚如雪檐薯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天注暗,我揣著相機(jī)與錄音坛缕,去河邊找鬼。 笑死捆昏,一個(gè)胖子當(dāng)著我的面吹牛赚楚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骗卜,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼宠页,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了寇仓?” 一聲冷哼從身側(cè)響起勇皇,我...
    開封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎焚刺,沒想到半個(gè)月后敛摘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乳愉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年兄淫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了屯远。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捕虽,死狀恐怖慨丐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情泄私,我是刑警寧澤房揭,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站晌端,受9級(jí)特大地震影響捅暴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咧纠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一蓬痒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧漆羔,春花似錦梧奢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鸟顺,卻和暖如春惦蚊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诊沪。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來泰國打工养筒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留曾撤,地道東北人端姚。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像挤悉,于是被迫代替她去往敵國和親渐裸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

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