Android 廣播

廣播接收器 是Android的四大組件之一,可見廣播在Android中的重要性;

1. 什么是廣播?

廣播(Broadcast)是組件之間傳遞數(shù)據(jù)的一種機制,也是一種方式。

2. 廣播的作用及使用場景

作用
?傳遞數(shù)據(jù),傳遞信息;
?應(yīng)用內(nèi)或是應(yīng)用之間傳遞數(shù)據(jù),都可以使用廣播。

場景

  1. 同一app內(nèi)部的同一組件內(nèi)的消息通信(單個或多個線程之間),建議使用Handler;
  1. 同一app內(nèi)部的不同組件之間的消息通信(單個進程),推薦使用《Android各組件/控件間通信利器之EventBus》。;
  2. 同一app具有多個進程的不同組件之間的消息通信,推薦使用廣播;
  3. 不同app之間的組件之間消息通信,推薦使用廣播;
  4. Android系統(tǒng)在特定情況下與App之間的消息通信,推薦使用廣播。

3. 廣播的分類

根據(jù)廣播的發(fā)送方式,可以將其分為以下五種類型:

編號 名稱 描述
1 Normal Broadcast 普通廣播
2 System Broadcast 系統(tǒng)廣播
3 Ordered broadcast 有序廣播
4 Sticky Broadcast 粘性廣播
( android 5.0/api 21中不再推薦使用)
5 Local Broadcast App應(yīng)用內(nèi)廣播

下面分別總結(jié)下各種類型的發(fā)送方式及其特點。

  1. Normal Broadcast:普通廣播
    此處將普通廣播界定為:開發(fā)者自己定義的intent,以context.sendBroadcast_"AsUser"(intent, ...)形式。
    具體可以使用的方法有:
sendBroadcast(intent)/sendBroadcast(intent, receiverPermission)/sendBroadcastAsUser(intent, userHandler)/sendBroadcastAsUser(intent, userHandler,receiverPermission)。

普通廣播會被注冊了的相應(yīng)的感興趣(intent-filter匹配)接收,且順序是無序的。如果發(fā)送廣播時有相應(yīng)的權(quán)限要求,BroadCastReceiver如果想要接收此廣播,也需要有相應(yīng)的權(quán)限。

  1. System Broadcast: 系統(tǒng)廣播
    Android系統(tǒng)中內(nèi)置了多個系統(tǒng)廣播,只要涉及到手機的基本操作,基本上都會發(fā)出相應(yīng)的系統(tǒng)廣播。如:開啟啟動,網(wǎng)絡(luò)狀態(tài)改變,拍照,屏幕關(guān)閉與開啟,點亮不足等等。每個系統(tǒng)廣播都具有特定的intent-filter,其中主要包括具體的action,系統(tǒng)廣播發(fā)出后,將被相應(yīng)的BroadcastReceiver接收。系統(tǒng)廣播在系統(tǒng)內(nèi)部當(dāng)特定事件發(fā)生時,有系統(tǒng)自動發(fā)出。
  1. Ordered broadcast:有序廣播
    同步執(zhí)行的廣播,同一時刻只能有一個接收器接收到,當(dāng)該接收器完成之后才能繼續(xù)傳播,有先后順序,可以被接收器截斷。
    有序廣播的有序廣播中的“有序”是針對廣播接收者而言的,指的是發(fā)送出去的廣播被BroadcastReceiver按照先后循序接收。有序廣播的定義過程與普通廣播無異,只是其的主要發(fā)送方式變?yōu)椋?/li>
sendOrderedBroadcast(intent, receiverPermission, ...)

對于有序廣播,其主要特點總結(jié)如下:

  1. 多個具當(dāng)前已經(jīng)注冊且有效的BroadcastReceiver接收有序廣播時,是按照先后順序接收的,先后順序判定標(biāo)準(zhǔn)遵循為:將當(dāng)前系統(tǒng)中所有有效的動態(tài)注冊和靜態(tài)注冊的BroadcastReceiver按照priority屬性值從大到小排序,對于具有相同的priority的動態(tài)廣播和靜態(tài)廣播,動態(tài)廣播會排在前面。
  1. 先接收的BroadcastReceiver可以對此有序廣播進行截斷,使后面的BroadcastReceiver不再接收到此廣播,也可以對廣播進行修改,使后面的BroadcastReceiver接收到廣播后解析得到錯誤的參數(shù)值。當(dāng)然,一般情況下,不建議對有序廣播進行此類操作,尤其是針對系統(tǒng)中的有序廣播。
  2. Sticky Broadcast:粘性廣播(在 android 5.0/api 21中deprecated,不再推薦使用,相應(yīng)的還有粘性有序廣播,同樣已經(jīng)deprecated)。
    既然已經(jīng)deprecated,此處不再多做總結(jié)。
  3. Local Broadcast:App應(yīng)用內(nèi)廣播(此處的App應(yīng)用以App應(yīng)用進程為界)

由前文闡述可知,Android中的廣播可以跨進程甚至跨App直接通信,且注冊是exported對于有intent-filter的情況下默認值是true,由此將可能出現(xiàn)安全隱患如下:

  1. 其他App可能會針對性的發(fā)出與當(dāng)前App intent-filter相匹配的廣播,由此導(dǎo)致當(dāng)前App不斷接收到廣播并處理;
  2. 其他App可以注冊與當(dāng)前App一致的intent-filter用于接收廣播,獲取廣播具體信息。

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

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

App應(yīng)用內(nèi)廣播可以理解成一種局部廣播的形式,廣播的發(fā)送者和接收者都同屬于一個App。實際的業(yè)務(wù)需求中,App應(yīng)用內(nèi)廣播確實可能需要用到。同時,之所以使用應(yīng)用內(nèi)廣播時,而不是使用全局廣播的形式,更多的考慮到的是Android廣播機制中的安全性問題。
相比于全局廣播,App應(yīng)用內(nèi)廣播優(yōu)勢體現(xiàn)在:

1.安全性更高;
2.更加高效。

為此,Android v4兼容包中給出了封裝好的LocalBroadcastManager類,用于統(tǒng)一處理App應(yīng)用內(nèi)的廣播問題,使用方式上與通常的全局廣播幾乎相同,只是注冊/取消注冊廣播接收器和發(fā)送廣播時將主調(diào)context變成了LocalBroadcastManager的單一實例。

代碼片段如下:

//registerReceiver(mBroadcastReceiver, intentFilter);
//注冊應(yīng)用內(nèi)廣播接收器
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);

//unregisterReceiver(mBroadcastReceiver);
//取消注冊應(yīng)用內(nèi)廣播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver);
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
intent.putExtra("name", "qqyumidi");
//sendBroadcast(intent);
//發(fā)送應(yīng)用內(nèi)廣播
localBroadcastManager.sendBroadcast(intent);

本地廣播(在應(yīng)用內(nèi)部傳遞的廣播)

  1. 本地廣播的優(yōu)勢
    所有數(shù)據(jù)交換都在本程序內(nèi),不必擔(dān)心數(shù)據(jù)泄露
    其他程序無法將廣播發(fā)送到本程序內(nèi)部
    更高效
  1. 本地廣播要注意的地方
    只能動態(tài)注冊,不能靜態(tài)注冊
  2. 本地廣播的用法

實例化LocalBroadcastManager

mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);

發(fā)送廣播(調(diào)用LocalBroadcastManager的sendBroadcast方法)

Intent intent = new Intent("com.foxnickel.broadcasttest.LOCAL_BROADCAST");
mLocalBroadcastManager.sendBroadcast(intent);//發(fā)送本地廣播

接收廣播:
新建廣播接收器(與普通的廣播接收器一樣)

class LocalReceiver extends BroadcastReceiver{

   @Override
   public void onReceive(Context context, Intent intent) {
       Toast.makeText(context,"received local broadcast",Toast.LENGTH_SHORT).show();
   }
}

注冊接收器,接收廣播(調(diào)用LocalBroadcastManager的registerBroadcastReceiver方法)

   IntentFilter intentFilter = new IntentFilter("com.foxnickel.broadcasttest.LOCAL_BROADCAST");
   LocalReceiver localReceiver = new LocalReceiver();
   mLocalBroadcastManager.registerReceiver(localReceiver,intentFilter);

記得銷毀接收器

@Override
protected void onDestroy() {
   super.onDestroy();
   mLocalBroadcastManager.unregisterReceiver(localReceiver);
}

4. 廣播的具體實現(xiàn)和應(yīng)用

實現(xiàn)流程

  1. 注冊廣播接收者,廣播接收者BroadcastReceiver通過Binder機制向AMS(Activity Manager Service)進行注冊;
  2. 廣播發(fā)送者發(fā)送廣播,廣播發(fā)送者通過binder機制向AMS發(fā)送廣播;
  3. 分發(fā)廣播,根據(jù)廣播信息查找廣播接收者,AMS查找符合相應(yīng)條件(IntentFilter/Permission等)的BroadcastReceiver,將廣播發(fā)送到BroadcastReceiver(一般情況下是Activity)相應(yīng)的消息循環(huán)隊列中;
  4. 接收者接收廣播, 消息循環(huán)執(zhí)行拿到此廣播,回調(diào)BroadcastReceiver中的onReceive()方法。

Android廣播分為兩個方面:
廣播發(fā)送者和廣播接收者,通常情況下,BroadcastReceiver指的就是廣播接收者(廣播接收器)。

BroadcastReceiver
1.自定義BroadcastReceiver
?自定義廣播接收器需要繼承基類BroadcastReceivre,并實現(xiàn)抽象方法onReceive(context, intent)方法。
? 廣播接收器接收到相應(yīng)廣播后,會自動回到onReceive(..)方法。
?默認情況下,廣播接收器也是運行在UI線程,因此,onReceive方法中不能執(zhí)行太耗時的操作,否則將因此ANR。
?一般情況下,根據(jù)實際業(yè)務(wù)需求,onReceive方法中都會涉及到與其他組件之間的交互,如發(fā)送Notification、啟動service等。

下面代碼片段是一個簡單的廣播接收器的自定義:

public class MyBroadcastReceiver extends BroadcastReceiver {
public static final String TAG = "MyBroadcastReceiver";
public static int m = 1;

  @Override
  public void onReceive(Context context, Intent intent) {
      Log.w(TAG, "intent:" + intent);
      String name = intent.getStringExtra("name");
      Log.w(TAG, "name:" + name + " m=" + m);
     m++;

     Bundle bundle = intent.getExtras();

 }
}

2.注冊廣播接收器:
兩種注冊方式:靜態(tài)注冊、動態(tài)注冊

靜態(tài)注冊
在AndroidManifest文件中進行注冊的廣播接收器,常駐型廣播,也就是說當(dāng)應(yīng)用程序關(guān)閉后,如果有信息廣播來,程序也會被系統(tǒng)調(diào)用自動運行。

直接在AndroidManifest.xml文件中進行注冊。規(guī)則如下:

<receiver android:enabled=["true" | "false"]
    android:exported=["true" | "false"]
    android:icon="drawable resource"
    android:label="string resource"
    android:name="string"
    android:permission="string"
    android:process="string" >
    . . .
</receiver>


其中,需要注意的屬性

屬性 說明
android:exported 此broadcastReceiver能否接收其他App的發(fā)出的廣播,這個屬性默認值有點意思,其默認值是由receiver中有無intent-filter決定的。
如果有intent-filter,默認值為true,否則為false。(同樣的,activity/service中的此屬性默認值一樣遵循此規(guī)則)同時,需要注意的是,這個值的設(shè)定是以application或者application user id為界的,而非進程為界(一個應(yīng)用中可能含有多個進程);
android:name 此broadcastReceiver類名;
android:permission 如果設(shè)置,具有相應(yīng)權(quán)限的廣播發(fā)送方發(fā)送的廣播才能被此broadcastReceiver所接收;
android:process broadcastReceiver運行所處的進程。默認為app的進程。可以指定獨立的進程(Android四大基本組件都可以通過此屬性指定自己的獨立進程)

常見的注冊形式:

<receiver android:name=".MyBroadcastReceiver" >
//intent-filter用于指定此廣播接收器將用于接收特定的廣播類型
    <intent-filter>
//用于接收網(wǎng)絡(luò)狀態(tài)改變時系統(tǒng)自身所發(fā)出的廣播
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
//用于接收開機啟動時系統(tǒng)自身所發(fā)出的廣播,當(dāng)此App首次啟動時,系統(tǒng)會自動實例化MyBroadcastReceiver,并注冊到系統(tǒng)中。
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

<receiver
       android:name=".BootCompleteReceiver"
       android:enabled="true"
       android:exported="true">
       <intent-filter>
           <action android:name="android.intent.action.BOOT_COMPLETED"/>
       </intent-filter>
</receiver>

之前常說:靜態(tài)注冊的廣播接收器即使app已經(jīng)退出,主要有相應(yīng)的廣播發(fā)出,依然可以接收到,但此種描述自Android 3.1開始有可能不再成立,具體分析詳見本文后面部分。

動態(tài)注冊
通過Context的registerBroadcastReceiver方法進行注冊的廣播接收器。動態(tài)注冊廣播不是常駐型廣播,也就是說廣播跟隨程序的生命周期。需要手動注銷(調(diào)用unregisterBroadcastReceiver方法)。

動態(tài)注冊時,不需要在AndroidManifest中注冊<receiver/>組件。直接在代碼中通過調(diào)用Context的registerReceiver函數(shù),可以在程序中動態(tài)注冊BroadcastReceiver。
registerReceiver的定義形式如下:

registerReceiver(BroadcastReceiver receiver, IntentFilter filter)
//或
registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)

典型的寫法示例如下:

public class MainActivity extends Activity {
public static final String BROADCAST_ACTION = "com.example.corn";
private BroadcastReceiver mBroadcastReceiver;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

     mBroadcastReceiver = new MyBroadcastReceiver();
     IntentFilter intentFilter = new IntentFilter();
     intentFilter.addAction(BROADCAST_ACTION);
     registerReceiver(mBroadcastReceiver, intentFilter);
 }

 @Override
 protected void onDestroy() {
     super.onDestroy();
     unregisterReceiver(mBroadcastReceiver);
 }
}

注:Android中所有與觀察者模式有關(guān)的設(shè)計中,一旦涉及到register,必定在相應(yīng)的時機需要unregister。因此,上例在onDestroy()回到中需要

unregisterReceiver(mBroadcastReceiver)。

當(dāng)此Activity實例化時,會動態(tài)將MyBroadcastReceiver注冊到系統(tǒng)中。當(dāng)此Activity銷毀時,動態(tài)注冊的MyBroadcastReceiver將不再接收到相應(yīng)的廣播。

3.發(fā)送廣播:
經(jīng)常說”發(fā)送廣播“和”接收“,表面上看廣播作為Android廣播機制中的實體,實際上這一實體本身是并不是以所謂的”廣播“對象存在的,而是以”意圖“(Intent)去表示。定義廣播的定義過程,實際就是相應(yīng)廣播”意圖“的定義過程,然后通過廣播發(fā)送者將此”意圖“發(fā)送出去。被相應(yīng)的BroadcastReceiver接收后將會回調(diào)onReceive()函數(shù)。

下段代碼片段顯示的是一個普通廣播的定義過程,并發(fā)送出去。其中setAction(..)對應(yīng)于BroadcastReceiver中的intentFilter中的action。

Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
intent.putExtra("name", "qqyumidi");
sendBroadcast(intent);//發(fā)送普通廣播

sendOrderedBroadcast(intent,null);//發(fā)送有序廣播

4.廣播接收者的回調(diào)
不同注冊方式的廣播接收器回調(diào)onReceive(context, intent)中的context具體類型:

  1. 對于靜態(tài)注冊的ContextReceiver,回調(diào)onReceive(context, intent)中的context具體指的是ReceiverRegistertedContext;
  1. 對于全局廣播的動態(tài)注冊的ContextReceiver,回調(diào)onReceive(context, intent)中的context具體指的是Activity Context;
  2. 對于通過LocalBroadcastManager動態(tài)注冊的ContextReceiver,回調(diào)onReceive(context, intent)中的context具體指的是Application Context。

注:對于LocalBroadcastManager方式發(fā)送的應(yīng)用內(nèi)廣播,只能通過LocalBroadcastManager動態(tài)注冊的ContextReceiver才有可能接收到(靜態(tài)注冊或其他方式動態(tài)注冊的ContextReceiver是接收不到的)。

@Override
  public void onReceive(Context context, Intent intent) {
      Toast.makeText(context,"received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show();
      Toast.makeText(context,intent.getStringExtra("data"),Toast.LENGTH_SHORT).show();//獲取intent里的數(shù)據(jù)
  }

有序廣播的接收可以設(shè)置優(yōu)先級(-1000~1000)

<receiver android:name=".MyBroadcastReceiver" 
android:enabled="true" 
android:exported="true">
   <intent-filter android:priority="100">//優(yōu)先級低,后收到 
        <action android:name="com.foxnickel.broadcasttest.MY_BROADCAST"/> 
  </intent-filter> 
</receiver> 

<receiver android:name=".AnotherReceiver" 
        android:enabled="true" 
        android:exported="true"> 
  <intent-filter android:priority="200">//優(yōu)先級高,先收到
       <action android:name="com.foxnickel.broadcasttest.MY_BROADCAST"/>   
   </intent-filter>
 </receiver>

優(yōu)先級高的接收器可以截斷廣播(abortBroadcast()方法),也可以給后面的廣播傳遞數(shù)據(jù)(通過Bundle)[參考android-BroadcastReceiver 發(fā)送有序廣播]

5.不同Android API版本中廣播機制相關(guān)API重要變遷

  1. Android5.0/API level 21開始粘滯廣播和有序粘滯廣播過期,以后不再建議使用;
  2. ”靜態(tài)注冊的廣播接收器即使app已經(jīng)退出,主要有相應(yīng)的廣播發(fā)出,依然可以接收到,但此種描述自Android 3.1開始有可能不再成立“

Android 3.1開始系統(tǒng)在Intent與廣播相關(guān)的flag增加了參數(shù),分別是

FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES。
FLAG_INCLUDE_STOPPED_PACKAGES:包含已經(jīng)停止的包(停止:即包所在的進程已經(jīng)退出)
FLAG_EXCLUDE_STOPPED_PACKAGES:不包含已經(jīng)停止的包

主要原因如下:
自Android3.1開始,系統(tǒng)本身則增加了對所有app當(dāng)前是否處于運行狀態(tài)的跟蹤。在發(fā)送廣播時,不管是什么廣播類型,系統(tǒng)默認直接增加了值為FLAG_EXCLUDE_STOPPED_PACKAGES的flag,導(dǎo)致即使是靜態(tài)注冊的廣播接收器,對于其所在進程已經(jīng)退出的app,同樣無法接收到廣播。
詳情參見Android官方文檔

由此,對于系統(tǒng)廣播,由于是系統(tǒng)內(nèi)部直接發(fā)出,無法更改此intent flag值,因此,3.1開始對于靜態(tài)注冊的接收系統(tǒng)廣播的BroadcastReceiver,如果App進程已經(jīng)退出,將不能接收到廣播。

但是對于自定義的廣播,可以通過復(fù)寫此flag為FLAG_INCLUDE_STOPPED_PACKAGES,使得靜態(tài)注冊的BroadcastReceiver,即使所在App進程已經(jīng)退出,也能能接收到廣播,并會啟動應(yīng)用進程,但此時的BroadcastReceiver是重新新建的。

Intent intent = new Intent(); intent.setAction(BROADCAST_ACTION); 
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); 
intent.putExtra("name", "qqyumidi"); sendBroadcast(intent);

注:

  1. 對于動態(tài)注冊類型的BroadcastReceiver,由于此注冊和取消注冊實在其他組件(如Activity)中進行,因此,不受此改變影響。
  2. 在3.1以前,相信不少app可能通過靜態(tài)注冊方式監(jiān)聽各種系統(tǒng)廣播,以此進行一些業(yè)務(wù)上的處理(如即時app已經(jīng)退出,仍然能接收到,可以啟動service等..),3.1后,靜態(tài)注冊接受廣播方式的改變,將直接導(dǎo)致此類方案不再可行。于是,通過將Service與App本身設(shè)置成不同的進程已經(jīng)成為實現(xiàn)此類需求的可行替代方案。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容