1. 程式人生 > >[Android]廣播機制概述 一

[Android]廣播機制概述 一

廣播簡單描述

Android廣播分為兩個方面:廣播發送者和廣播接收者,通常情況下,BroadcastReceiver指的就是廣播接收者(廣播接收器)。廣播作為Android元件間的通訊方式,可以使用的場景如下:1

Note:

  1. 同一app內部的同一組件內的訊息通訊(單個或多個執行緒之間);
  2. 同一app內部的不同元件之間的訊息通訊(單個程序);
  3. 同一app具有多個程序的不同元件之間的訊息通訊;
  4. 不同app之間的元件之間訊息通訊;

  5. Android系統在特定情況下與App之間的訊息通訊。
    具體實現流程要點粗略概括如下:
    5.1 廣播接收者BroadcastReceiver通過Binder機制向AMS(Activity Manager Service)進行註冊;
    5.2 廣播發送者通過binder機制向AMS傳送廣播;
    5.3 AMS查詢符合相應條件(IntentFilter/Permission等)的BroadcastReceiver,將廣播發送到BroadcastReceiver(一般情況下是Activity)相應的訊息迴圈佇列中;
    5.4 訊息迴圈執行拿到此廣播,回撥BroadcastReceiver中的onReceive()方法。
    從實現原理看上,Android中的廣播使用了觀察者模式,基於訊息的釋出/訂閱事件模型。因此,從實現的角度來看,Android中的廣播將廣播的傳送者和接受者極大程度上解耦,使得系統能夠方便整合,更易擴充套件。

對於不同的廣播型別,以及不同的BroadcastReceiver註冊方式,具體實現上會有不同。但總體流程大致如上。
由此看來,廣播發送者和廣播接收者分別屬於觀察者模式中的訊息釋出和訂閱兩端,AMS屬於中間的處理中心。廣播發送者和廣播接收者的執行是非同步的,發出去的廣播不會關心有無接收者接收,也不確定接收者到底是何時才能接收到。顯然,整體流程與EventBus非常類似。

在上文說列舉的廣播機制具體可以使用的場景中,現分析實際應用中的適用性:

第一種情況,同一app內部的同一組件內的訊息通訊(單個或多個執行緒之間),實際應用中肯定是不會用到廣播機制的(雖然可以用),無論是使用擴充套件變數作用域、基於介面的回撥還是Handler-post/Handler-Message等方式,都可以直接處理此類問題,若適用廣播機制,顯然有些“殺雞牛刀”的感覺,會顯太“重”;

第二種情況, 同一app內部的不同元件之間的訊息通訊(單個程序),對於此類需求,在有些教複雜的情況下單純的依靠基於介面的回撥等方式不好處理,此時可以直接使用EventBus等,相對而言,EventBus由於是針對統一程序,用於處理此類需求非常適合,且輕鬆解耦。可以參見檔案《Android各元件/控制元件間通訊利器之EventBus》。

第三、四、五情形:由於涉及不同程序間的訊息通訊,此時根據實際業務使用廣播機制會顯得非常適宜。下面主要針對Android廣播中的具體知識點進行總結。

2. BroadcastReceiver

2.1 自定義BroadcastReceiver

不做介紹

2.2 靜態註冊

<receiver android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:name="string"
android:permission="string"
android:process="string" >
. . .
</receiver>
  • exported: 表示是否可以應用外被訪問, 此處為APP內,而非相同程序
    預設值為false,若是有intentFilter,則為true
  • process: 可指定單獨的程序名,預設為app
  • name: 對應相應的receiver類名
  • permission: 表示若要接收某個intent 必須得配置與之相應的許可權

靜態註冊的Receiver,若是APP程序未啟動時,是否會被呼叫到 ?
Refs: [AMS廣播發送原理]

2.3 動態註冊

IntentFilter filter = new IntentFilter();
filter.addAction(...);
...
//
registerReceivers(broadcastReceiver, filter)
unRegisterReceivers(broadcastReceiver)
//

register/unregister必須成對出現

3. 廣播型別

廣播型別是根據傳送時sendxxxBroadcast()方法來決定的,這個方法會決定AMS將intent傳給receiver的方式

Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
intent.putExtra("key", "value");
sendBroadcast(intent);
sendBroadcast(intent, [permission, bundle])

sendOrderedBroadcast
sendStickyBroadcast

3.1 Normal Broadcast

普通廣播的傳送類似以上程式碼, 發出後根據IntentFilter來匹配相應的receivers
特點:1. 無序的,為何普通廣播是無序的呢? 參考:BroadcastQueue 動態廣播的分發

3.2 System Broadcast

必須由系統程序來發送,在AMS分發時會根據callUid來校驗
類似的系統廣播有:開關機, 網路變化,資訊強度變化, 時間更新, WIFI資訊變化,亮度及電量變化等等

3.3 Ordered Broadcast

有序廣播,是指廣播發出後,Ams/BroadcastQueue 在分發廣播時是按一定的順序來分發下去

sendOrderedBroadcast(intent...) // 傳送方式與接收方式都有所區別
  1. 接收順序

    NOTE:
    Order廣播是以一定的順序來接收的: Prority > registered > static register

  2. 可編輯或取消

 //並且可對intent中的內容進行修改
isOrderedBroadcast()
abortBroadcast()

3.4 Sticky Broadcast (已經過時,不深入討論)

  • 粘性廣播(在 android 5.0/api 21中deprecated,不再推薦使用,相應的還有粘性有序廣播,同樣已經deprecated)
  • 傳送廣播後一直在後臺快取中,再次傳送時,則會使用快取中的Broadcast
  • 下一次註冊時,註冊後立即會接收到cache中的廣播,除非不再使用removeStickyBroadcast
sendStickyBroadcast(intent)
removeStickyBroadcast(intent)

3.5 Local Broadcast

指在應用內接收的廣播(以APP程序為限),區別於Normal Broadcast(全域性廣播)
預設的廣播發出來後,會以APP跨程序通訊的方式,可能會由其它程序的receiver來處理

Refs: Ams廣播原理,在收集廣播時,會判斷接收者為specify uid或是all_user

對於有intent-filter的情況下預設值是true,由此將可能出現安全隱患如下:

  1. 其他App可能會針對性的發出與當前App intent-filter相匹配的廣播,由此導致當前App不斷接收到廣播並處理;
  2. 其他App可以註冊與當前App一致的intent-filter用於接收廣播,獲取廣播具體資訊。

無論哪種情形,這些安全隱患都確實是存在的。由此,最常見的增加安全性的方案是:

  1. 對於同一App內部發送和接收廣播,將exported屬性人為設定成false,使得非本App內部發出的此廣播不被接收;
  2. 在廣播發送和接收時,都增加上相應的permission,用於許可權驗證;
  3. 傳送廣播時,指定特定廣播接收器所在的包名,具體是通過intent.setPackage(packageName)指定在,這樣此廣播將只會傳送到此包中的App內與之相匹配的有效廣播接收器中。

App應用內廣播可以理解成一種區域性廣播的形式,廣播的傳送者和接收者都同屬於一個App。實際的業務需求中,App應用內廣播確實可能需要用到。同時,之所以使用應用內廣播時,而不是使用全域性廣播的形式,更多的考慮到的是Android廣播機制中的安全性問題。

相比於全域性廣播,App應用內廣播優勢體現在:
1. 安全性更高;
2. 更加高效。

為此,Android v4相容包中給出了封裝好的LocalBroadcastManager類,用於統一處理App應用內的廣播問題,使用方式上與通常的全域性廣播幾乎相同,只是註冊/取消註冊廣播接收器和傳送廣播時將主調context變成了LocalBroadcastManager的單一例項。

程式碼片段如下: 提升即效率[^2]

//registerReceiver(mBroadcastReceiver, intentFilter);
  //註冊應用內廣播接收器
  localBroadcastManager = LocalBroadcastManager.getInstance(this);
  localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);

 //unregisterReceiver(mBroadcastReceiver);
  //取消註冊應用內廣播接收器
  localBroadcastManager.unregisterReceiver(mBroadcastReceiver);

 Intent intent = new Intent();
 intent.setAction(BROADCAST_ACTION);
 intent.putExtra("name", "qqyumidi");
 //sendBroadcast(intent);
//傳送應用內廣播
localBroadcastManager.sendBroadcast(intent);