前言
-
BroadcastReceiver
(廣播接收器),屬于Android
四大組件之一 - 在
Android
開發中,BroadcastReceiver
的應用場景非常多 - 今天,我將詳細講解關于
BroadcastReceiver
的一切相關知識
目錄
1. 定義
即 廣播,是一個全局的監聽器,屬于Android
四大組件之一
Android
廣播分為兩個角色:廣播發送者、廣播接收者
2. 作用
監聽 / 接收 應用 App
發出的廣播消息,并 做出響應
3. 應用場景
-
Android
不同組件間的通信(含 :應用內 / 不同應用之間) - 多線程通信
- 與
Android
系統在特定情況下的通信
如:電話呼入時、網絡可用時
4. 實現原理
4.1 采用的模型
-
Android
中的廣播使用了設計模式中的觀察者模式:基于消息的發布 / 訂閱事件模型
因此,Android將廣播的發送者 和 接收者 解耦,使得系統方便集成,更易擴展
4.2 模型講解
-
模型中有3個角色:
- 消息訂閱者(廣播接收者)
- 消息發布者(廣播發布者)
- 消息中心(
AMS
,即Activity Manager Service
)
示意圖 & 原理如下
5. 使用流程
- 使用流程如下:
- 下面,我將一步步介紹如何使用
BroadcastReceiver
即上圖中的 開發者手動完成部分
5.1 自定義廣播接收者BroadcastReceiver
- 繼承
BroadcastReceivre
基類 - 必須復寫抽象方法
onReceive()
方法
- 廣播接收器接收到相應廣播后,會自動回調
onReceive()
方法- 一般情況下,
onReceive
方法會涉及 與 其他組件之間的交互,如發送Notification
、啟動Service
等- 默認情況下,廣播接收器運行在
UI
線程,因此,onReceive()
方法不能執行耗時操作,否則將導致ANR
- 代碼范例
mBroadcastReceiver.java
// 繼承BroadcastReceivre基類
public class mBroadcastReceiver extends BroadcastReceiver {
// 復寫onReceive()方法
// 接收到廣播后,則自動調用該方法
@Override
public void onReceive(Context context, Intent intent) {
//寫入接收廣播后的操作
}
}
5.2 廣播接收器注冊
注冊的方式分為兩種:靜態注冊、動態注冊
5.2.1 靜態注冊
- 注冊方式:在AndroidManifest.xml里通過<receive>標簽聲明
- 屬性說明:
<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>
- 注冊示例
<receiver
//此廣播接收者類是mBroadcastReceiver
android:name=".mBroadcastReceiver" >
//用于接收網絡狀態改變時發出的廣播
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
當此 App
首次啟動時,系統會自動實例化mBroadcastReceiver
類,并注冊到系統中。
5.2.2 動態注冊
注冊方式:在代碼中調用
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. 動態注冊:調用Context的registerReceiver()方法
registerReceiver(mBroadcastReceiver, intentFilter);
}
// 注冊廣播后,要在相應位置記得銷毀廣播
// 即在onPause() 中unregisterReceiver(mBroadcastReceiver)
// 當此Activity實例化時,會動態將MyBroadcastReceiver注冊到系統中
// 當此Activity銷毀時,動態注冊的MyBroadcastReceiver將不再接收到相應的廣播。
@Override
protected void onPause() {
super.onPause();
//銷毀在onResume()方法中的廣播
unregisterReceiver(mBroadcastReceiver);
}
}
特別注意
- 動態廣播最好在
Activity
的onResume()
注冊、onPause()
注銷。 - 原因:
- 對于動態廣播,有注冊就必然得有注銷,否則會導致內存泄露
重復注冊、重復注銷也不允許
-
Activity
生命周期如下:
Activity生命周期的方法是成對出現的:
- onCreate() & onDestory()
- onStart() & onStop()
- onResume() & onPause()
在onResume()注冊、onPause()注銷是因為onPause()在App死亡前一定會被執行,從而保證廣播在App死亡前一定會被注銷,從而防止內存泄露。
- 不在onCreate() & onDestory() 或 onStart() & onStop()注冊、注銷是因為:
當系統因為內存不足(優先級更高的應用需要內存,請看上圖紅框)要回收Activity占用的資源時,Activity在執行完onPause()方法后就會被銷毀,有些生命周期方法onStop(),onDestory()就不會執行。當再回到此Activity時,是從onCreate方法開始執行。- 假設我們將廣播的注銷放在onStop(),onDestory()方法里的話,有可能在Activity被銷毀后還未執行onStop(),onDestory()方法,即廣播仍還未注銷,從而導致內存泄露。
- 但是,onPause()一定會被執行,從而保證了廣播在App死亡前一定會被注銷,從而防止內存泄露。
5.2.3 兩種注冊方式的區別
5.3 廣播發送者向AMS發送廣播
5.3.1 廣播的發送
- 廣播 是 用”意圖(
Intent
)“標識 - 定義廣播的本質 = 定義廣播所具備的“意圖(
Intent
)” - 廣播發送 = 廣播發送者 將此廣播的“意圖(
Intent
)”通過sendBroadcast()方法發送出去
5.3.2 廣播的類型
廣播的類型主要分為5類:
- 普通廣播(
Normal Broadcast
) - 系統廣播(
System Broadcast
) - 有序廣播(
Ordered Broadcast
) - 粘性廣播(
Sticky Broadcast
) - App應用內廣播(
Local Broadcast
)
具體說明如下:
1. 普通廣播(Normal Broadcast)
即 開發者自身定義 intent
的廣播(最常用)。發送廣播使用如下:
Intent intent = new Intent();
//對應BroadcastReceiver中intentFilter的action
intent.setAction(BROADCAST_ACTION);
//發送廣播
sendBroadcast(intent);
- 若被注冊了的廣播接收者中注冊時
intentFilter
的action
與上述匹配,則會接收此廣播(即進行回調onReceive()
)。如下mBroadcastReceiver
則會接收上述廣播
<receiver
//此廣播接收者類是mBroadcastReceiver
android:name=".mBroadcastReceiver" >
//用于接收網絡狀態改變時發出的廣播
<intent-filter>
<action android:name="BROADCAST_ACTION" />
</intent-filter>
</receiver>
- 若發送廣播有相應權限,那么廣播接收者也需要相應權限
2. 系統廣播(System Broadcast)
- Android中內置了多個系統廣播:只要涉及到手機的基本操作(如開機、網絡狀態變化、拍照等等),都會發出相應的廣播
- 每個廣播都有特定的Intent - Filter(包括具體的action),Android系統廣播action如下:
系統操作 | action |
---|---|
監聽網絡變化 | android.net.conn.CONNECTIVITY_CHANGE |
關閉或打開飛行模式 | Intent.ACTION_AIRPLANE_MODE_CHANGED |
充電時或電量發生變化 | Intent.ACTION_BATTERY_CHANGED |
電池電量低 | Intent.ACTION_BATTERY_LOW |
電池電量充足(即從電量低變化到飽滿時會發出廣播 | Intent.ACTION_BATTERY_OKAY |
系統啟動完成后(僅廣播一次) | 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 |
關閉系統時 | Intent.ACTION_SHUTDOWN |
重啟設備 | Intent.ACTION_REBOOT |
注:當使用系統廣播時,只需要在注冊廣播接收者時定義相關的action即可,并不需要手動發送廣播,當系統有相關操作時會自動進行系統廣播
3. 有序廣播(Ordered Broadcast)
- 定義
發送出去的廣播被廣播接收者按照先后順序接收
有序是針對廣播接收者而言的
-
廣播接受者接收廣播的順序規則(同時面向靜態和動態注冊的廣播接受者)
- 按照Priority屬性值從大-小排序;
- Priority屬性相同者,動態注冊的廣播優先;
-
特點
- 接收廣播按順序接收
- 先接收的廣播接收者可以對廣播進行截斷,即后接收的廣播接收者不再接收到此廣播;
- 先接收的廣播接收者可以對廣播進行修改,那么后接收的廣播接收者將接收到被修改后的廣播
具體使用
有序廣播的使用過程與普通廣播非常類似,差異僅在于廣播的發送方式:
sendOrderedBroadcast(intent);
4. App應用內廣播(Local Broadcast)
背景
Android中的廣播可以跨App直接通信(exported對于有intent-filter情況下默認值為true)-
沖突
可能出現的問題:- 其他App針對性發出與當前App intent-filter相匹配的廣播,由此導致當前App不斷接收廣播并處理;
- 其他App注冊與當前App一致的intent-filter用于接收廣播,獲取廣播具體信息;
即會出現安全性 & 效率性的問題。
解決方案
使用App應用內廣播(Local Broadcast)
- App應用內廣播可理解為一種局部廣播,廣播的發送者和接收者都同屬于一個App。
- 相比于全局廣播(普通廣播),App應用內廣播優勢體現在:安全性高 & 效率高
-
具體使用1 - 將全局廣播設置成局部廣播
- 注冊廣播時將exported屬性設置為false,使得非本App內部發出的此廣播不被接收;
- 在廣播發送和接收時,增設相應權限permission,用于權限驗證;
- 發送廣播時指定該廣播接收器所在的包名,此廣播將只會發送到此包中的App內與之相匹配的有效廣播接收器中。
通過intent.setPackage(packageName)指定報名
具體使用2 - 使用封裝好的LocalBroadcastManager類
使用方式上與全局廣播幾乎相同,只是注冊/取消注冊廣播接收器和發送廣播時將參數的context變成了LocalBroadcastManager的單一實例
注:對于LocalBroadcastManager方式發送的應用內廣播,只能通過LocalBroadcastManager動態注冊,不能靜態注冊
//注冊應用內廣播接收器
//步驟1:實例化BroadcastReceiver子類 & IntentFilter mBroadcastReceiver
mBroadcastReceiver = new mBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
//步驟2:實例化LocalBroadcastManager的實例
localBroadcastManager = LocalBroadcastManager.getInstance(this);
//步驟3:設置接收廣播的類型
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
//步驟4:調用LocalBroadcastManager單一實例的registerReceiver()方法進行動態注冊
localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);
//取消注冊應用內廣播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver);
//發送應用內廣播
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
localBroadcastManager.sendBroadcast(intent);
5. 粘性廣播(Sticky Broadcast)
由于在Android5.0 & API 21中已經失效,所以不建議使用,在這里也不作過多的總結。
6. 特別注意
對于不同注冊方式的廣播接收器回調OnReceive(Context context,Intent intent)中的context返回值是不一樣的:
- 對于靜態注冊(全局+應用內廣播),回調onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;
- 對于全局廣播的動態注冊,回調onReceive(context, intent)中的context返回值是:Activity Context;
- 對于應用內廣播的動態注冊(LocalBroadcastManager方式),回調onReceive(context, intent)中的context返回值是:Application Context。
- 對于應用內廣播的動態注冊(非LocalBroadcastManager方式),回調onReceive(context, intent)中的context返回值是:Activity Context;
7. 總結
- 本文主要介紹了
Android
中四大組件的BroadcastReceiver
的所有知識 - Carson帶你學四大組件文章系列:
Carson帶你學Android:頁面活動-Activity
Carson帶你學Android:廣播-BroadcastReceiver
Carson帶你學Android:服務-Service
Carson帶你學Android:內存承載器-ContentProvider
歡迎關注Carson_Ho的簡書
不定期分享關于安卓開發的干貨,追求短、平、快,但卻不缺深度。