用廣播 BroadcastReceiver 更新 UI 界面真的好嗎?全方位解析廣播

你們好,因爲公衆號有一個勘誤,因此在掘金從新更正後發佈本文。android

這是 面試系列 的第三期。本期咱們未來探討一下 Android 四大組件的重要組成部分:廣播 BroadcastReceiver。git

往期內容傳遞:
Android 面試:說說 Android 的四種啓動模式
Android 面試:如何理解 Activity 的生命週期github

前言

BroadcastReceiver 做爲 Android 四大組件之一,應用場景可謂很是之多。因此我相信任何一個有必定 Android 開發經驗的工程師都不會在這個題上栽跟斗。但,某些細節,或許咱們能夠注意一下。面試

實際上我在面試過程當中也遇到了這樣的題。下面請容許我用「柳學兄」的思路帶你們進入面試營。設計模式

BroadcastReceiver 內部基本原理是什麼?

Android 的廣播 BroadcastReceiver 是一個全局的監聽器,主要用於監聽 / 接收應用發出的廣播消息,並做出響應。其採用了設計模式中的 觀察者模式 ,可將廣播基於 消息訂閱者消息發佈者消息中心(AMS:即 Activity Manager Service)解耦,經過 Binder 機制造成訂閱關係。安全

圖片來源於網絡
圖片來源於網絡

說說 BroadcastReceiver 的兩種註冊方式

Android 廣播的兩種註冊方式確定難不倒任何人,實際上我估計也只有對少許的 Android 開發面試者纔會遇到這樣的題,這裏不會有什麼特別的,熟悉的能夠直接跳過bash

  • 靜態註冊
    靜態註冊廣播的方式只須要在 AndroidManifest.xml 裏經過 標籤聲明。下面附上一些屬性說明。網絡

    <receiver 
      android:enabled=["true" | "false"]
      //此 broadcastReceiver 可否接收其餘 App 發出的廣播
      //默認值是由 receiver 中有無 intent-filter 決定的:若是有 intent-filter,默認值爲 true,不然爲 false
      android:exported=["true" | "false"]
      android:icon="drawable resource"
      android:label="string resource"
      //繼承 BroadcastReceiver 子類的類名
      android:name=".mBroadcastReceiver"
      //具備相應權限的廣播發送者發送的廣播才能被此 BroadcastReceiver 所接收;
      android:permission="string"
      // BroadcastReceiver 運行所處的進程
      // 默認爲 App 的進程,能夠指定獨立的進程
      //注:Android 四大基本組件均可以經過此屬性指定本身的獨立進程
      android:process="string" >
    
      //用於指定此廣播接收器將接收的廣播類型
      //本示例中給出的是用於接收網絡狀態改變時發出的廣播
       <intent-filter>
            <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
      </intent-filter>
    </receiver>複製代碼
  • 動態註冊
    動態註冊方式是經過調用 Context 下面的 registerReceiver() 進行註冊,能夠調用 unregisterReceiver() 進行註銷。須要注意的是:動態廣播最好在 Activity 的 onResume() 註冊,並在 onPause() 進行註銷。

爲何建議動態廣播儘可能在 onPause() 進行註銷?

咱們能夠先看看 Activity 的生命週期。異步

圖片來源於網絡
圖片來源於網絡

首先有註冊就得有註銷,不然必定會形成內存泄漏。注意上面途中紅框圈住的部分。,閱讀官方源碼發現,當系統由於內存不足須要回收 Activity 佔用的資源時,Activity 在執行完 onPause() 方法後就可能面臨着被銷燬的危險,有些生命週期方法,如:onStop()onDestroy() 根本就不會執行,而 onPause() 因爲必定會調用的特殊性,天然是避免內存泄漏的好方法。工具

兩種註冊方式的區別也是能夠用圖一目瞭然。

圖片來源於網絡
圖片來源於網絡

說說 Android 的經常使用廣播類型吧

基本在 Android 領域經常使用的方式就是直接調用 Context 提供的方法 sendBroadcast()sendOrderBroadcase() 發送無序廣播和有序廣播。

  • 無序廣播
    無序廣播是徹底異步的,經過 Context.sendBroadcast() 方法來發送,從效率上來看,還算是比較高的。正如它的名稱同樣,無序廣播對全部的廣播接收者而言,是無序的。也就是說,全部接收者沒法肯定接收時序的順序,這樣也致使了,無序廣播沒法被中止。當它被髮送出去以後,它將通知全部這條廣播的接收者,直到沒有與之匹配的廣播接收者爲止。

  • 有序廣播
    有序廣播經過 Context.sendOrderedBroadcast() 方法來發送。有序廣播和無序廣播最大的不一樣,就是它能夠容許接收者設定優先級,它會按照接收者設定的優先級依次傳播。而高優先級的接收者,能夠對廣播的數據進行處理或者中止掉此條廣播的繼續傳播。廣播會先發送給優先級高 (android:priority) 的 Receiver,並且這個 Receiver 有權決定是繼續發送到下一個 Receiver 或者是直接終止廣播。

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

可能仍是有很多的朋友知道 Sticky 廣播方式。

  • 粘性廣播 Sticky
    Sticky 廣播和它的名字很像,它是一個具備粘性的廣播。它被髮出去以後,會一直滯留在系統中,直到有與之匹配的接收者,纔會將其發出去。它採用 Context.sendStickyBroadcast() 方法進行發送廣播。

    從官方文檔上能夠看到,若是想要發送一個 Sticky 廣播,須要具備 BROADCAST_STICKY 權限,這個能夠在 AndroidManifest.xml 中進行註冊,而若是沒有此權限,則會拋出 SecurityException 異常。

    對於系統而言,只會保留最後一條 Sticky 廣播,而且會一直保留下去,也就是說,若是咱們發送的 Sticky 廣播不被取消,當有一個接收者的時候就會收到它,再來一個仍是能收到。全部咱們須要在合適的實際,調用 removeStickyBoradcast() 方法,將其取消掉。

    從官方文檔中也能夠看到 StickyBroadcast 已經被標記爲 @Deprecated ,出於一些安全的考慮,已經將其標記爲廢棄,再也不推薦使用。咱們做爲開發者,對於一些被標記爲 @Depracated 的方法,使用起來仍是須要謹慎的。

有時候基於數據安全考慮,咱們想發送廣播只有本身(本進程)能接收到,怎麼處理?

首先,Android 中的廣播能夠跨進程通訊,由於 exported 對於有 Intent-filter 的狀況下默認爲 true。因此咱們難以有這樣的需求:

  • 對於某些敏感性的廣播,咱們不但願暴露給外部。
  • 其餘 App 可能會發出和當前 App intent-filter 相匹配的廣播,致使 App 不斷進行廣播接收和處理。

這真是一個壞消息,咱們必須讓咱們的應用變得有效率並足夠的安全

通常咱們能天然地想到在註冊廣播的時候把 exported 值設爲 false 並給 App 的廣播增長上權限,可問題是權限不夠是一個字符串,面對當前如此強大的反編譯技術,這終究是不安全的。

爲了解決這樣的問題,咱們不難想到能夠經過往主線程的消息池(Message Queue)裏發送消息,讓其作到只有主線程的 Handler 能夠分發處理它。或者在發送廣播的時候直接經過 Intent.setPackage(packageName) 指定廣播接收器的包名。

要不是咱們項目中有個 BroadcastUtil 工具類,我還以前真不知道 Support V4 包下還有這麼一個 LocalBroadcastManager 本地廣播類。

本地廣播 在 Android Support v4 : 21 版本後加入了咱們的你們庭。它使用 LocalBroadcastManager (如下簡稱 LBM)類來管理。

LocalBroadcast 的使用很是的簡單,只須要將 Broadcast 的對應 API,替換爲 LBM 爲咱們提供的 API 便可。

LBM 是一個單例對象,可使用 LocalBroadcastManager.getInstance(Context context) 方法獲取到。在 Context 中定義的和 Broadcast 相關的方法,在 LBM 中都有對應的 API 。很是有意思的是,LBM 爲了區分異步和同步,使用了 sendBroadcast()sendBroadcastSync() 方法來作爲區分。

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

廢話扯了這麼多,終於說到標題上的問題了。

直接回答:能夠,爲何不能夠呢?在實際開發中咱們不是常常這麼用麼?

很好,能夠確定你是一個真實的 Android 開發者了,不過在認證你的「合格」以前,想問問 BroadcastReceiver 的生命週期。

什麼?BroadcastReceiver 的生命週期?糟糕,面試前只複習了 Activity 和 Fragment 的生命週期,雜還有人問 BroadcastReceiver 的生命週期。

因此,你支支吾吾了。

其實仍是有比較多的人瞭解 BroadcastReceiver 的生命週期的。BroadcastReceiver 有生命週期,但比較短,並且很短。當它的 onReceive() 方法執行完成後,它的生命週期也就隨之結束了。這時候因爲 BroadcastReceiver 已經不處於 active 狀態,因此極有可能被系統幹掉。也就是說若是你在 onReceive() 去開線程進行異步操做或者打開 Dialog 都有可能在沒達到你要的結果時進程就被系統殺掉了。

因此,正確答案是?

更新 UI 界面這個定義太普遍了。實際開發中其實大多數狀況都是能夠採用 BroadcastReceiver 來更新 UI,因此也形成了不少人回答就想上面很確定和自信的回答能夠。

實際上咱們知道 Receiver 也是運行在主線程的,不能作耗時操做。雖然超時時間相對於 Activity 的 5 秒更高,有足足的 10 秒。但不意味着咱們實際開發中全部的更新 UI 界面操做時間都在安全範圍以內。

此外,對於頻繁更新 UI,也不推薦這種方式。Android 廣播的發送和接收都包含了必定的代價,它的傳輸都是經過 Binder 進程間通訊機制來實現的,那麼系統確定會爲了廣播能順利傳遞而作一些進程間通訊的準備。並且可能會因爲其它因素致使廣播發送和到達不許時(或者說接收會延遲)。

這種狀況可能嗎?

極可能,並且很容易發生。咱們要先了解 Android 的 ActivityManagerService 有一個專門的消息隊列來接收發送出來的廣播,sendBroadcast() 執行完後就當即返回,但這時發送來的廣播只是被放入到隊列,並不必定立刻被處理。當處理到當前廣播時,又會把這個廣播分發給註冊的廣播接收分發器ReceiverDispatcher,ReceiverDispatcher 最後又把廣播交給接 Receiver 所在的線程的消息隊列去處理(就是你熟悉的 UI 線程的 Message Queue)。

整個過程從發送 ActivityManagerService 到 ReceiverDispatcher 進行了兩次 Binder 進程間通訊,最後還要交到 UI 的消息隊列,若是基中有一個消息的處理阻塞了 UI,固然也會延遲你的 onReceive() 的執行。

BroadcastReceiver 和 EventBus 有啥不一樣?

EventBus 做爲 GitHub 上一個頗受歡迎的庫,目前也是有着 16.3 k 的星星,足以見其強大。

因此在很多面試中固然會遇到這樣的提問。這不,筆者在咕咚面試的時候就被面試官問到了這個題,又一個打臉,當時我像被電了一番,答的並不怎麼樣。

衆所周知,廣播是 Android 的四大組件之一。系統系統級的事件都是經過廣播來通知的,好比說網絡的變化、電量的變化、短信接收和發送狀態等。因此,若是是和 Android 系統相關的通知,咱們還得選擇本地廣播。

可是!!!廣播相對於其餘實現方式,是很重量級的,它消耗的資源較多。它的優點體如今和 SDK 的緊密聯繫,onReceive() 方法自帶了 Context 和 Intent 參數,因此在必定意義上實現了便捷性,但若是對 Context 和 Intent 應用不多或者說只作不多的交互的話,使用廣播真的就是一種浪費!!!

那 EventBus 呢?

先說說其優勢:

  • 調度靈活
    要說到優勢,這必定是我最早想到的。由於它真的是太靈活了,在實際開發中感受它就是一個機靈鬼,想去哪就去哪,根本就不須要像廣播同樣關注 Context 的注入與傳遞。父類對於通知的監聽和處理還能夠直接繼承給子類,能夠設置優先級讓 Subscriber 關注到優先級更高的通知,其粘滯事件(sticky events)可以保證通知不會因 Subscriber 的不在場而忽略。可繼承、優先級、粘滯,是 EventBus 比之於廣播、觀察者等方式最大的優勢,它們使得建立結構良好組織緊密的通知系統成爲可能。

  • 使用簡單
    進入到 EventBus 的官網,看一眼 README.md,簡直不能再簡單,簡簡單單三個步驟,再在 build.gradle 中添加一個依賴,輕輕鬆鬆搞定有木有?若是不想建立 EventBus 的實例,還能夠直接調用靜態方法 EventBus.getDefault() 獲取。

  • 快速且輕量
    做爲一個 GitHub 的明星項目,性能方面是能夠放心的。

EventBus 這麼棒,那咱們有組建通訊就用 EventBus 吧。

還真是人無完人,物無完物。EventBus 也有着它的致命弱點。EventBus 最大的缺點在於其邏輯性,直接看其代碼,一不當心根本看不通有沒有?另一個問題是,當程序較大後,觀察者獨有的接口膨脹缺點也會伴隨着你的項目,你能想象不少 Event 後綴類的感受嗎?

綜上,EventBus 因爲其針對統一進程,因此在某些複雜的狀況下單純依靠接口回調很差處理組件通訊的時候,直接去嘗試 EventBus 吧。

說了這麼多,在廣播和 EventBus 這個十字路口猶豫不決的時候,還會糾結選擇嗎?

歡迎關注南塵的公衆號:nanchen
若是你喜歡,你能夠選擇分享給你們。若是你有好的文章,歡迎投稿,讚揚所有歸你全部。

長按上方二維碼關注
                  作不完的開源,寫不完的矯情
                  一塊兒來看 nanchen 的成長筆記複製代碼
相關文章
相關標籤/搜索