Broadcast Receiver 基礎

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 被銷燬;

注意:

  1. 在 onReceive() 是執行在主線程中的,所以不能在 onReceive()裏面做耗時操作,否則會引起 anr。

  2. 在同等情況下,動態註冊的廣播接收者會比靜態註冊的廣播接收者優先級高,會先收到 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 有以下幾個好處:

  1. 可以避免廣播被其它app 接受到,避免信息泄漏
  2. 可以避免自己的 Receiver 接收到破壞性的 Broadcast
  3. 不用進行 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 被廢棄,不再使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章