【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個角色:
- 消息訂閱者(廣播接收者)
- 消息發(fā)布者(廣播發(fā)布者)
- 消息中心(
AMS
,即Activity Manager Service
)
- 示意圖 & 原理如下
使用流程
- 使用流程如下:
- 下面,我將一步步介紹如何使用
BroadcastReceiver
即上圖中的 開發(fā)者手動完成部分
1、自定義廣播接收器BroadcastReceiver
- 繼承BroadcastReceivre基類
- 必須復寫抽象方法onReceive()方法
- 廣播接收器接收到相應廣播后,會自動回調 onReceive() 方法
- 一般情況下,onReceive方法會涉及 與其他組件之間的交互,如發(fā)送Notification、啟動Service等
- 默認情況下,廣播接收器運行在 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()注銷。
- 原因:
- 對于動態(tài)廣播,有注冊就必然得有注銷,否則會導致內存泄露
重復注冊、重復注銷也不允許
- 對于動態(tài)廣播,有注冊就必然得有注銷,否則會導致內存泄露
-
Activity生命周期如下:
Activity生命周期
Activity生命周期的方法是成對出現(xiàn)的:
- onCreate() & onDestory()
- onStart() & onStop()
- onResume() & onPause()
在onResume()注冊、onPause()注銷是因為onPause()在App死亡前一定會被執(zhí)行,從而保證廣播在App死亡前一定會被注銷,從而防止內存泄露。
- 不在onCreate() & onDestory() 或 onStart() & onStop()注冊、注銷是因為:當系統(tǒng)因為內存不足(優(yōu)先級更高的應用需要內存,請看上圖紅框)要回收Activity占用的資源時,Activity在執(zhí)行完onPause()方法后就會被銷毀,有些生命周期方法onStop(),onDestory()就不會執(zhí)行。當再回到此Activity時,是從onCreate方法開始執(zhí)行。
- 假設我們將廣播的注銷放在onStop(),onDestory()方法里的話,有可能在Activity被銷毀后還未執(zhí)行onStop(),onDestory()方法,即廣播仍還未注銷,從而導致內存泄露。
- 但是,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-filter的action中的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)
- 本地廣播可理解為一種局部廣播,廣播的發(fā)送者和接收者都同屬于一個App。
- 相比于全局廣播(普通廣播),App應用內廣播優(yōu)勢體現(xiàn)在:安全性高 & 效率高
-
具體使用1——將全局廣播設置成局部廣播
- 注冊廣播時將exported屬性設置為false,使得非本App內部發(fā)出的此廣播不被接收;
- 在廣播發(fā)送和接收時,增設相應權限permission,用于權限驗證;
- 發(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)注冊的廣播接受者)
- 按照Priority屬性值從大-小排序;
- Priority屬性相同者,動態(tài)注冊的廣播優(yōu)先;
- 特點
- 接收廣播按順序接收
- 先接收的廣播接收者可以對廣播進行截斷(abortBroadcast()),即后接收的廣播接收者不再接收到此廣播;
- 先接收的廣播接收者可以對廣播進行修改,再使用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生命周期總結
- 廣播接收者的生命周期非常短暫的,在接收到廣播的時候創(chuàng)建,onReceive()方法結束之后銷毀;
- 廣播接收者中不要做一些耗時的工作,否則會彈出 Application No Response 錯誤對話框;
- 最好也不要在廣播接收者中創(chuàng)建子線程做耗時的工作,因為廣播接收者被銷毀后進程就成為了空進程,很容易被系統(tǒng)殺掉;
- 耗時的較長的工作可以通過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生命周期
如何讓自己的廣播只讓指定的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)
- 從 MVC 的角度考慮(應用程序內) 其實回答這個問題的時候還可以這樣問,android 為什么要有那 4 大組件, 現(xiàn)在的移動開發(fā)模型基本上也是照搬的 web 那一套 MVC 架構,只不過是改了點嫁妝而已。android 的四大組件本質 上就是為了實現(xiàn)移動或者說嵌入式設備上的 MVC 架構,它們之間有時候是一種相互依存的關系,有時候又是一種補 充關系,引入廣播機制可以方便幾大組件的信息和數(shù)據(jù)交互。
- 程序間互通消息(例如在自己的應用程序內監(jiān)聽系統(tǒng)來電)
- 效率上(參考 UDP 的廣播協(xié)議在局域網的方便性)
- 設計模式上(反轉控制的一種應用,類似監(jiān)聽者模式)
引用:
Android四大組件:BroadcastReceiver史上最全面解析
Android四大組件——BroadcastReceiver普通廣播、有序廣播、攔截廣播、本地廣播、Sticky廣播、系統(tǒng)廣播
BroadcastReceiver生命周期探討
Android開發(fā)之BroadcastReceiver詳解
BroadcastReceiver生命周期探討