4.1 BroadcastReceiver 使用方法大全

點此進入:從零快速構建APP系列目錄導圖
點此進入:UI編程系列目錄導圖
點此進入:四大組件系列目錄導圖
點此進入:數據網絡和線程系列目錄導圖

爲了方便於進行系統級別的消息通知, Android 引入了一套類似的廣播消息機制,而且 Android 中的廣播機制顯得很靈活。它是 Android 四大組件之一,主要用於接收系統或者 App 發送的廣播事件。與廣播配套是用的是廣播接收器,它是一種用於響應系統範圍廣播通知的組件。許多廣播都是由系統發起的,當然應用也可以發起廣播。儘管廣播接收器不會顯示用戶界面,但它們可以創建狀態欄通知,在發生廣播事件時提醒用戶。但廣播接收器更常見的用途只是作爲通向其他組件的“通道”,設計用於執行極少量的工作。 例如,它可能會基於事件發起一項服務來執行某項工作。本篇就將對這一機制的方方面面進行詳細的講解。

本節例程下載地址:WillFlowBroadcast

一、廣播機制簡介

1、廣播機制概述

爲什麼說 Android 中的廣播機制更加靈活呢?這是因爲 Android 中的每個應用程序都可以對自己感興趣的廣播進行註冊,這樣該程序就只會接收到自己所關心的廣播內容,這些廣播可能是來自於系統的,例如,通知屏幕已關閉、電池電量不足或已拍攝照片的廣播。也可能是來自於其他應用程序的,例如,通知其他應用某些數據已下載至設備,並且可供其使用。當然 Android 提供了一套完整的 API,允許應用程序自由地發送和接收廣播。

2、廣播機制分類

Android 中的廣播主要可以分爲兩種類型:無序廣播 和 有序廣播。

無序廣播也叫標準廣播(Normal broadcasts),它是一種完全異步執行的廣播,在廣播發出之後,所有的廣播接收器幾乎都會在同一時刻接收到這條廣播消息,因此它們之間沒有任何先後順序可言。這種廣播的效率會比較高,但同時也意味着它是無法被截斷的。

標準廣播的工作流程如下圖所示:

有序廣播(Ordered broadcasts) 則是一種同步執行的廣播,在廣播發出之後,同一時刻只會有一個廣播接收器能夠收到這條廣播消息,當這個廣播接收器中的邏輯執行完畢後,廣播纔會繼續傳遞。比如有三個廣播接收者 A、B、C,優先級是 A > B > C。那這個消息先傳給 A,再傳給 B,最後傳給 C。每個接收者有權終止廣播,比如 B 終止廣播,C 就無法接收到。所以此時的廣播接收器是有先後順序的,優先級高的廣播接收器就可以先收到廣播消息,並且前面的廣播接收器還可以截斷正在傳遞的廣播,這樣後面的廣播接收器就無法收到廣播消息了。

有序廣播的工作流程如下圖所示:

有序廣播和無序廣播的共同點:

內部得的實現機制是相同的:通過 Android 系統的 Binder 機制實現通信。

3、關於 BroadCastReceiver 的生命週期

  • 相比於我們之前提到的Activity和Fragment的生命週期,廣播接收者的生命週期是非常短暫的,在接收到廣播的時候創建,onReceive()方法結束之後銷燬;
  • 廣播接收者中不要做一些耗時的工作,否則會彈出 Application No Response 錯誤對話框;
  • 最好也不要在廣播接收者中創建子線程做耗時的工作,因爲廣播接收者被銷燬後進程就成爲了空進程,很容易被系統殺掉;
  • 耗時的較長的工作最好放在服務中完成;

掌握了這些基本概念後,我們就可以來嘗試一下廣播的用法了。

二、接收系統廣播

Android 內置了很多系統級別的廣播,我們可以在應用程序中通過監聽這些廣播來得到各種系統的狀態信息。比如手機開機完成後會發出一條廣播,電池的電量發生變化會發出一條廣播,時間或時區發生改變也會發出一條廣播等等。如果想要接收到這些廣播,就需要使用廣播接收器,下面我們就來看一下它的具體用法。

1、動態註冊監聽網絡變化

廣播接收器可以自由地對自己感興趣的廣播進行註冊,這樣當有相應的廣播發出時,廣播接收器就能夠收到該廣播,並在內部處理相應的邏輯。註冊廣播的方式一般有兩種,在代碼中註冊和在 AndroidManifest.xml 中註冊,其中前者也被稱爲動態註冊,後者也被稱爲靜態註冊。

那麼該如何創建一個廣播接收器呢?其實只需要新建一個類,讓它繼承自 BroadcastReceiver,並重寫父類的 onReceive()方法就行了。這樣當有廣播到來時, onReceive()方法就會得到執行,具體的邏輯就可以在這個方法中處理。

修改 MainActivity 中的代碼,如下所示:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init();

    }

    private void init() {
        mIntentFilter = new IntentFilter();
        mIntentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");

        mNetworkChangeReceiver = new NetworkChangeReceiver();
        registerReceiver(mNetworkChangeReceiver, mIntentFilter);
    }

    /**
     * 接收系統廣播:動態的監聽網絡變化
     */
    private class NetworkChangeReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            ConnectivityManager connectionManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connectionManager.getActiveNetworkInfo();

            if (networkInfo != null && networkInfo.isAvailable()) {
                Toast.makeText(context, "網絡可用!", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(context, "網絡不可用!", Toast.LENGTH_SHORT).show();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mNetworkChangeReceiver);
    }

可以看到,我們在 MainActivity 中定義了一個內部類 NetworkChangeReceiver,這個類是繼承自 BroadcastReceiver 的,並重寫了父類的 onReceive()方法。這樣每當網絡狀態發生變化時, onReceive()方法就會得到執行,這裏只是簡單地使用 Toast 提示了一段文本信息。

然後觀察 onCreate()方法,首先我們創建了一個 IntentFilter 的實例,並給它添加了一個值爲 android.net.conn.CONNECTIVITY_CHANGE 的 action,爲什麼要添加這個值呢?因爲當網絡狀態發生變化時,系統發出的正是一條值爲 android.net.conn.CONNECTIVITY_CHANGE 的廣播,也就是說我們的廣播接收器想要監聽什麼廣播,就在這裏添加相應的action 就行了。接下來創建了一個 NetworkChangeReceiver 的實例,然後調用 registerReceiver() 方法進行註冊,將 NetworkChangeReceiver 的實例和 IntentFilter 的實例都傳了進去,這樣 NetworkChangeReceiver 就會收到所有值爲 android.net.conn.CONNECTIVITY_CHANGE 的廣播,也就實現了監聽網絡變化的功能。

然後我們通過 getSystemService() 方法得到了 ConnectivityManager 的實例,這是一個系統服務類,專門用於管理網絡連接的。然後調用它的 getActiveNetworkInfo() 方法可以得到 NetworkInfo 的實例,接着調用 NetworkInfo 的 isAvailable() 方法,就可以判斷出當前是否有網絡了,最後我們還是通過 Toast 的方式對用戶進行提示。

另外,這裏有非常重要的一點需要說明, Android 系統爲了保證應用程序的安全性做了規定,如果程序需要訪問一些系統的關鍵性信息,必須在配置文件中聲明權限纔可以,否則程序將會直接崩潰,比如這裏查詢系統的網絡狀態就是需要聲明權限的。方法是:打開AndroidManifest.xml 文件,在

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.wgh.willflowbroadcast">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

最後要記得,動態註冊的廣播接收器一定都要取消註冊才行,這裏我們是在 onDestroy() 方法中通過調用 unregisterReceiver()方法來實現的。

編譯運行看效果:

2、靜態註冊實現開機啓動

動態註冊的廣播接收器可以自由地控制註冊與註銷,在靈活性方面有很大的優勢,但是它也存在着一個缺點,即必須要在程序啓動之後才能接收到廣播,因爲註冊的邏輯是寫在 onCreate() 方法中的。那麼有沒有什麼辦法可以讓程序在未啓動的情況下就能接收到廣播呢?這就需要使用靜態註冊的方式了。

這裏我們準備讓程序接收一條開機廣播,當收到這條廣播時就可以在 onReceive() 方法裏執行相應的邏輯,從而實現開機啓動的功能。

新建一個 BootCompleteReceiver ,代碼如下:
    /**
     * 接收系統廣播:靜態的監聽系統自啓動
     */
    public class BootCompleteReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "接收到開機自啓動廣播!", Toast.LENGTH_LONG).show();
        }
    }

可以看到,這裏不再使用內部類的方式來定義廣播接收器,因爲我們需要在 AndroidManifest.xml 中將這個廣播接收器的類名註冊進去:

        <receiver android:name=".MainActivity$BootCompleteReceiver">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

終於, 標籤內出現了一個新的標籤 ,所有靜態註冊的廣播接收器都是在這裏進行註冊的。它的用法其實和 標籤非常相似,首先通過 android:name 來指定具體註冊哪一個廣播接收器,然後在 標籤里加入想要接收的廣播就行了,由於 Android 系統啓動完成後會發出一條值爲 android.intent.action.BOOT_COMPLETED 的廣播,因此我們在這裏添加了相應的 action。

最後別忘了添加相應的權限:

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

三、發送自定義廣播

1、發送標準廣播

在發送廣播之前,我們還是需要先定義一個廣播接收器來準備接收此廣播才行, 不然發出去也是白髮。

新建一個 MyBroadcastReceiver,代碼如下:
    /**
     * 接收自定義廣播
     */
    public static class MyBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "接收到自定義廣播!", Toast.LENGTH_SHORT).show();
        }
    }

注意:因爲此處代碼我們定義成了 MainActivity 的內部類,所以該廣播接收者應該定義成static的,否則的話會報錯並引起應用崩潰。

然後在 AndroidManifest.xml 中對這個廣播接收器進行靜態註冊:

    <receiver android:name=".MainActivity$MyBroadcastReceiver">
        <intent-filter>
            <action android:name="com.willflow.broadcasttest.MY_BROADCAST" />
        </intent-filter>
    </receiver>

可以看到,這裏讓 MyBroadcastReceiver 接收一條值爲 com.willflow.broadcasttest.MY_BROADCAST 的廣播,因此我們在發送廣播的時候,需要發出這樣的一條廣播:

    private void initView() {
        mButton = (Button) findViewById(R.id.buttonPanel);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent("com.willflow.broadcasttest.MY_BROADCAST");
                sendBroadcast(intent);
            }
        });
    }

可以看到,我們在按鈕的點擊事件裏面加入了發送自定義廣播的邏輯。首先構建出了一個 Intent 對象,並把要發送的廣播的值傳入,然後調用了 Context 的 sendBroadcast()方法將廣播發送出去,這樣所有監聽 com.willflow.broadcasttest.MY_BROADCAST 這條廣播的廣播接收器就會收到消息,此時發出去的廣播就是一條標準廣播。

編譯運行看效果:

這樣我們就成功完成了發送自定義廣播的功能。另外,由於廣播是使用 Intent 進行傳遞的,因此我們還可以在 Intent 中攜帶一些數據傳遞給廣播接收器。

2、發送有序廣播

廣播是一種可以跨進程的通信方式,這一點從前面接收系統廣播的時候就可以看出來了,因此在我們應用程序內發出的廣播,其他的應用程序應該也是可以收到的。那麼如何如何做到只有特定的應用的廣播接受者才能接收到廣播呢?這其實就用到了有序廣播。

更改 MainActivity 中的發送廣播代碼如下:
    private void initView() {
        mButton = (Button) findViewById(R.id.buttonPanel);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent("com.willflow.broadcasttest.MY_BROADCAST");
                // 發送有序廣播
                sendOrderedBroadcast(intent, null);
            }
        });
    }

可以看到,發送有序廣播只需要改動一行代碼,即將 sendBroadcast()方法改成 sendOrderedBroadcast()方法就可以了。 sendOrderedBroadcast()方法接收兩個參數,第一個參數仍然是Intent,第二個參數是一個與權限相關的字符串,這裏傳入 null 就行了。那麼該如何設定廣播接收器的先後順序呢?當然是在註冊的時候進行設定的了。

修改 AndroidManifest.xml 中的代碼,如下所示:
        <receiver android:name=".MainActivity$MyBroadcastReceiver">
            <intent-filter  android:priority="100" >
                <action android:name="com.willflow.broadcasttest.MY_BROADCAST" />
            </intent-filter>
        </receiver>

可以看到,我們通過 android:priority 屬性給廣播接收器設置了優先級,優先級比較高的廣播接收器就可以先收到廣播,這裏將 MyBroadcastReceiver 的優先級設成了 100,以保證它一定會在 AnotherBroadcastReceiver 之前收到廣播。既然已經獲得了接收廣播的優先權,那麼 MyBroadcastReceiver 就可以選擇是否允許廣播繼續傳遞了。

修改 MyBroadcastReceiver 中的代碼,如下所示:
    /**
     * 接收自定義廣播
     */
    public class MyBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "接收到自定義廣播!", Toast.LENGTH_SHORT).show();
            // 終止廣播的傳遞
            abortBroadcast();
        }
    }

我們在 onReceive() 方法中調用了 abortBroadcast() 方法,表示將這條廣播截斷,後面的廣播接收器將無法再接收到這條廣播。所以我們可以再註冊一個相同的自定義廣播接受者,代碼如下:

    public class AnotherBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "在另一個廣播接受者中接收到自定義廣播!", Toast.LENGTH_SHORT).show();
        }
    }

同樣需要進行靜態註冊:

        <receiver android:name=".MainActivity$AnotherBroadcastReceiver">
            <intent-filter>
                <action android:name="com.willflow.broadcasttest.MY_BROADCAST" />
            </intent-filter>
        </receiver>

注意,這裏我們沒有指定優先級別。

當然這個廣播其實也是可以在另起一個App進行註冊的,不過我們看到的效果將會是同樣的,因爲前面說過了:廣播是一種可以跨進程的通信方式。另外,我們提供一種簡單的判斷接收到的廣播是否爲有序廣播的方式:在 BroadcastReceiver 類中 onReceive() 方法中調用 boolean b = isOrderedBroadcast() ,這個方法用於告訴我們當前的接收到的廣播是否爲有序廣播。

最後,我們說一下 sendOrderedBroadcast 的另一個重載函數。我們在通過 Context.sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras) 發送廣播時,我們可以指定 resultReceiver 廣播接收者,這個接收者我們可以認爲是最終接收者,通常情況下如果比他優先級更高的接收者如果沒有終止廣播,那麼他的 onReceive 會被執行兩次,第一次是正常的按照優先級順序執行接收,第二次是作爲最終接收者接收,如果比它優先級高的接收者終止了廣播,那麼它依然能接收到廣播。

四、使用本地廣播

前面我們發送和接收的廣播全部都是屬於系統全局廣播,即發出的廣播可以被其他任何的任何應用程序接收到,並且我們也可以接收來自於其他任何應用程序的廣播。這樣就很容易會引起安全性的問題,比如說我們發送的一些攜帶關鍵性數據的廣播有可能被其他的應用程序截獲,或者其他的程序不停地向我們的廣播接收器裏發送各種垃圾廣播。

爲了能夠簡單地解決廣播的安全性問題, Android 引入了一套本地廣播機制,使用這個機制發出的廣播只能夠在應用程序的內部進行傳遞,並且廣播接收器也只能接收來自本應用程序發出的廣播,這樣所有的安全性問題就都不存在了。本地廣播的用法並不複雜,主要就是使用了一個 LocalBroadcastManager 來對廣播進行管理,並提供了發送廣播和註冊廣播接收器的方法。

修改 MainActivity 中的代碼,如下所示:
    private void initView() {
        mButtonLocal = (Button) findViewById(R.id.buttonLocal);
        mButtonLocal.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent("com.willflow.broadcasttest.LOCAL_BROADCAST");
                // 發送本地廣播
                mLocalBroadcastManager.sendBroadcast(intent);
            }
        });
    }

    private void init() {
        mIntentFilterLocal = new IntentFilter();
        mIntentFilterLocal.addAction("com.willflow.broadcasttest.LOCAL_BROADCAST");

        mLocalReceiver = new LocalReceiver();
        mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
        mLocalBroadcastManager.registerReceiver(mLocalReceiver, mIntentFilterLocal);
    }

    /**
     * 接收本地廣播
     */
    private class LocalReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "接收到本地廣播!", Toast.LENGTH_LONG).show();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mLocalBroadcastManager.unregisterReceiver(mLocalReceiver);
    }

其實這基本上就和我們前面所學的動態註冊廣播接收器以及發送廣播的代碼是一樣的。只不過現在首先是通過 LocalBroadcastManager 的 getInstance() 方法得到了它的一個實例,然後在註冊廣播接收器的時候調用的是 LocalBroadcastManager 的 registerReceiver() 方法,在發送廣播的時候調用的是 LocalBroadcastManager 的 sendBroadcast() 方法,僅此而已。

另外還有一點需要說明,本地廣播是無法通過靜態註冊的方式來接收的。其實這也完全可以理解,因爲靜態註冊主要就是爲了讓程序在未啓動的情況下也能收到廣播,而發送本地廣播時,我們的程序肯定是已經啓動了,因此也完全不需要使用靜態註冊的功能。

編譯運行看效果:

五、使用廣播的幾點問題

1、如何讓自己的廣播只讓指定的 App 接收?

其實這個問題之前我們已經回答過了,這裏我們再提供幾種方式。

(1)添加權限

自己的應用(假設名稱爲應用 A)在發送廣播的時候給自己發送的廣播添加自定義權限,假設權限名爲:com.willflow.android.permission,然後需要在應用 A 的 AndroidManifest.xml 中聲明如下權限:

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

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

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

或者在應用 AndroidManifest.xml 中的 標籤中進行如下配置:

        <receiver android:name="com.willflow.android.broadcastReceiver.MyReceiver"
            android:permission="com.willflow.android.permission">
            <intent-filter >
                <action android:name="com.willflow.mybroadcast"></action>
            </intent-filter>
        </receiver>
每個權限通過 protectionLevel 來標識保護級別:
  • normal :低風險權限,只要請了就可以使用(在 AndroidManifest.xml 中添加 標籤),安裝時不需要用戶確認;
  • dangerous:高風險權限,安裝時需要用戶的確認纔可使用;
  • signature:只有當申請權限的應用程序的數字簽名與聲明此權限的應用程序的數字簽名相同時(如果是申請系統權限,則需要與系統簽名相同),才能將權限授給它;
  • signatureOrSystem:簽名相同,或者申請權限的應用爲系統應用(在 system image 中)。

上述四類權限級別同樣可用於自定義權限中,如果我們需要對自己的應用程序(或部分應用)進行訪問控制,則可以通過在 AndroidManifest.xml 中添加 標籤,將其屬性中的 protectionLevel 設置爲上述四類級別中的某一種來實現。

2、廣播的優先級對無序廣播生效嗎?

廣播的優先級推薦的範圍是:[-1000,+1000],而且廣播的優先級對無序廣播其實也是生效的,有可能你會覺得這很奇怪,但是還有更讓人奇怪的,那就是如果設置的優先級值超過這個範圍也是可以的。另外關於優先級還有一個值得注意的問題,那就是動態註冊的廣播優先級誰高呢?答案是:誰先註冊誰優先級高。也就是我們所說的先來後到原則。

3、Android 4.3 以上版本監聽開機啓動廣播的問題解決

在Android 4.3以上的版本,允許我們將應用安裝在SD上,我們都知道是系統開機間隔一小段時間後,才裝載SD卡的,這樣我們的應用就可能監聽不到這個廣播了。所以我們需要既監聽開機廣播又監聽SD卡掛載廣播纔可以解決這個問題。

另外,有些手機可能並沒有SD卡,所以這兩個廣播監聽我們不能寫到同一個Intetn-filter裏面,而是應該寫成兩個,配置代碼如下:

<receiver android:name=".MyBroadcastReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
    <intent-filter>
        <action android:name="ANDROID.INTENT.ACTION.MEDIA_MOUNTED"/>
        <action android:name="ANDROID.INTENT.ACTION.MEDIA_UNMOUNTED"/>
        <data android:scheme="file"/>
    </intent-filter>
</receiver>

最後,給大家提供下常用的系統廣播,你可以到下篇查看!

點此進入:GitHub開源項目“愛閱”。“愛閱”專注於收集優質的文章、站點、教程,與大家共分享。下面是“愛閱”的效果圖:


聯繫方式:

簡書:WillFlow
CSDN:WillFlow
微信公衆號:WillFlow

微信公衆號:WillFlow

發佈了81 篇原創文章 · 獲贊 21 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章