Broadcast Receiver 基礎
Broadcast Receiver 是四大組件之一,可以用來接受系統或者app(可以app 內部組件,也可以是跨 app)的各種事件,當然這些事件必須通過 sendBroadcast()方法發送出來,Broadcast Receiver 纔可以接受到。
廣播可以作爲組件之間,跨進程,跨應用之間的通信,更多的時候,是配合系統的內置 Broadcast 對我們的 APP 需要的手機轉態進行管理,例如 wifi 狀態。
概述
註冊方式
你可以通過兩種方式註冊一個 Broadcast Receiver,一種是在AndroidMainFest 中註冊,稱爲靜態註冊,註冊方式如下:
<receiver android:name="br.NetWorkChangeToastReceiver"></receiver>
另一種是動態註冊,通過代碼註冊,調用 Context.registerReceiver() 方法去註冊
private void registerBroadcastReceiver() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(new NetWorkChangeToastReceiver(), intentFilter);
}
兩種方式存在一定的區別,通過代碼動態註冊的,具有組件級別效果,也就是說,在 Broadcast 未調用 onReceive()之前,如果通過unRegisterReceive()方法被調用,則該 Receiver 被銷燬;
注意:
在 onReceive() 是執行在主線程中的,所以不能在 onReceive()裏面做耗時操作,否則會引起 anr。
在同等情況下,動態註冊的廣播接收者會比靜態註冊的廣播接收者優先級高,會先收到 broadcast
生命週期解釋
通過 AndroidMainFest 文件註冊的 Receiver,在 APP 安裝的時候,會通過 package manage 全部註冊;從這個角度來說,這些 Receiver 和 APP 是相對獨立的,即便你的 APP 沒有啓動,這些 Receiver 也可以在 onReeiver() 接受特定的事件。
通過 Context 註冊的 Broadcast Receiver 的生命週期和註冊它的 Context 是息息相關的,在Context 沒有被銷燬的情況下,這個 Receiver 的onReceive()方法纔會被調用 。從另外一點來說,如果你是通過 Activity 去 register(),你需要在退出這個 Activity 之前 unRegister() 這個 Activity(),同時注意調用方法的時刻,如果在 onCreate() register,則需要在 onDestory() 的時候 unRegister():如果是 onResume() 的時候 register(),則需要在 onPasue() 的實現 unRegister();避免多次調用 register() 和 unRegister() 方法。
下面是一個例子,進行跨 APP 發送和接受廣播
首先在接收端 APP 實現自定義的 Broadcast Receiver,自定義的 BroadcastReceiver 類文件如下,這裏接受對應 Action 的Broadcast Receiver 然後Toast 接收到的內容:
public class AppCrossBroadcast extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TextUtils.equals(action, "com.fx.app.cross.br1")) {
String data = intent.getStringExtra("data");
if (data != null) {
Toast.makeText(context, "收到" + data, Toast.LENGTH_SHORT).show();
}
}
}
}
然後在接收端靜態註冊這個 Broadcast Receiver,在 androdmainfest 文件中:
<receiver
android:name="common.AppCrossBroadcast"
android:exported="true">
<intent-filter>
<action android:name="com.fx.app.cross.br1"></action>
</intent-filter>
</receiver>
這裏 android:exported=”true” 這個屬性設置爲 true 表示,這個Application 組件是否可以被其它應用訪問(調用);
基礎用法及語義
有序廣播和無序(標準)廣播
通過 sendBroadcast(Intent ) 方法發送的廣播,稱爲標準無序廣播,相對下面介紹的一種廣播,效率要高之。但是意味着所有匹配 Action 的 Reveiver 都可以接收。
我們可以通過 sendOrderBroadcast(Intent,String),發送有序廣播,這種廣播根據 Receiver 的優先級,被 Receiver 順序接收和處理,這裏意味着,你可以攔截 Broadcast(通過調用),你可以在 Receiver 之間傳遞數據。
下面是一個例子,首先定義 Broadcast Receiver:
public class OrderBr1 extends BroadcastReceiver {
public final String TAG = this.getClass().getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TextUtils.equals(action, "com.fx.order")) {
Toast.makeText(context, TAG + "收到廣播", Toast.LENGTH_SHORT).show();
}
}
}
然後依次複製,生成 OrderBr2,OrderBr3。代碼都是一樣的,不過在 OdderBr2 裏面,進行廣播的攔截,代碼如下:
public class OrderBr2 extends BroadcastReceiver {
public final String TAG = this.getClass().getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TextUtils.equals(action, "com.fx.order")) {
Toast.makeText(context, TAG + "收到廣播", Toast.LENGTH_SHORT).show();
}
abortBroadcast();// 攔截廣播
}
}
然後在 AndroidMainFest 中 receiver 標籤的子標籤 intent-filter 標籤裏面,修改 android:priority 屬性。
<receiver android:name="br.OrderBr1">
<intent-filter android:priority="100">
<action android:name="com.fx.order"></action>
</intent-filter>
</receiver>
<receiver android:name="br.OrderBr2">
<intent-filter android:priority="99">
<action android:name="com.fx.order"></action>
</intent-filter>
</receiver>
<receiver android:name="br.OrderBr3">
<intent-filter android:priority="98">
<action android:name="com.fx.order"></action>
</intent-filter>
</receiver>
最後,我們在我們的 Activity 裏面發送 Broadcast,代碼如下:
private void sendOrderBr() {
Intent intent = new Intent();
intent.setAction("com.fx.order");
sendOrderedBroadcast(intent, null);
}
我們可以看到,OrderBr3 是沒有接收到廣播的。
使用 permission
sendOrderedBroadcast(intent, null)方法裏面,第二個參數是一個 permission,這很好理解,我們的應用在使用某些功能或者訪問硬件的時候,需要一些 uses-permission 例如下面這個是訪問網絡狀態的 uses-permission。
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
同樣 BroadcastReceiver 也可以對 Receiver 所以的應用 進行 permission 過濾,這裏的過濾包括髮送方和接收方,對發送方過濾,意味着,如果你的應用沒有在 androidmanifest 中聲明對應的 例如我們發送一條這樣的廣播,
sendOrderedBroadcast(intent, Manifest.permission.SEND_SMS);
那麼只有在 AndroidManiFest 文件中,聲明瞭發送信息的權限,你的 app 才能準確的發送這條廣播,同樣的道理,想要接收這個 Broadcast 的 receiver 也需要
這裏可能比較難理解,我先講解一下,我們調用這個方法,最後會調用 ContextImpl 類裏面的 sendOrderedBroadcast()方法:
@Override
public void sendOrderedBroadcast(Intent intent, String receiverPermission) {
warnIfCallingFromSystemProcess();
String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
String[] receiverPermissions = receiverPermission == null ? null
: new String[] {receiverPermission};
try {
intent.prepareToLeaveProcess(this);
ActivityManagerNative.getDefault().broadcastIntent(
mMainThread.getApplicationThread(), intent, resolvedType, null,
Activity.RESULT_OK, null, null, receiverPermissions, AppOpsManager.OP_NONE,
null, true, false, getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
之後會把需要的 permission 寫進 Parcel 裏面,然後發送出去;那麼接收 Broadcast 的代碼呢?接收和發送 Broadcast 都是由一個叫做 ActivityManagerService 的類進行代理的,你通過 Activity 或者 Service 發出出去的 Broadcast 都有 ActivityManagerService 進行接收和分發,然後 ActivityManagerService 根據註冊的 permission 和優先級,分發 Broadcast。
tip:你也可在 receiver 標籤裏面,設置 android:permission 屬性,對
Local Broadcast
本地廣播,在 app 內通信; google 建議在不需要 IPC(跨進程通信)的前提下,使用 Local Broadcast 效率比普通Broadcast 要高。
android 可以通過 support 包輕鬆簡單的發送 local broadcast,先來看下 LocalBroadcastManager 類的介紹和用法:
LocalBroadcastManager 在v4 包裏面,根據 api 的介紹“這是一個幫助類,幫助你在你的進程內註冊和發送 broadcast”,然後使用 Local Broadcast 有以下幾個好處:
- 可以避免廣播被其它app 接受到,避免信息泄漏
- 可以避免自己的 Receiver 接收到破壞性的 Broadcast
- 不用進行 ipc,效率遠遠高於普通的 global Broadcast。
這個類,註冊和處理相關的 Broadcast,它和 ActivityManagerService 是獨立的,也就是你通過 LocalBroadcastManager 發送 Broadcast 和註冊 receiver 是不會通過 ActivityManagerService 的,下面是這個類處理 broadcast 的流程圖:
註冊的時候, 其實就是將 Receiver 加入到兩個HashMap 中,其中一個 mReceivers,記錄了某個Broadcast 對應的所有 IntentFilter,這個 HashMap 主要作爲鎖對象,確保所有的 Receiver 在線程中同步。另一個 HashMap 纔是起數據存儲的作用,把 action 作爲 key,IntentFilter 和 Receiver 值生成的複合對象作爲 value 值。
然後發送廣播的時候,大概流程如下:
發送廣播的時候,會根據Intent 的action 去查找是否有對應的 Receiver,然後匹配 action,scheme,category 等,如果匹配成功,則將對應的 Receiver 加入 一個 ArrayList,同時 Handler 發送消息,通知處理髮送的廣播,通過遍歷 ArrayList,執行符合條件的 Receiver 的onReceive()方法。
具體詳細可以參考源碼。
goAsync()方法
我們知道,一個 Receiver 如果執行完 onReceive() 方法,則這個組件會被系統回收。如果你在 onReceive()方法裏面執行異步操作,很可能因爲這個 Receiver 已經被收回了,導致業務出錯甚至app直接崩潰。android 在 Broadcast 類裏面 提供了 goAsync()方法,提供我們在其它子線程進行耗時操作,避免引起 anr 問題。
public class AsyncBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final PendingResult pendingResult = goAsync();
Thread thread = new Thread(new SleepThread(1000 * 20) {
@Override
public void run() {
super.run();
Log.i("Async", " ->finish");
pendingResult.finish();
}
});
thread.start();
Log.i("Async", " ->onReceive");
}
}
這樣需要進行耗時操作就不會引起 anr 問題了。
Sticky Broadcast- 粘性廣播
普通發送的廣播都是非粘性的,根據 google 官方 api 的解釋,所謂粘性廣播,定義如下:
Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the Intent you are sending stays around after the broadcast is complete,so that others can quickly retrieve that data through the return value of {@link #registerReceiver(BroadcastReceiver, IntentFilter)}. In all other ways, this behaves the same as{@link #sendBroadcast(Intent)}.
簡單來說,這條廣播會一直停留在系統裏面,所以只要有新的 receiver 註冊,馬上便可以收到這條廣播。不過,你可以通過 removeStickyBroadcast() 移除這條廣播。發送粘性廣播,需要特定的 user-permission,如下:
<uses-permission android:name="android.permission.BROADCAST_STICKY"></uses-permission>
但是在 api 21 以後,也就是 android 5.0 以後, 出於 安全原因,sticky broadcast 被廢棄,不再使用。