Android 廣播

【Android 廣播】

<span id="BroadcastReceiver簡介"/>

BroadcastReceiver簡介

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

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

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

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

BroadcastReceiver作用

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

廣播應用場景

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

如:電話呼入時、網絡可用時

實現(xiàn)原理

采用的模型

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

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

模型講解

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

使用流程

  • 使用流程如下:

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

1、自定義廣播接收器BroadcastReceiver

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

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

2、廣播接收器注冊

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

1)靜態(tài)注冊
  • 注冊方式:在AndroidManifest.xml里通過<receive>標簽聲明
  • 屬性說明:
<receiver 
    android:enabled=["true" | "false"]
//exported:此broadcastReceiver能否接收其他App的發(fā)出的廣播
//默認值是由receiver中有無intent-filter決定的:如果有intent-filter,默認值為true,否則為false
    android:exported=["true" | "false"]
    android:icon="drawable resource"
    android:label="string resource"
//繼承BroadcastReceiver子類的類名
    android:name=".mBroadcastReceiver"
//具有相應權限的廣播發(fā)送者發(fā)送的廣播才能被此BroadcastReceiver所接收;
    android:permission="string"
//BroadcastReceiver運行所處的進程
//默認為app的進程,可以指定獨立的進程
//注:Android四大基本組件都可以通過此屬性指定自己的獨立進程
    android:process="string" >

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

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

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

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


    Activity生命周期

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

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

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

  1. 不在onCreate() & onDestory() 或 onStart() & onStop()注冊、注銷是因為:當系統(tǒng)因為內存不足(優(yōu)先級更高的應用需要內存,請看上圖紅框)要回收Activity占用的資源時,Activity在執(zhí)行完onPause()方法后就會被銷毀,有些生命周期方法onStop(),onDestory()就不會執(zhí)行。當再回到此Activity時,是從onCreate方法開始執(zhí)行。
  2. 假設我們將廣播的注銷放在onStop(),onDestory()方法里的話,有可能在Activity被銷毀后還未執(zhí)行onStop(),onDestory()方法,即廣播仍還未注銷,從而導致內存泄露。
  3. 但是,onPause()一定會被執(zhí)行,從而保證了廣播在App死亡前一定會被注銷,從而防止內存泄露。

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

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

3、發(fā)送廣播

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

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

無序廣播

1、普通廣播(Normal Broadcast)

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

① 自定義廣播接收器

只要繼承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)聽的內容。
當有廣播發(fā)送時,需要判斷該廣播是否和我們監(jiān)聽的內容一致,如果一致則接收

若發(fā)送廣播有相應權限,那么廣播接收者也需要相應權限

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();
//廣播內容
intent.setAction("com.handsome.hensen");

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

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

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

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

  • Android中內置了多個系統(tǒng)廣播:只要涉及到手機的基本操作(如開機、網絡狀態(tài)變化、拍照等等),都會發(fā)出相應的廣播
  • 每個廣播都有特定的Intent - Filter(包括具體的action),Android系統(tǒng)廣播action如下:
系統(tǒng)操作 action
監(jiān)聽網絡變化 android.net.conn.CONNECTIVITY_CHANGE
關閉或打開飛行模式 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
設備當前設置被改變時(界面語言、設備方向等) Intent.ACTION_CONFIGURATION_CHANGED
插入耳機時 Intent.ACTION_HEADSET_PLUG
未正確移除SD卡但已取出來時(正確移除方法:設置--SD卡和設備內存--卸載SD卡) Intent.ACTION_MEDIA_BAD_REMOVAL
插入外部儲存裝置(如SD卡) Intent.ACTION_MEDIA_CHECKING
成功安裝APK Intent.ACTION_PACKAGE_ADDED
成功刪除APK Intent.ACTION_PACKAGE_REMOVED
重啟設備 Intent.ACTION_REBOOT
屏幕被關閉 Intent.ACTION_SCREEN_OFF
屏幕被打開 Intent.ACTION_SCREEN_ON
關閉系統(tǒng)時 Intent.ACTION_SHUTDOWN
重啟設備 Intent.ACTION_REBOOT

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

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

3、本地廣播(Local Broadcast)

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

    1. 注冊廣播時將exported屬性設置為false,使得非本App內部發(fā)出的此廣播不被接收;
    2. 在廣播發(fā)送和接收時,增設相應權限permission,用于權限驗證;
    3. 發(fā)送廣播時指定該廣播接收器所在的包名,此廣播將只會發(fā)送到此包中的App內與之相匹配的有效廣播接收器中。
      通過intent.setPackage(packageName) 指定報名
  • 具體使用2 - 使用封裝好的LocalBroadcastManager類使用方式上與全局廣播幾乎相同,只是注冊/取消注冊廣播接收器和發(fā)送廣播時將參數(shù)的context變成了LocalBroadcastManager的單一實例
    注:對于LocalBroadcastManager方式發(fā)送的應用內廣播,只能通過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中已經失效,所以不建議使用。
sticky廣播通過Context.sendStickyBroadcast()函數(shù)來發(fā)送,用此函數(shù)發(fā)送的廣播會一直滯留,當有匹配此廣播的廣播接收器被注冊后,該廣播接收器就會收到此條信息。使用此函數(shù)需要發(fā)送廣播時,需要獲得BROADCAST_STICKY權限

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

sendStickyBroadcast只保留最后一條廣播,并且一直保留下去,這樣即使已經有廣播接收器處理了該廣播,當再有匹配的廣播接收器被注冊時,此廣播仍會被接收。如果你只想處理一遍該廣播,可以通過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ù)來結果傳給下一個廣播接收器接收,那么后接收的廣播接收者將通過getResult()函數(shù)來取得上個廣播接收器接收返回的結果
  • 具體使用有序廣播的使用過程與普通廣播非常類似,差異僅在于廣播的發(fā)送方式:通過Context.sendOrderedBroadcast()來發(fā)送
public abstract void sendOrderedBroadcast(@RequiresPermission Intent intent,
        @Nullable String receiverPermission);
/* 第一個參數(shù) Intent 類型:意圖
 * 第二個參數(shù) String 類型 receiverPermission,接收器需要的權限
 * 第三個參數(shù) BroadcastReceiver 類型,自己定義的接收器作為最終接收器
 * 第四個參數(shù) Handler 類型,用于執(zhí)行接收器的回調,如果為 null 則在主線程中執(zhí)行
 * 第五個參數(shù) int 類型,結果代碼的初始碼
 * 第六個參數(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)先級的廣播接收者,并在最高級廣播中傳遞結果到下一個廣播

public class PriorityBroadcastReceiver {
    public static class HighPriority extends BroadcastReceiver {
        //高級廣播接收者
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.e("receive", "High");
            //setResult傳遞結果到下一個廣播接收器
            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");
            //獲取上一個廣播接收器結果
            int code = getResultCode();
            String data = getResultData();
            Log.e("receive", "獲取到上一個廣播接收器結果:" + "code=" + code + "data=" + data);
        }
    }

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

注意:內部類的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類名與內部類的名字之間用$符號隔開,否則會報錯

③ 發(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()方法會報錯,因為只有有序廣播才能傳遞結果

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

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: 獲取到上一個廣播接收器結果:code=0data=hello
11-25 11:50:07.233 12777-12777/com.handsome.boke2 E/receive: Low

攔截廣播

上面我們提到過有序廣播中可以攔截廣播,那么我們在上面程序的基礎上修改代碼,在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();
            //傳遞結果到下一個廣播接收器
            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)在有這樣的一個應用場景,按照上面的程序走,只能在第一個廣播中被攔截住了,后面的廣播則不執(zhí)行。如果這個時候我們需要一個不管有沒有被攔截都必須執(zhí)行的廣播,我們稱為最終廣播接收者,那應該怎么辦。同樣的,發(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í)行終結廣播

特別注意

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

  • 對于靜態(tài)注冊(全局+本地廣播),回調onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext
  • 對于全局廣播的動態(tài)注冊,回調onReceive(context, intent)中的context返回值是:Activity Context
  • 對于本地廣播的動態(tài)注冊(LocalBroadcastManager方式),回調onReceive(context, intent)中的context返回值是:Application Context
  • 對于本地廣播的動態(tài)注冊(非LocalBroadcastManager方式),回調onReceive(context, intent)中的context返回值是:Activity Context

BroadCastReceiver生命周期

BroadcastReceiver對象的生命周期

靜態(tài)注冊的BroadcastReceiver

在AndroidManifest.xml中注冊的BroadcastReceiver, 每次收到一個Intent, 也就是onReceive被回調的時候, 這個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對象的生命周期就已經到頭了, 這也是為什么我們不能在onReceive中進行一些異步操作的原因, 有可能異步操作還沒完成, BroadcastReceiver所在的進程就被kill了。
表現(xiàn)出來的結果就是, BroadcastReceiver中的成員變量無法保存它們的值, 因為它們每次都是重新創(chuàng)建的, 之前的已經隨著BroadcastReceiver對象被銷毀了。

動態(tài)注冊的BroadcastReceiver

但是有一種情況, BroadcastReceiver的成員變量是可用的, 那就是動態(tài)注冊的BroadcastReceiver. 動態(tài)注冊的BroadcastReceiver對象的生命其實是受我們控制的.
實際測試, 使用Context.registerReceiver和Context.unregisterReceiver注冊的BroadcastReceiver每次收到廣播都是使用我們注冊時傳入的對象處理的, 這也是符合我們代碼上的邏輯的. 當然, 此時靜態(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被回調時, 有可能這個進程只承載了這個BroadcastReceiver, 比如我們的應用沒有運行的情況, 等onReceive返回, 這個時候我們的進程的會被視為空進程(empty process), 此時Android有極大可能回收掉空進程, 這種情況下靜態(tài)成員變量也無法保存值了.

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

<span id="BroadCastReceiver生命周期總結"/>

BroadCastReceiver生命周期總結

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

Android 廣播面試題

請描述一下 BroadcastReceiver

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

  • 無序廣播:完全異步,邏輯上可以被任何廣播接收者接收到。優(yōu)點是效率較高。缺點是一個接收者不能將處理結果傳遞給下一個接收者,并無法終止廣播 intent 的傳播。
  • 有序廣播:按照被接收者的優(yōu)先級順序,在被接收者中依次傳播。比如有三個廣播接收者 A,B,C,優(yōu)先級是 A >B > C。那這個消息先傳給 A,再傳給 B,最后傳給 C。每個接收者有權終止廣播,比如 B 終止廣播,C 就無法接收到。 此外 A 接收到廣播后可以對結果對象進行操作,當廣播傳給 B 時,B 可以從結果對象中取得 A 存入的數(shù)據(jù)。
    在 通 過 Context.sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras)時我們可以指定 resultReceiver 廣播接收者,這個接收者我們可以認為是最終接收者,通常情況下如果比他優(yōu)先級更高的接收者如果沒有終止廣播,那么他的 onReceive 會被執(zhí)行兩次,第一次是正常的按照優(yōu)先級順序執(zhí)行,第二次是作為最終接收者接收。如果比他優(yōu)先級高的接收者終止了廣播,那么他依然能接收到廣播。

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

詳細見上文#BroadcastReceiver簡介

如何注冊和使用 BroadcastReceiver

在清單文件中注冊廣播接收者稱為靜態(tài)注冊,在代碼中注冊稱為動態(tài)注冊。
靜態(tài)注冊的廣播接收者只要 app 在系統(tǒng)中運行則一直可以接收到廣播消息;
動態(tài)注冊的廣播接收者當注冊的 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);

詳細見上文#2、廣播接收器注冊

BroadCastReceiver生命周期

見上文#BroadCastReceiver生命周期總結

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

(2015.09.02)

1、自己的應用(假設名稱為應用 A)在發(fā)送廣播的時候給自己發(fā)送的廣播添加自定義權限,假設權限名為:

com.itheima.android.permission,然后需要在應用 A 的 AndroidManifest.xml 中聲明如下權限:

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

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

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

或者在應用 AndroidManifest.xml 中的<receiver>標簽中進行如下配置:

<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>

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

什么是最終廣播接收者?

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

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

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

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

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

如何判斷當前BroadcastReceiver接收到的是有序廣播還是無序廣播 ?

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

Android 引入廣播機制的用意

(2017-2-23)

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

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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容