Android 廣播

【Android 廣播】

<span id="BroadcastReceiver簡介"/>

BroadcastReceiver簡介

BroadcastReceiver(廣播接收器),是一個全局的監(jiān)聽器悔叽,屬于 Android 四大組件之一
Android 廣播分為兩個角色:廣播發(fā)送者、廣播接收者。

在 Android 中泊碑,Broadcast 是一種廣泛運用的在應(yīng)用程序之間傳輸信息的機制。而 BroadcastReceiver 是對發(fā)送出來的 Broadcast 進行過濾接受并響應(yīng)的一類組件毯欣。

廣播接收者(BroadcastReceiver)用于接收廣播 Intent 的, 廣播 Intent 的發(fā)送是通過調(diào)用 sendBroadcast/sendOrderedBroadcast 來實現(xiàn)的蛾狗。通常一個廣播 Intent 可以被訂閱了此 Intent 的多個廣播接收者所接收。

廣播機制是一個典型的發(fā)布—訂閱模式仪媒,也就是我們所說的觀察者模式。廣播最大的特點就是發(fā)送方并不關(guān)心接收方是否接到數(shù)據(jù),也不關(guān)心接收方是如何處理數(shù)據(jù)的算吩,通過這樣的形式來達到接留凭、收雙方的完全解耦合。

BroadcastReceiver作用

監(jiān)聽 / 接收 應(yīng)用 App 發(fā)出的廣播消息偎巢,并 做出響應(yīng)

廣播應(yīng)用場景

  • Android不同組件間的通信(含 :應(yīng)用內(nèi) / 不同應(yīng)用之間)
  • 多線程通信
  • Android 系統(tǒng)在特定情況下的通信

如:電話呼入時蔼夜、網(wǎng)絡(luò)可用時

實現(xiàn)原理

采用的模型

  • Android中的廣播使用了設(shè)計模式中的觀察者模式:基于消息的發(fā)布 / 訂閱事件模型

因此,Android將廣播的發(fā)送者 和 接收者 解耦压昼,使得系統(tǒng)方便集成求冷,更易擴展

模型講解

  • 模型中有3個角色:
    1. 消息訂閱者(廣播接收者)
    2. 消息發(fā)布者(廣播發(fā)布者)
    3. 消息中心(AMS,即Activity Manager Service
  • 示意圖 & 原理如下

使用流程

  • 使用流程如下:

  • 下面窍霞,我將一步步介紹如何使用BroadcastReceiver
    即上圖中的 開發(fā)者手動完成部分

1匠题、自定義廣播接收器BroadcastReceiver

  • 繼承BroadcastReceivre基類
  • 必須復(fù)寫抽象方法onReceive()方法
  1. 廣播接收器接收到相應(yīng)廣播后,會自動回調(diào) onReceive() 方法
  2. 一般情況下但金,onReceive方法會涉及 與其他組件之間的交互韭山,如發(fā)送Notification、啟動Service等
  3. 默認(rèn)情況下冷溃,廣播接收器運行在 UI 線程钱磅,因此,onReceive()方法不能執(zhí)行耗時操作似枕,否則將導(dǎo)致ANR
  • 代碼范例mBroadcastReceiver.java
// 繼承BroadcastReceivre基類
public class mBroadcastReceiver extends BroadcastReceiver {
  // 復(fù)寫onReceive()方法
  // 接收到廣播后盖淡,則自動調(diào)用該方法
  @Override
  public void onReceive(Context context, Intent intent) {
   //寫入接收廣播后的操作
    }
}

<span id="2、廣播接收器注冊"/>

2凿歼、廣播接收器注冊

注冊的方式分為兩種:靜態(tài)注冊褪迟、動態(tài)注冊

1)靜態(tài)注冊
  • 注冊方式:在AndroidManifest.xml里通過<receive>標(biāo)簽聲明
  • 屬性說明:
<receiver 
    android:enabled=["true" | "false"]
//exported:此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運行所處的進程
//默認(rèn)為app的進程,可以指定獨立的進程
//注:Android四大基本組件都可以通過此屬性指定自己的獨立進程
    android:process="string" >

//用于指定此廣播接收器將接收的廣播類型
//本示例中給出的是用于接收網(wǎng)絡(luò)狀態(tài)改變時發(fā)出的廣播
 <intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>
  • 注冊示例
<receiver 
    //此廣播接收者類是mBroadcastReceiver
    android:name=".mBroadcastReceiver" >
    //用于接收網(wǎng)絡(luò)狀態(tài)改變時發(fā)出的廣播
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>

當(dāng)此 App首次啟動時攀唯,系統(tǒng)會自動實例化mBroadcastReceiver類洁桌,并注冊到系統(tǒng)中。

2)動態(tài)注冊
  • 注冊方式:在代碼中調(diào)用Context.registerReceiver()方法
  • 具體代碼如下:
// 選擇在Activity生命周期方法中的onResume()中注冊
@Override
  protected void onResume(){
      super.onResume();
    // 1. 實例化BroadcastReceiver子類 &  IntentFilter
     mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver();
     IntentFilter intentFilter = new IntentFilter();
    // 2. 設(shè)置接收廣播的類型
    intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
    // 3. 動態(tài)注冊:調(diào)用Context的registerReceiver()方法
     registerReceiver(mBroadcastReceiver, intentFilter);
 }

// 注冊廣播后侯嘀,要在相應(yīng)位置記得銷毀廣播
// 即在onPause() 中unregisterReceiver(mBroadcastReceiver)
// 當(dāng)此Activity實例化時另凌,會動態(tài)將MyBroadcastReceiver注冊到系統(tǒng)中
// 當(dāng)此Activity銷毀時,動態(tài)注冊的MyBroadcastReceiver將不再接收到相應(yīng)的廣播戒幔。
 @Override
 protected void onPause() {
     super.onPause();
      //銷毀在onResume()方法中的廣播
     unregisterReceiver(mBroadcastReceiver);
     }
}
特別注意
  • 動態(tài)廣播最好在Activity 的 onResume()注冊吠谢、onPause()注銷
  • 原因:
    1. 對于動態(tài)廣播诗茎,有注冊就必然得有注銷工坊,否則會導(dǎo)致內(nèi)存泄露
      重復(fù)注冊、重復(fù)注銷也不允許
  1. Activity生命周期如下:


    Activity生命周期

    Activity生命周期的方法是成對出現(xiàn)的:

  • onCreate() & onDestory()
  • onStart() & onStop()
  • onResume() & onPause()

在onResume()注冊、onPause()注銷是因為onPause()在App死亡前一定會被執(zhí)行王污,從而保證廣播在App死亡前一定會被注銷罢吃,從而防止內(nèi)存泄露。

  1. 不在onCreate() & onDestory() 或 onStart() & onStop()注冊昭齐、注銷是因為:當(dāng)系統(tǒng)因為內(nèi)存不足(優(yōu)先級更高的應(yīng)用需要內(nèi)存尿招,請看上圖紅框)要回收Activity占用的資源時,Activity在執(zhí)行完onPause()方法后就會被銷毀阱驾,有些生命周期方法onStop()就谜,onDestory()就不會執(zhí)行。當(dāng)再回到此Activity時里覆,是從onCreate方法開始執(zhí)行丧荐。
  2. 假設(shè)我們將廣播的注銷放在onStop(),onDestory()方法里的話租谈,有可能在Activity被銷毀后還未執(zhí)行onStop()篮奄,onDestory()方法,即廣播仍還未注銷割去,從而導(dǎo)致內(nèi)存泄露窟却。
  3. 但是,onPause()一定會被執(zhí)行呻逆,從而保證了廣播在App死亡前一定會被注銷夸赫,從而防止內(nèi)存泄露。

PS. 服務(wù)Service相反咖城,切勿在 Activity 的 onResume() 和 onPause() 期間綁定和取消綁定服務(wù)茬腿。

兩種注冊方式的區(qū)別

3、發(fā)送廣播

  • 廣播 是 用”意圖(Intent)“標(biāo)識
  • 定義廣播的本質(zhì) = 定義廣播所具備的“意圖(Intent)”
  • 廣播發(fā)送 = 廣播發(fā)送者 將此廣播的“意圖(Intent)”通過sendBroadcast()方法發(fā)送出去

在Android系統(tǒng)中宜雀,根據(jù)廣播的執(zhí)行順序不同切平,可將其分為有序廣播和無序廣播。

無序廣播

1辐董、普通廣播(Normal Broadcast)

普通廣播(Normal Broadcast)即 開發(fā)者自身定義 intent的廣播(最常用)悴品。
普通廣播是完全異步的,通過Context的sendBroadcast()方法來發(fā)送简烘,消息傳遞效率比較高苔严,但所有receivers(接收器)的執(zhí)行順序不確定。
缺點是:接收者不能將處理結(jié)果傳遞給下一個接收者孤澎,并且無法終止廣播Intent的傳播届氢,直到?jīng)]有與之匹配的廣播接收器為止。下面以自定義的普通廣播進行演示

① 自定義廣播接收器

只要繼承BroadcastReceiver并實現(xiàn)onReceive()方法

public class MyBroadcastReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.e("receive","onReceive");
    }
}

② 注冊廣播接收器

BroadcastReceiver是四大組件之一覆旭,所以毫不疑問需要注冊退子,BroadcastReceiver的注冊有兩種方法:

  • 通過manifests配置
  • 通過代碼動態(tài)配置

1岖妄、方法一:通過manifests配置

<receiver android:name=".BroadcastReceiver.MyBroadcastReceiver">
    <intent-filter>
        <action android:name="com.handsome.hensen" />
    </intent-filter>
</receiver>

這里需要加入intent-filteraction中的name屬性,表示我們監(jiān)聽的內(nèi)容寂祥。
當(dāng)有廣播發(fā)送時衣吠,需要判斷該廣播是否和我們監(jiān)聽的內(nèi)容一致,如果一致則接收

若發(fā)送廣播有相應(yīng)權(quán)限壤靶,那么廣播接收者也需要相應(yīng)權(quán)限

2、方法二:通過代碼動態(tài)配置

//創(chuàng)建廣播
MyBroadcastReceiver receiver = new MyBroadcastReceiver();
//注冊廣播
registerReceiver(receiver, new IntentFilter("com.handsome.hensen"));

如果你是使用動態(tài)注冊廣播的則需要在Activity的onDestroy的時候注銷廣播接收器

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

③ 發(fā)送廣播

這里我們以一個按鈕來發(fā)送廣播惊搏,通過sendBroadcast()方法發(fā)送我們的創(chuàng)建的Intent自定義廣播

final Intent intent = new Intent();
//廣播內(nèi)容
intent.setAction("com.handsome.hensen");

bt_send.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
       sendBroadcast(intent);
   }
});

運行代碼
運行程序后贮乳,我們點擊發(fā)送廣播。我們以Log信息來驗證發(fā)出的廣播被我們準(zhǔn)確的接收

11-25 10:27:43.341 5188-5188/com.handsome.boke2 E/receive: onReceive

2恬惯、系統(tǒng)廣播(System Broadcast)

  • Android中內(nèi)置了多個系統(tǒng)廣播:只要涉及到手機的基本操作(如開機向拆、網(wǎng)絡(luò)狀態(tài)變化、拍照等等)酪耳,都會發(fā)出相應(yīng)的廣播
  • 每個廣播都有特定的Intent - Filter(包括具體的action)浓恳,Android系統(tǒng)廣播action如下:
系統(tǒng)操作 action
監(jiān)聽網(wǎng)絡(luò)變化 android.net.conn.CONNECTIVITY_CHANGE
關(guān)閉或打開飛行模式 Intent.ACTION_AIRPLANE_MODE_CHANGED
充電時或電量發(fā)生變化 Intent.ACTION_BATTERY_CHANGED
電池電量低 Intent.ACTION_BATTERY_LOW
電池電量充足(即從電量低變化到飽滿時會發(fā)出廣播 Intent.ACTION_BATTERY_OKAY
系統(tǒng)啟動完成后(僅廣播一次) Intent.ACTION_BOOT_COMPLETED
按下照相時的拍照按鍵(硬件按鍵)時 Intent.ACTION_CAMERA_BUTTON
屏幕鎖屏 Intent.ACTION_CLOSE_SYSTEM_DIALOGS
設(shè)備當(dāng)前設(shè)置被改變時(界面語言、設(shè)備方向等) Intent.ACTION_CONFIGURATION_CHANGED
插入耳機時 Intent.ACTION_HEADSET_PLUG
未正確移除SD卡但已取出來時(正確移除方法:設(shè)置--SD卡和設(shè)備內(nèi)存--卸載SD卡) Intent.ACTION_MEDIA_BAD_REMOVAL
插入外部儲存裝置(如SD卡) Intent.ACTION_MEDIA_CHECKING
成功安裝APK Intent.ACTION_PACKAGE_ADDED
成功刪除APK Intent.ACTION_PACKAGE_REMOVED
重啟設(shè)備 Intent.ACTION_REBOOT
屏幕被關(guān)閉 Intent.ACTION_SCREEN_OFF
屏幕被打開 Intent.ACTION_SCREEN_ON
關(guān)閉系統(tǒng)時 Intent.ACTION_SHUTDOWN
重啟設(shè)備 Intent.ACTION_REBOOT

注:當(dāng)使用系統(tǒng)廣播時碗暗,只需要在注冊廣播接收者時定義相關(guān)的action即可颈将,并不需要手動發(fā)送廣播,當(dāng)系統(tǒng)有相關(guān)操作時會自動進行系統(tǒng)廣播

<receiver android:name=".BroadcastReceiver.MyBroadcastReceiver">
    <intent-filter>
        <!--重啟設(shè)備-->
        <action android:name="android.intent.action.REBOOT" />
    </intent-filter>
</receiver>

3言疗、本地廣播(Local Broadcast)

  • 由于Android中的廣播可以跨App直接通信晴圾,所有應(yīng)用程序都可以接收到(exported對于有intent-filter情況下默認(rèn)值為true)
  • 可能出現(xiàn)的問題:
    • 其他App針對性發(fā)出與當(dāng)前App intent-filter相匹配的廣播,由此導(dǎo)致當(dāng)前App不斷接收廣播并處理噪奄;
    • 其他App注冊與當(dāng)前App一致的intent-filter用于接收廣播死姚,獲取廣播具體信息;即會出現(xiàn)安全性 & 效率性的問題勤篮。
  • 解決方案:使用本地廣播(Local Broadcast)
  1. 本地廣播可理解為一種局部廣播都毒,廣播的發(fā)送者和接收者都同屬于一個App
  2. 相比于全局廣播(普通廣播)碰缔,App應(yīng)用內(nèi)廣播優(yōu)勢體現(xiàn)在:安全性高 & 效率高
  • 具體使用1——將全局廣播設(shè)置成局部廣播

    1. 注冊廣播時將exported屬性設(shè)置為false账劲,使得非本App內(nèi)部發(fā)出的此廣播不被接收;
    2. 在廣播發(fā)送和接收時手负,增設(shè)相應(yīng)權(quán)限permission涤垫,用于權(quán)限驗證;
    3. 發(fā)送廣播時指定該廣播接收器所在的包名竟终,此廣播將只會發(fā)送到此包中的App內(nèi)與之相匹配的有效廣播接收器中蝠猬。
      通過intent.setPackage(packageName) 指定報名
  • 具體使用2 - 使用封裝好的LocalBroadcastManager類使用方式上與全局廣播幾乎相同,只是注冊/取消注冊廣播接收器和發(fā)送廣播時將參數(shù)的context變成了LocalBroadcastManager的單一實例
    注:對于LocalBroadcastManager方式發(fā)送的應(yīng)用內(nèi)廣播统捶,只能通過LocalBroadcastManager動態(tài)注冊榆芦,不能靜態(tài)注冊

注冊Receiver

 //創(chuàng)建廣播
receiver = new MyBroadcastReceiver();
//注冊本地廣播
LocalBroadcastManager.getInstance(ReceiverActivity.this).registerReceiver(receiver,
        new IntentFilter("com.handsome.hensen"));

注銷Receiver

LocalBroadcastManager.getInstance(ReceiverActivity.this).unregisterReceiver(receiver);

發(fā)送異步廣播

final Intent intent = new Intent();
intent.setAction("com.handsome.hensen2");
LocalBroadcastManager.getInstance(ReceiverActivity.this).sendBroadcast(intent);

發(fā)送同步廣播

LocalBroadcastManager.getInstance(ReceiverActivity.this).sendBroadcastSync(intent);

4柄粹、粘性廣播(Sticky Broadcast)

由于在Android5.0 & API 21中已經(jīng)失效,所以不建議使用匆绣。
sticky廣播通過Context.sendStickyBroadcast()函數(shù)來發(fā)送驻右,用此函數(shù)發(fā)送的廣播會一直滯留,當(dāng)有匹配此廣播的廣播接收器被注冊后崎淳,該廣播接收器就會收到此條信息堪夭。使用此函數(shù)需要發(fā)送廣播時,需要獲得BROADCAST_STICKY權(quán)限

<uses-permission android:name="android.permission.BROADCAST_STICKY"/>

sendStickyBroadcast只保留最后一條廣播拣凹,并且一直保留下去森爽,這樣即使已經(jīng)有廣播接收器處理了該廣播,當(dāng)再有匹配的廣播接收器被注冊時嚣镜,此廣播仍會被接收爬迟。如果你只想處理一遍該廣播,可以通過removeStickyBroadcast()函數(shù)來實現(xiàn)菊匿。

有序廣播

5付呕、有序廣播(Ordered Broadcast)

  • 定義發(fā)送出去的廣播被廣播接收者按照先后順序接收

有序是針對廣播接收者而言的

  • 廣播接受者接收廣播的順序規(guī)則(同時面向靜態(tài)和動態(tài)注冊的廣播接受者)
    1. 按照Priority屬性值從大-小排序;
    2. Priority屬性相同者跌捆,動態(tài)注冊的廣播優(yōu)先徽职;
  • 特點
    1. 接收廣播按順序接收
    2. 先接收的廣播接收者可以對廣播進行截斷abortBroadcast()),即后接收的廣播接收者不再接收到此廣播疹蛉;
    3. 先接收的廣播接收者可以對廣播進行修改活箕,再使用setResult()函數(shù)來結(jié)果傳給下一個廣播接收器接收,那么后接收的廣播接收者將通過getResult()函數(shù)來取得上個廣播接收器接收返回的結(jié)果
  • 具體使用有序廣播的使用過程與普通廣播非常類似可款,差異僅在于廣播的發(fā)送方式:通過Context.sendOrderedBroadcast()來發(fā)送
public abstract void sendOrderedBroadcast(@RequiresPermission Intent intent,
        @Nullable String receiverPermission);
/* 第一個參數(shù) Intent 類型:意圖
 * 第二個參數(shù) String 類型 receiverPermission育韩,接收器需要的權(quán)限
 * 第三個參數(shù) BroadcastReceiver 類型,自己定義的接收器作為最終接收器
 * 第四個參數(shù) Handler 類型闺鲸,用于執(zhí)行接收器的回調(diào)筋讨,如果為 null 則在主線程中執(zhí)行
 * 第五個參數(shù) int 類型,結(jié)果代碼的初始碼
 * 第六個參數(shù)初始化參數(shù)
 * 第七個參數(shù) Bundle 類型摸恍,額外的數(shù)據(jù)*/
public abstract void sendOrderedBroadcast(@RequiresPermission @NonNull Intent intent,
        @Nullable String receiverPermission, @Nullable BroadcastReceiver resultReceiver,
        @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
        @Nullable Bundle initialExtras);
① 自定義廣播接收器

我們創(chuàng)建一個類悉罕,存放三個有優(yōu)先級的廣播接收者,并在最高級廣播中傳遞結(jié)果到下一個廣播

public class PriorityBroadcastReceiver {
    public static class HighPriority extends BroadcastReceiver {
        //高級廣播接收者
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.e("receive", "High");
            //setResult傳遞結(jié)果到下一個廣播接收器
            int code = 0;
            String data = "hello";
            Bundle bundle = null;
            setResult(code, data, bundle);
        }
    }

    public static class MidPriority extends BroadcastReceiver {
        //中級廣播接收者
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.e("receive", "Mid");
            //獲取上一個廣播接收器結(jié)果
            int code = getResultCode();
            String data = getResultData();
            Log.e("receive", "獲取到上一個廣播接收器結(jié)果:" + "code=" + code + "data=" + data);
        }
    }

    public static class LowPriority extends BroadcastReceiver {
        //低級廣播接收者
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.e("receive", "Low");
        }
    }
}

注意:內(nèi)部類的BroadcastReceiver必須由public static修飾立镶,否則會報錯

② 注冊廣播接收器

這里的注冊方式和普通廣播是一樣的壁袄,這里的區(qū)別在于priority屬性,確定了他們之間的優(yōu)先級

<receiver android:name=".BroadcastReceiver.PriorityBroadcastReceiver$HighPriority">
    <intent-filter android:priority="3000">
        <action android:name="com.handsome.hensen2" />
    </intent-filter>
</receiver>
<receiver android:name=".BroadcastReceiver.PriorityBroadcastReceiver$MidPriority">
    <intent-filter android:priority="2000">
        <action android:name="com.handsome.hensen2" />
    </intent-filter>
</receiver>
<receiver android:name=".BroadcastReceiver.PriorityBroadcastReceiver$LowPriority">
    <intent-filter android:priority="1000">
        <action android:name="com.handsome.hensen2" />
    </intent-filter>
</receiver>

注意:BroadcastReceiver類名與內(nèi)部類的名字之間用$符號隔開媚媒,否則會報錯

③ 發(fā)送廣播

和之前的不一樣的地方嗜逻,這里是使用sendOrderedBroadcast()發(fā)送有序廣播

final Intent intent = new Intent();
intent.setAction("com.handsome.hensen2");
bt_send.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        sendOrderedBroadcast(intent,null);
    }
});

注意:這里需要發(fā)送的是有序廣播,否則在接收者中通過setResult()和getResult()方法會報錯缭召,因為只有有序廣播才能傳遞結(jié)果

運行代碼
運行程序后栈顷,我們點擊發(fā)送廣播逆日。我們以Log信息來驗證發(fā)出的廣播被我們準(zhǔn)確的接收,數(shù)據(jù)被我們準(zhǔn)確的傳遞

11-25 11:50:07.207 12777-12777/com.handsome.boke2 E/receive: High
11-25 11:50:07.217 12777-12777/com.handsome.boke2 E/receive: Mid
11-25 11:50:07.218 12777-12777/com.handsome.boke2 E/receive: 獲取到上一個廣播接收器結(jié)果:code=0data=hello
11-25 11:50:07.233 12777-12777/com.handsome.boke2 E/receive: Low

攔截廣播

上面我們提到過有序廣播中可以攔截廣播萄凤,那么我們在上面程序的基礎(chǔ)上修改代碼室抽,在HighPriority接收器中加上攔截廣播

① 創(chuàng)建廣播
通過在BroadcastReceiver中,執(zhí)行abortBroadcast()方法靡努,廣播就不會繼續(xù)往下傳遞了

public static class HighPriority extends BroadcastReceiver {
        //高級廣播接收者
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.e("receive", "High");
            //攔截廣播
            abortBroadcast();
            //傳遞結(jié)果到下一個廣播接收器
            int code = 0;
            String data = "hello";
            Bundle bundle = null;
            setResult(code, data, bundle);
        }
    }

運行代碼
運行程序后坪圾,我們點擊發(fā)送廣播。我們以Log信息來驗證我們攔截了廣播

11-25 12:12:36.405 30867-30867/com.handsome.boke2 E/receive: High

可以看到惑朦,后面的Mid和Low廣播都沒有Log信息神年,說明我們攔截成功了

最終廣播接收者

現(xiàn)在有這樣的一個應(yīng)用場景,按照上面的程序走行嗤,只能在第一個廣播中被攔截住了,后面的廣播則不執(zhí)行垛耳。如果這個時候我們需要一個不管有沒有被攔截都必須執(zhí)行的廣播栅屏,我們稱為最終廣播接收者,那應(yīng)該怎么辦堂鲜。同樣的栈雳,發(fā)送有序廣播也考慮到這一點,通過以下代碼來發(fā)送廣播缔莲,并指定我們不管有沒有被攔截都必須執(zhí)行的最終廣播接收者

bt_send.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        sendOrderedBroadcast(intent, null, new PriorityBroadcastReceiver.LowPriority(),
                new Handler(), 0, null, null);
    }
});

運行代碼哥纫,我們查看Log信息

11-25 12:22:33.466 4764-4764/com.handsome.boke2 E/receive: High
11-25 12:22:33.468 4764-4764/com.handsome.boke2 E/receive: Low

可以發(fā)現(xiàn),之前只是有High的Log信息痴奏,因為是被攔截了蛀骇,而Log信息多了一條Low,說明我們攔截后读拆,還要執(zhí)行終結(jié)廣播

特別注意

對于不同注冊方式的廣播接收器回調(diào)OnReceive(Context context擅憔,Intent intent)中的context返回值不一樣的:

  • 對于靜態(tài)注冊(全局+本地廣播),回調(diào)onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext檐晕;
  • 對于全局廣播的動態(tài)注冊暑诸,回調(diào)onReceive(context, intent)中的context返回值是:Activity Context
  • 對于本地廣播的動態(tài)注冊(LocalBroadcastManager方式)辟灰,回調(diào)onReceive(context, intent)中的context返回值是:Application Context个榕。
  • 對于本地廣播的動態(tài)注冊(非LocalBroadcastManager方式),回調(diào)onReceive(context, intent)中的context返回值是:Activity Context芥喇;

BroadCastReceiver生命周期

BroadcastReceiver對象的生命周期

靜態(tài)注冊的BroadcastReceiver

在AndroidManifest.xml中注冊的BroadcastReceiver, 每次收到一個Intent, 也就是onReceive被回調(diào)的時候, 這個BroadcastReceiver都是新創(chuàng)建出來的, 官方文檔中寫:

A BroadcastReceiver object is only valid for the duration of the call to onReceive(Context, Intent). Once your code returns from this function, the system considers the object to be finished and no longer active.

也就是說, 出了onReceive, 這個BroadcastReceiver對象的生命周期就已經(jīng)到頭了, 這也是為什么我們不能在onReceive中進行一些異步操作的原因, 有可能異步操作還沒完成, BroadcastReceiver所在的進程就被kill了西采。
表現(xiàn)出來的結(jié)果就是, BroadcastReceiver中的成員變量無法保存它們的值, 因為它們每次都是重新創(chuàng)建的, 之前的已經(jīng)隨著BroadcastReceiver對象被銷毀了。

動態(tài)注冊的BroadcastReceiver

但是有一種情況, BroadcastReceiver的成員變量是可用的, 那就是動態(tài)注冊的BroadcastReceiver. 動態(tài)注冊的BroadcastReceiver對象的生命其實是受我們控制的.
實際測試, 使用Context.registerReceiver和Context.unregisterReceiver注冊的BroadcastReceiver每次收到廣播都是使用我們注冊時傳入的對象處理的, 這也是符合我們代碼上的邏輯的. 當(dāng)然, 此時靜態(tài)變量也是可用的.

BroadcastReceiver所在進程的生命周期

對于那種在AndroidManifest.xml中靜態(tài)注冊的BroadcastReceiver, 成員變量是沒法用了, 有人說, 是不是用static變量就可以了呢, 某些情況下是可以的, 什么情況呢, 就是進程不會被kill的情況.
官方文檔里面有一段

Once you return from onReceive(), the BroadcastReceiver is no longer active, and its hosting process is only as important as any other application components that are running in it.

在AndroidManifest.xml中靜態(tài)注冊的BroadcastReceiver的onReceive被回調(diào)時, 有可能這個進程只承載了這個BroadcastReceiver, 比如我們的應(yīng)用沒有運行的情況, 等onReceive返回, 這個時候我們的進程的會被視為空進程(empty process), 此時Android有極大可能回收掉空進程, 這種情況下靜態(tài)成員變量也無法保存值了.

如果我們的程序正在運行, 則Android不一定會回收掉我們的進程, 因為此時我們的進程級別會以進程中承載的級別最高的組件為準(zhǔn). 在我的實際項目中, 我是在播放歌曲的情況下監(jiān)聽線控耳機的按下Intent, 這個時候我的應(yīng)用是有一個前臺服務(wù)播放歌曲的, 此時我的進程至少是可見進程(visible process)級別, 幾乎不會被Android kill掉, 所以我可以在BroadcastReceiver中使用一個靜態(tài)成員變量記錄上一次點擊的時間.

<span id="BroadCastReceiver生命周期總結(jié)"/>

BroadCastReceiver生命周期總結(jié)

  1. 廣播接收者的生命周期非常短暫的乃坤,在接收到廣播的時候創(chuàng)建苛让,onReceive()方法結(jié)束之后銷毀沟蔑;
  2. 廣播接收者中不要做一些耗時的工作,否則會彈出 Application No Response 錯誤對話框狱杰;
  3. 最好也不要在廣播接收者中創(chuàng)建子線程做耗時的工作瘦材,因為廣播接收者被銷毀后進程就成為了空進程,很容易被系統(tǒng)殺掉;
  4. 耗時的較長的工作可以通過Intent啟動Service來完成,但不能綁定Service关噪。

Android 廣播面試題

請描述一下 BroadcastReceiver

BroadCastReceiver 是 Android 四大組件之一男图,主要用于接收系統(tǒng)或者 app 發(fā)送的廣播事件
內(nèi)部通信實現(xiàn)機制:通過 Android 系統(tǒng)的 Binder 機制實現(xiàn)通信培廓。
廣播分兩種:有序廣播和無序廣播。

  • 無序廣播:完全異步,邏輯上可以被任何廣播接收者接收到憔儿。優(yōu)點是效率較高。缺點是一個接收者不能將處理結(jié)果傳遞給下一個接收者放可,并無法終止廣播 intent 的傳播谒臼。
  • 有序廣播:按照被接收者的優(yōu)先級順序,在被接收者中依次傳播耀里。比如有三個廣播接收者 A蜈缤,B,C冯挎,優(yōu)先級是 A >B > C底哥。那這個消息先傳給 A,再傳給 B房官,最后傳給 C趾徽。每個接收者有權(quán)終止廣播,比如 B 終止廣播翰守,C 就無法接收到附较。 此外 A 接收到廣播后可以對結(jié)果對象進行操作,當(dāng)廣播傳給 B 時潦俺,B 可以從結(jié)果對象中取得 A 存入的數(shù)據(jù)拒课。
    在 通 過 Context.sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras)時我們可以指定 resultReceiver 廣播接收者,這個接收者我們可以認(rèn)為是最終接收者事示,通常情況下如果比他優(yōu)先級更高的接收者如果沒有終止廣播早像,那么他的 onReceive 會被執(zhí)行兩次,第一次是正常的按照優(yōu)先級順序執(zhí)行肖爵,第二次是作為最終接收者接收卢鹦。如果比他優(yōu)先級高的接收者終止了廣播,那么他依然能接收到廣播。

在我們的項目中經(jīng)常使用廣播接收者接收系統(tǒng)通知冀自,比如開機啟動揉稚、sd 掛載、低電量熬粗、外播電話搀玖、鎖屏等。

詳細(xì)見上文#BroadcastReceiver簡介

如何注冊和使用 BroadcastReceiver

在清單文件中注冊廣播接收者稱為靜態(tài)注冊驻呐,在代碼中注冊稱為動態(tài)注冊灌诅。
靜態(tài)注冊的廣播接收者只要 app 在系統(tǒng)中運行則一直可以接收到廣播消息;
動態(tài)注冊的廣播接收者當(dāng)注冊的 Activity 或者 Service 銷毀了那么就接收不到 廣播了含末。
靜態(tài)注冊:在清單文件中進行如下配置

<receiver android:name=".BroadcastReceiver1" >
    <intent-filter>
        <action android:name="android.intent.action.CALL" >
        </action>
    </intent-filter>
</receiver>

動態(tài)注冊:在代碼中進行如下注冊

receiver = new BroadcastReceiver();
IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(CALL_ACTION); context.registerReceiver(receiver, intentFilter);

詳細(xì)見上文#2猜拾、廣播接收器注冊

BroadCastReceiver生命周期

見上文#BroadCastReceiver生命周期總結(jié)

如何讓自己的廣播只讓指定的app接收

(2015.09.02)

1、自己的應(yīng)用(假設(shè)名稱為應(yīng)用 A)在發(fā)送廣播的時候給自己發(fā)送的廣播添加自定義權(quán)限佣盒,假設(shè)權(quán)限名為:

com.itheima.android.permission挎袜,然后需要在應(yīng)用 A 的 AndroidManifest.xml 中聲明如下權(quán)限:

<permission android:name="com.itheima.android.permission"
    android:protectionLevel="normal">
</permission>
<uses-permission android:name="com.itheima.android.permission"/>

2、其他應(yīng)用(假設(shè)名稱誒應(yīng)用 B)如果想接收該廣播肥惭,那么就必須知道應(yīng)用 A 廣播使用的權(quán)限宋雏。然后在應(yīng)用 B 的清單文件中如下配置:

<uses-permission android:name="com.itheima.android.permission"/>

或者在應(yīng)用 AndroidManifest.xml 中的<receiver>標(biāo)簽中進行如下配置:

<receiver android:name="com.itheima.android.broadcastReceiver.MyReceiver"
android:permission="com.itheima.android.permission">
    <intent-filter >
        <action android:name="com.itheima.mybroadcast"></action>
    </intent-filter>
</receiver>

每個權(quán)限通過 protectionLevel 來標(biāo)識保護級別:
normal : 低 風(fēng) 險 權(quán) 限 , 只 要 申 請 了 就 可 以 使 用 ( 在 AndroidManifest.xml 中 添 加<uses-permission>標(biāo)簽)务豺,安裝時不需要用戶確認(rèn);
dangerous:高風(fēng)險權(quán)限嗦明,安裝時需要用戶的確認(rèn)才可使用笼沥;
signature:只有當(dāng)申請權(quán)限的應(yīng)用程序的數(shù)字簽名與聲明此權(quán)限的應(yīng)用程序的數(shù)字簽名相同時(如果是申請系統(tǒng)權(quán)限,則需要與系統(tǒng)簽名相同)娶牌,才能將權(quán)限授給它奔浅;
signatureOrSystem:簽名相同,或者申請權(quán)限的應(yīng)用為系統(tǒng)應(yīng)用(在 system image 中)诗良。
上述四類權(quán)限級別同樣可用于自定義權(quán)限中汹桦。如果開發(fā)者需要對自己的應(yīng)用程序(或部分應(yīng)用)進行 訪 問 控 制 , 則 可 以 通 過 在 AndroidManifest.xml 中 添 加 <permission> 標(biāo) 簽 鉴裹, 將 其 屬 性 中 的protectionLevel 設(shè)置為上述四類級別中的某一種來實現(xiàn)舞骆。

什么是最終廣播接收者?

最終廣播是我們自己應(yīng)用發(fā)送有序廣播時通過 ContextWrapper.sendOrderedBroadcast()方法指定的當(dāng)前應(yīng)用 下的廣播径荔,該廣播可能會被執(zhí)行兩次督禽,第一次是作為普通廣播按照優(yōu)先級接收廣播,第二次是作為 final receiver 必須 接收一次总处。

廣播的優(yōu)先級對無序廣播生效嗎狈惫?

生效的。廣播的優(yōu)先級推薦的范圍是:[-1000,+1000]鹦马,但是如果設(shè)置的優(yōu)先級值超過這個范圍也是可以的胧谈。

動態(tài)注冊的廣播優(yōu)先級誰高忆肾?

誰先注冊誰優(yōu)先級高。

如何判斷當(dāng)前BroadcastReceiver接收到的是有序廣播還是無序廣播 菱肖?

(2015-10-16)
在 BroadcastReceiver 類中 onReceive()方法中客冈,可以調(diào)用 boolean b = isOrderedBroadcast();該方法是BroadcastReceiver 類中提供的方法,用于告訴我們當(dāng)前的接收到的廣播是否為有序廣播蔑滓。

Android 引入廣播機制的用意

(2017-2-23)

  1. 從 MVC 的角度考慮(應(yīng)用程序內(nèi)) 其實回答這個問題的時候還可以這樣問郊酒,android 為什么要有那 4 大組件, 現(xiàn)在的移動開發(fā)模型基本上也是照搬的 web 那一套 MVC 架構(gòu)燎窘,只不過是改了點嫁妝而已褐健。android 的四大組件本質(zhì) 上就是為了實現(xiàn)移動或者說嵌入式設(shè)備上的 MVC 架構(gòu),它們之間有時候是一種相互依存的關(guān)系蚜迅,有時候又是一種補 充關(guān)系俊抵,引入廣播機制可以方便幾大組件的信息和數(shù)據(jù)交互。
  2. 程序間互通消息(例如在自己的應(yīng)用程序內(nèi)監(jiān)聽系統(tǒng)來電)
  3. 效率上(參考 UDP 的廣播協(xié)議在局域網(wǎng)的方便性)
  4. 設(shè)計模式上(反轉(zhuǎn)控制的一種應(yīng)用刹帕,類似監(jiān)聽者模式)

引用:
Android四大組件:BroadcastReceiver史上最全面解析
Android四大組件——BroadcastReceiver普通廣播、有序廣播钱贯、攔截廣播尉共、本地廣播爸邢、Sticky廣播、系統(tǒng)廣播
BroadcastReceiver生命周期探討
Android開發(fā)之BroadcastReceiver詳解
BroadcastReceiver生命周期探討

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末唾戚,一起剝皮案震驚了整個濱河市叹坦,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌莹捡,老刑警劉巖篮赢,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡纱耻,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赠群,你說我怎么就攤上這事查描≡扔停” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長纷跛。 經(jīng)常有香客問我,道長叮阅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮眯分,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘飘诗。我一直安慰自己,他們只是感情好溉潭,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布别惦。 她就那樣靜靜地躺著,像睡著了一般扰付。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盐固,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天曙咽,我揣著相機與錄音例朱,去河邊找鬼箫荡。 笑死,一個胖子當(dāng)著我的面吹牛婉弹,可吹牛的內(nèi)容都是我干的氯哮。 我是一名探鬼主播姆打,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼玛追,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了陆馁?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎杨箭,沒想到半個月后辽狈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刮萌,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了牺陶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掰伸。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡多搀,死狀恐怖廊谓,靈堂內(nèi)的尸體忽然破棺而出蒸痹,到底是詐尸還是另有隱情,我是刑警寧澤榛鼎,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布苏揣,位于F島的核電站框沟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏梅垄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吞加。 院中可真熱鬧,春花似錦、人聲如沸码党。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽销钝。三九已至蒸健,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背饺著。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留梢睛,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親诫硕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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