Android四大組件之廣播接收器(BroadCast Receiver)

(內(nèi)容來自《Android第一行代碼(第二版)》)

附:Android基礎(chǔ)之四大組件

本文目錄

1. 廣播簡介

2. 廣播的接收

  • 2.1 動態(tài)注冊監(jiān)聽網(wǎng)絡(luò)變化
  • 2.2 靜態(tài)實(shí)現(xiàn)開機(jī)啟動注冊

3. 發(fā)送自定義廣播

  • 3.1 發(fā)送標(biāo)準(zhǔn)廣播
  • 3.2 發(fā)送有序廣播

4. 使用本地廣播


分割線


1. 廣播簡介

Android中的廣播主要可以分為兩種類型:標(biāo)準(zhǔn)廣播有序廣播。
標(biāo)準(zhǔn)廣播( Normal broadcasts)是一種完全異步執(zhí)行的廣播,在廣播發(fā)出之后,所有的廣 播接收器幾乎都會在同一時刻接收到這條廣播消息,因此它們之間沒有任何先后順序可言。這種廣播的效率會比較高,但同時也意味著它是無法被截斷的。

標(biāo)準(zhǔn)廣播的工作流程.png

有序廣播( Ordered broadcasts)則是一種同步執(zhí)行的廣播,在廣播發(fā)出之后,同一時刻只會有一個廣播接收器能夠收到這條廣播消息,當(dāng)這個廣播接收器中的邏輯執(zhí)行完畢后,廣播才會繼續(xù)傳遞。所以此時的廣播接收器是有先后順序的,優(yōu)先級高的廣播接收器就可以先收到廣播消息,并且前面的廣播接收器還可以截斷正在傳遞的廣播,這樣后面的廣播接收器就無法收到廣播消息了。

有序廣播的工作流程.png

2. 廣播的接收

2.1.動態(tài)注冊監(jiān)聽網(wǎng)絡(luò)變化

廣播接收器可以自由地對自己感興趣的廣播進(jìn)行注冊,這樣當(dāng)有相應(yīng)的廣播發(fā)出時,廣播接收器就能夠收到該廣播,并在內(nèi)部處理相應(yīng)的邏輯。

注冊廣播的方式一般有兩種,在代碼中注冊和在AndroidManifest.xml中注冊,其中前者也被稱為動態(tài)注冊,后者也被稱為靜態(tài)注冊。

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

新建一個BroadcastTest項(xiàng)目,然后修改MainActivity中的代碼
public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;

    private NetworkChangeReceiver networkChangeReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        intentFilter = new IntentFilter();
        intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        networkChangeReceiver = new NetworkChangeReceiver();
        registerReceiver(networkChangeReceiver, intentFilter);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(networkChangeReceiver);
    }
    
    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, "network is available", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(context, "network is unavailable", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

可以看到,我們在 MainActivity中定義了一個內(nèi)部類NetworkChangeReceiver,這個類是繼承自 BroadcastReceiver的,并重寫了父類的 onReceive()方法。這樣每當(dāng)網(wǎng)絡(luò)狀態(tài)發(fā)生變化時, onReceive()方法就會得到執(zhí)行。

在 onReceive()方法中

  • 首先通過 getSystemService()方法得到了 ConnectivityManager的實(shí)例,這是一個系統(tǒng)服務(wù)類,專門用于管理網(wǎng)絡(luò)連接的。
  • 然后調(diào)用它的 getActiveNetworkInfo()方法可以得到 NetworkInfo的實(shí)例
  • 接著調(diào)用 NetworkInfo的 isAvailable()方法,就可以判斷出當(dāng)前是否有網(wǎng)絡(luò)了
  • 最后我們通過 Toast的方式對用戶進(jìn)行提示。

然后觀察 onCreate()方法

  • 首先我們創(chuàng)建了一個 IntentFilter的實(shí)例,并給它添加了個值為 android.net.conn. CONNECTIVITY_CHANGE的 action,為什么要添加這個值呢?因?yàn)楫?dāng)網(wǎng)絡(luò)狀態(tài)發(fā)生變化時,系統(tǒng)發(fā)出的正是一條值為android.net.conn.CONNECTIVITY_CHANGE的廣播,也就是說我們的廣播接收器想要監(jiān)聽什么廣播,就在這里添加相應(yīng)的action。
  • 接下來創(chuàng)建了ー個 NetworkChangeReceiver的實(shí)例,然后調(diào)用registerReceiver()方法進(jìn)行注冊,將Network ChangeReceiver的實(shí)例和 IntentFilter的實(shí)例都傳了進(jìn)去,這樣 NetworkChangeReceiver就會收到所有值為 android.net.conn.CONNECTIVITY_CHANGE的廣播,也就實(shí)現(xiàn)了監(jiān)聽網(wǎng)絡(luò)變化的功能。

最后要記得,動態(tài)注冊的廣播接收器一定都要取消注冊才行,這里我們是在onDestroy()方法中通過調(diào)用 unregisterReceiver()方法來實(shí)現(xiàn)的。

另外,這里有非常重要的一點(diǎn)需要說明, Android系統(tǒng)為了保護(hù)用戶設(shè)備的安全和隱私,做了嚴(yán)格的規(guī)定:如果程序需要進(jìn)行一些對用戶來說比較敏感的操作,就必須在配置文件中聲明權(quán)限才可以,否則程序?qū)苯颖罎?。比如這里訪問系統(tǒng)的網(wǎng)絡(luò)狀態(tài)就是需要聲明權(quán)限的。

打開AndroidManifest xml文件,在里面加入如下權(quán)限就可以訪問系統(tǒng)網(wǎng)絡(luò)狀態(tài)了:

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

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest">
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    ......
</manifest>

整體來說,代碼還是非常簡單的,現(xiàn)在運(yùn)行一下程序。首先你會在注冊完成的時候收到一條廣播,然后按下Home鍵回到主界面(注意不能按Back建,否則 onDestroy()方法會執(zhí)行),接著打開WLAN啟動和禁用網(wǎng)絡(luò),你就會看到有 Toast提醒你網(wǎng)絡(luò)發(fā)生了變化。


20190504_220455.gif

2.2.靜態(tài)實(shí)現(xiàn)開機(jī)啟動注冊

動態(tài)注冊的廣播接收器可以自由地控制注冊與注銷,在靈活性方面有很大的優(yōu)勢,但是它也存在著一個缺點(diǎn),即必須要在程序啟動之后才能接收到廣播,因?yàn)樽缘倪壿嬍菍懺趏nCreate()方法中的。那么有沒有什么辦法可以讓程序在未啟動的情況下就能接收到廣播呢?這就需要使用靜態(tài)注冊的方式了。

這里我們準(zhǔn)備讓程序接收一條開機(jī)廣播,當(dāng)收到這條廣播時就可以在onReceive()方法里執(zhí)行相應(yīng)的邏輯,從而實(shí)現(xiàn)開機(jī)啟動的功能??梢允褂?AndroidStudio提供的快捷方式來創(chuàng)建一個廣播接收器


圖片.png

右擊 com. example. broadcasttest包→ New→ Other→Broadcast Receiver


圖片.png

這里我們將廣播接收器命名為 BootCompleteReceiver, Exported屬性表示是否允許這個廣播接收器接收本程序以外的廣播, Enabled屬性表示是否啟用這個廣播接收器。勾選這兩個屬性,點(diǎn)擊 Finish完成創(chuàng)建。

然后修改 BootCompleteReceiver中的代碼,如下所示

public class BootCompleteReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show();
    }
}

代碼非常簡單,我們只是在onReceive()方法中使用 Toast彈出一段提示信息。

另外,靜態(tài)的廣播接收器一定要在AndroidManifest. xml文件中注冊才可以使用,不過由于我們是使用 AndroidStudio的快捷方式創(chuàng)建的廣播接收器,因此注冊這一步已經(jīng)被自動完成了。打開AndroidManifest.xml文件瞧一瞧,代碼如下所示:

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

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
   
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
       ...

        <receiver
            android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true">
        </receiver>
    </application>

</manifest>

可以看到,<application>標(biāo)簽內(nèi)出現(xiàn)了一個新的標(biāo)簽< receiver>,所有靜態(tài)的廣播接收器都是在這里進(jìn)行注冊的。它的用法其實(shí)和< activity>標(biāo)簽非常相似,也是通過 android: name來指定具體注冊哪一個廣播接收器,而 enabled和 exported屬性則是根據(jù)我們剛才勾選的狀態(tài)自動生成的。

不過目前 BootCompleteReceiver還是不能接收到開機(jī)廣播的,我們還需要對 AndroidMainfest.xml文件進(jìn)行修改才行,如下所示:

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

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        ...
        <receiver
            android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

由于 Android系統(tǒng)啟動完成后會發(fā)出一條值為android.intent. action.BOOT_COMPLETED的廣播,因此我們在< intent- filter>標(biāo)簽里添加了相應(yīng)的 action。另外,監(jiān)聽系統(tǒng)開機(jī)廣播也是需要聲明權(quán)限的,可以看到,我們使用<uses-permission> 標(biāo)簽又加入了一條android.permission. RECEIVE_BOOT_COMPLETED權(quán)限。

現(xiàn)在重新運(yùn)行程序后,我們的程序就已經(jīng)可以接收開機(jī)廣播了。將手機(jī)關(guān)閉并重新啟動,在啟動完成之后就會收到開機(jī)廣播。

需要注意的是不要在onReceive()方法中添加過多的邏輯或者進(jìn)行任何的耗時操作,因?yàn)樵趶V播接收器中是不允許開啟線程的,當(dāng)onReceive()方法運(yùn)行了較長時間而沒有結(jié)束時,程序就會報錯。因此廣播接收器更多的是扮演一種打開程序其他組件的角色,比如創(chuàng)建一條狀態(tài)蘭通知,或者啟動一個服務(wù)等。

3. 發(fā)送自定義廣播

3.1.發(fā)送標(biāo)準(zhǔn)廣播

在發(fā)送標(biāo)準(zhǔn)廣播之前,我們需要先定義一個廣播接收器來接收此廣播才行。

新建一個MyBroadcastReceiver
public class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
    }
}

當(dāng)MyBroadcastReceiver 收到自定義的廣播時,就會彈出received in MyBroadcastReceiver的Toast

接下來在AndroidManifest.xml文件中對這個廣播接收器進(jìn)行修改:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        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>

        <receiver
            android:name=".BootCompleteReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

        <receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.broadcasttest.MY_BROADCAST"/>
            </intent-filter>
        </receiver>

    </application>

</manifest>

這里讓MyBroadcastReceiver 接收一條值為com.example.broadcasttest.MY_BROADCAST的廣播,因此待會我們需要發(fā)出這樣一條廣播。

修改activity_main.xml中的代碼
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Send Broadcast" />

</LinearLayout>

加入一個按鈕,用來發(fā)送廣播。

修改MainActivity代碼

在onCreate()函數(shù)中加入按鈕監(jiān)聽代碼

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
                sendBroadcast(intent);
            }
        });

首先構(gòu)建Intent對象,并傳入要發(fā)送的廣播值,接著調(diào)用sendBroadcast()函數(shù)將廣播發(fā)送出去,這樣所有監(jiān)聽com.example.broadcasttest.MY_BROADCAST的廣播接收器就會收到消息。

運(yùn)行程序:


20190507_135937.gif

廣播是一種可以跨進(jìn)程的通信方式,這一點(diǎn)從前面的接收系統(tǒng)廣播的時候就可以看出來。因此在我們應(yīng)用程序內(nèi)發(fā)出的廣播,其他程序應(yīng)該也是可以收到的。為了驗(yàn)證這一點(diǎn),我們需要再創(chuàng)建一個BroadcastTest2項(xiàng)目。

在此項(xiàng)目中自定一個廣播接收器AnotherBroadcastReceiver
public class AnotherBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "received in AnotherBroadcastReceiver", Toast.LENGTH_SHORT).show();
    }
}
在AndroidManifest.xml文件中對這個廣播接收器進(jìn)行修改
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.broadcasttest2">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        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>

        <receiver
            android:name=".AnotherBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.broadcasttest.MY_BROADCAST" />
            </intent-filter>
        </receiver>
        
    </application>
</manifest>

這里接收的值和前面發(fā)送標(biāo)準(zhǔn)廣播中的廣播值一樣
運(yùn)行BroadcastTest2項(xiàng)目至手機(jī),并在BroadcastTest項(xiàng)目中點(diǎn)擊按鈕,會發(fā)現(xiàn)分別彈出兩次提示信息。


20190507_142819.gif

驗(yàn)證了我們的應(yīng)用程序發(fā)出的廣播是可以被其他的應(yīng)用程序接收到的。
不過到目前為止,我們發(fā)出的廣播都還是標(biāo)準(zhǔn)廣播,接下來我們嘗試一下有序廣播。

3.2.發(fā)送有序廣播

回到BroadcastTest項(xiàng)目

修改MainActivity中的代碼
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST");
                sendOrderedBroadcast(intent,null);
            }
        });

這里我們只改動了一行代碼,將sendBroadcast()方法改成了sendOrderedBroadcast()方法。
重新運(yùn)行程序會發(fā)現(xiàn)兩個程序還是會接收到這條廣播的。不過這時的廣播接收器是有先后順序的,并且前面的廣播接收器還可以將廣播進(jìn)行截斷,阻止其繼續(xù)傳播。

至于廣播接收器的先后順序,這是在注冊的時候進(jìn)行設(shè)定的,修改AndroidManifest.xml文件


圖片.png

這里通過android:priority屬性給廣播接收器設(shè)置了優(yōu)先級,優(yōu)先級比較高的接收器就可以先收到廣播,這里設(shè)成100,以保證其在AnotherBroadcastReceiver之前收到廣播。

接下來就可以在優(yōu)先級高的廣播接收器MyBroadcastReceiver中決定是否允許廣播繼續(xù)傳播了。

public class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
        abortBroadcast();
    }
}

調(diào)用abortBroadcast()用于將廣播截斷。

重新運(yùn)行程序


20190507_150108.gif

只有MyBroadcastReceiver 中的Toast彈出,說明這條廣播經(jīng)過MyBroadcastReceiver 之后確實(shí)終止傳遞了。

4. 使用本地廣播

前面所學(xué)的廣播都是全局廣播,這樣有個問題就是容易引起安全問題,能不能使發(fā)出的廣播只能在應(yīng)用程序內(nèi)傳遞而不被其他應(yīng)用程序截獲?這時就需要一個LocalBroadcastManager來對廣播進(jìn)行管理。

修改MainActivity中的代碼
public class MainActivity extends AppCompatActivity {

    private IntentFilter intentFilter;
    private LocalReceiver localReceiver;
    private LocalBroadcastManager localBroadcastManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        localBroadcastManager = LocalBroadcastManager.getInstance(this); // 獲取實(shí)例
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
                localBroadcastManager.sendBroadcast(intent); // 發(fā)送本地廣播
            }
        });
        intentFilter = new IntentFilter();
        intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
        localReceiver = new LocalReceiver();
        localBroadcastManager.registerReceiver(localReceiver, intentFilter); // 注冊本地廣播監(jiān)聽器
    }

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

    class LocalReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
        }
    }
}

首先通過LocalBroadcastManager 的getInstance()方法獲取一個實(shí)例
然后注冊廣播接收器的時候調(diào)用LocalBroadcastManager 的registerReceiver()方法
發(fā)送廣播的時候調(diào)用的是LocalBroadcastManager 的sendBroadcast()方法

這里我們在按鈕點(diǎn)擊事件里發(fā)出一條com.example.broadcasttest.LOCAL_BROADCAST廣播,然后在LocalReceiver 里接收這條廣播。

重新運(yùn)行程序


20190507_152625.gif

注:本地廣播無法通過靜態(tài)注冊的方式來接收

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容