廣播接收器(BroadcastReceiver)

  • 現(xiàn)實(shí)中的廣播:電臺(tái)為了傳達(dá)一些消息而發(fā)送廣播,通過(guò)廣播攜帶要傳達(dá)的消息,群眾只要買一個(gè)收音機(jī),就可以收到廣播了。

  • Android中的廣播:Android系統(tǒng)在運(yùn)行過(guò)程中,會(huì)發(fā)生很多事件,比如電量改變、耳機(jī)插入、收到短信、撥打電話、屏幕解鎖、系統(tǒng)開(kāi)機(jī)等,為了讓App知道事件的發(fā)生,系統(tǒng)會(huì)發(fā)送該事件的廣播,App只要注冊(cè)一個(gè)BroadcastReceiver,就可以接收到對(duì)應(yīng)的廣播,以便做出響應(yīng)。

在Android系統(tǒng)中,廣播是進(jìn)行進(jìn)程間通信(IPC)的重要手段,所以Android系統(tǒng)為我們提供了BroadcastReceiver來(lái)接收程序(包括我們開(kāi)發(fā)的程序和系統(tǒng)內(nèi)建的程序)所發(fā)出的廣播(實(shí)際上是Broadcast Intent)

本文所提到的廣播主要是指全局廣播,而對(duì)于進(jìn)程內(nèi)通信,建議使用局部廣播 LocalBroadcastManger

各種OnXxxListener只是程序級(jí)別的監(jiān)聽(tīng)器,這些監(jiān)聽(tīng)器運(yùn)行在指定程序所在進(jìn)程中,當(dāng)程序退出時(shí),OnXxxListener監(jiān)聽(tīng)器也隨之關(guān)閉;而B(niǎo)roadcastReceiver屬于系統(tǒng)級(jí)的監(jiān)聽(tīng)器,在Android 4.0以前,對(duì)于靜態(tài)注冊(cè)的BroadcastReceiver,只要存在與之匹配的Intent被廣播出來(lái)就會(huì)被觸發(fā)它,即便它所在的應(yīng)用程序還沒(méi)有啟動(dòng),系統(tǒng)也會(huì)啟動(dòng)這個(gè)應(yīng)用程序

廣播接收器的創(chuàng)建

1. 定義

創(chuàng)建一個(gè)類XxxReceiver繼承于BroadcastReceiver,并重寫(xiě)onReceive()

public class XxxReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {

    }
}
2. 注冊(cè)
  • 靜態(tài)注冊(cè)(常駐)

    靜態(tài)注冊(cè)就是在AndroidManifest中注冊(cè)BroadcastReceiver,并指定它所接收的廣播種類,如下面配置的XxxReceiver用來(lái)接收開(kāi)機(jī)廣播

    <receiver android:name=".XxxReceiver">
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>
    

使用靜態(tài)注冊(cè)的BroadcastReceiver,在Android 4.0以前,只要app被安裝,它就會(huì)一直生效;而Android 4.0之后,在app安裝后還需要先啟動(dòng)一次,它才會(huì)生效,并且如果用戶在應(yīng)用管理界面手動(dòng)殺死了這個(gè)BroadcastReceiver所在進(jìn)程,那么它將不在生效,直到用戶下一次啟動(dòng)app,才會(huì)再次生效。但是如果是系統(tǒng)在內(nèi)存不足時(shí)自動(dòng)殺死了這個(gè)BroadcastReceiver所在進(jìn)程,它仍然還是生效的。
BroadcastReceiver一旦生效,每次與之匹配的Intent被廣播出來(lái),系統(tǒng)就會(huì)創(chuàng)建對(duì)應(yīng)的BroadcastReceiver實(shí)例,并自動(dòng)觸發(fā)它的onReceive()方法,onReceive()方法執(zhí)行完后,BroadcastReceiver實(shí)例就會(huì)被銷毀(生命周期結(jié)束)。

  • 動(dòng)態(tài)注冊(cè)(非常駐)

    動(dòng)態(tài)注冊(cè)是指在Java代碼中注冊(cè)BroadcastReceiver,并通過(guò)IntentFilter來(lái)指定它接收的廣播種類。
    使用動(dòng)態(tài)注冊(cè)BroadcastReceiver,通常是在onResume()中使用registerReceiver(xxxReceiver, intentFilter)注冊(cè)它,在onPause()使用unregisterReceiver(xxxReceiver)注銷它,注銷之后BroadcastReceiver立即失效,這樣可以有效的節(jié)約系統(tǒng)消耗。下面代碼動(dòng)態(tài)注冊(cè)的XxxReceiver用于接收屏幕開(kāi)關(guān)廣播:

    @Override
    protected void onResume() {
        super.onResume();
        receiver = new XxxReceiver(); 
        IntentFilter intentFilter = new IntentFilter();   // 用于指定接收廣播的類型
        intentFilter.addAction(Intent.ACTION_SCREEN_ON);  // 屏幕點(diǎn)亮
        intentFilter.addAction(Intent.ACTION_SCREEN_OFF); // 屏幕熄滅
        registerReceiver(receiver, intentFilter);  // 注冊(cè)廣播接收器
    }
    
    @Override
    protected void onPause() {
        unregisterReceiver(receiver);  // 注銷廣播接收器
        super.onPause();
    }
    

    有些特殊的廣播,必須使用動(dòng)態(tài)注冊(cè)的BroadcastReceiver來(lái)接收,比如:

    • 屏幕開(kāi)關(guān)
    • 電量改變
    • 耳機(jī)插拔

    如果一個(gè)BroadcastReceiver用于更新UI,那么通常會(huì)使用動(dòng)態(tài)注冊(cè)。

接收系統(tǒng)發(fā)出的廣播

IP撥號(hào)器

撥打電話時(shí),系統(tǒng)會(huì)發(fā)出一個(gè)廣播,廣播中攜帶著用戶所要撥打的號(hào)碼。
我們可以創(chuàng)建一個(gè)BroadcastReceiver接收這個(gè)廣播,然后取出廣播中攜帶的號(hào)碼進(jìn)行修改(這里是加上線路號(hào)碼),然后把修改后的號(hào)碼放回廣播。

在IP撥號(hào)器中定義廣播接收器接收打電話廣播

public class CallReceiver extends BroadcastReceiver {
    
    /**
     * 當(dāng)廣播接收器接收到廣播時(shí),此方法會(huì)調(diào)用
     * @param context
     * @param intent
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        String number = getResultData();  // 拿到用戶撥打的號(hào)碼
        setResultData("17951" + number);  // 修改廣播內(nèi)的號(hào)碼
    }
}

在清單文件中靜態(tài)注冊(cè)該receiver并定義它接收的廣播類型

<receiver android:name=".CallReceiver">
    <intent-filter >
        <action android:name="android.intent.action.NEW_OUTGOING_CALL" />
    </intent-filter>
</receiver>

不要忘了申請(qǐng)接收打電話廣播所需要的權(quán)限

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

短信攔截器

Android系統(tǒng)在收到短信時(shí)會(huì)發(fā)送一條廣播,廣播中攜帶著短信的源號(hào)碼和內(nèi)容。
我們可以寫(xiě)一個(gè)短信攔截器app,在程序中定義一個(gè)BroadcastReceiver,并使其優(yōu)先級(jí)高于系統(tǒng)短信應(yīng)用的BroadcastReceiver優(yōu)先級(jí),這樣我們的app會(huì)先一步收到短信廣播,然后攔截廣播,使短信應(yīng)用收不到短信廣播,用戶也就看不到被攔截的短信了。

在短信攔截器中定義廣播接收器接收短信廣播

public class SmsBlockReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        Bundle bundle = intent.getExtras(); // 從廣播中取出短信
        Object[] objects = (Object[]) bundle.get("pdus");// 如果對(duì)方發(fā)來(lái)的短信內(nèi)容過(guò)長(zhǎng),短信會(huì)被拆分成多條,所以這里用的是數(shù)組
        // PDU(Protocol Data Unit)即協(xié)議數(shù)據(jù)單元
        for (Object object : objects) { // 數(shù)組中的每一個(gè)元素,就是一條短信
            SmsMessage sms = SmsMessage.createFromPdu((byte[]) object); // 把數(shù)組中的元素轉(zhuǎn)換成短信對(duì)象
            String number = sms.getOriginatingAddress();    // 獲取對(duì)方(源)號(hào)碼
            String content = sms.getMessageBody();          // 獲取短信內(nèi)容
            System.out.println(number + ":" + content);
            if ("13888888888".equals(number)) {
                abortBroadcast();   // 阻止其他廣播接收器接受該廣播,即攔截13888888888發(fā)來(lái)的短信
            }
        }
    }
}

然后在清單文件中配置BroadcastReceiver接收的廣播類型,注意要設(shè)置優(yōu)先級(jí)(設(shè)置為1000即高于系統(tǒng)應(yīng)用的BroadcastReceiver優(yōu)先級(jí)),保證我們app的BroadcastReceiver優(yōu)先級(jí)高于短信應(yīng)用的BroadcastReceiver優(yōu)先級(jí),才能實(shí)現(xiàn)短信攔截

<receiver android:name=".SmsBlockReceiver">
    <intent-filter android:priority="1000">
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter>
</receiver>

接收短信廣播同樣也需要申請(qǐng)權(quán)限

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

SD卡狀態(tài)偵聽(tīng)

首先定義廣播接收器

public class SdcardReceiver extends BroadcastReceiver {
    
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction(); // 取出Broadcast Intent中的action,判斷收到的是哪一個(gè)廣播
        if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
            Toast.makeText(context, "SD卡就緒", Toast.LENGTH_SHORT).show();
        } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
            Toast.makeText(context, "SD卡被卸載", Toast.LENGTH_SHORT).show();
        } else if (action.equals(Intent.ACTION_MEDIA_REMOVED)) {
            Toast.makeText(context, "SD卡被拔出", Toast.LENGTH_SHORT).show();
        }
    }
}

然后在清單文件中注冊(cè)這個(gè)廣播接收器,并指定它所接收的廣播類型。
一個(gè)廣播接收器可以接收多種廣播,只需要在intent-filter標(biāo)簽下定義多個(gè)action即可

<receiver android:name=".SdcardReceiver">
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_MOUNTED"/>    <!-- SD卡就緒 -->
        <action android:name="android.intent.action.MEDIA_UNMOUNTED"/>  <!-- SD卡被卸載(系統(tǒng)設(shè)置中卸載) -->
        <action android:name="android.intent.action.MEDIA_REMOVED"/>    <!-- SD卡被拔出(物理插拔) -->
        <data android:scheme="file"/>   <!-- 廣播中攜帶著以file為前綴的SD卡路徑,添加這個(gè)data項(xiàng)才能匹配 -->
    </intent-filter>
</receiver>

勒索app

寫(xiě)一個(gè)勒索app(僅供學(xué)習(xí)),使其具有下面的功能:

  • Back鍵無(wú)效:重寫(xiě)onBackPressed()使其不調(diào)用finish().
  • Home鍵無(wú)效:通過(guò)監(jiān)控Task棧,如果Task棧頂不是我們勒索app的Activity,就啟動(dòng)這個(gè)Activity.
  • 開(kāi)機(jī)自啟:在勒索app中定義接收開(kāi)機(jī)廣播的BroadcastReceiver,并在它的onReceive()中啟動(dòng)勒索app的Activity,這里主要介紹的就是這個(gè)功能的實(shí)現(xiàn)。

在勒索app中定義廣播接收器接收開(kāi)機(jī)廣播

public class BootReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // 當(dāng)接收到開(kāi)機(jī)廣播,啟動(dòng)勒索軟件MainActivity
        Intent intent1 = new Intent(context, MainActivity.class);
        // intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent1);
    }
}

以上代碼還不能啟動(dòng)MainActivity。使用標(biāo)準(zhǔn)啟動(dòng)模式啟動(dòng)Activity,Activity默認(rèn)會(huì)進(jìn)入啟動(dòng)它的組件所屬的Task棧中,廣播接收器(Activity Context之外)并沒(méi)有Task棧,也就無(wú)法啟動(dòng)Activity,解決的方法就是要為待啟動(dòng)的Activity指定FLAG_ACTIVITY_NEW_TASK標(biāo)記位,這樣啟動(dòng)的時(shí)候就會(huì)為它創(chuàng)建一個(gè)新的Task棧

intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

在清單文件中注冊(cè)接收開(kāi)機(jī)廣播的廣播接收器

<receiver android:name=".BootReceiver">
    <intent-filter >
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
</receiver>

申請(qǐng)權(quán)限

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

發(fā)送廣播

廣播是通過(guò)Intent發(fā)送的,給Intent設(shè)置一個(gè)action,然后調(diào)用下面三種方法即可發(fā)送我們自己的廣播:

  1. sendBroadcast():最普通的發(fā)送intent的方式,是一種無(wú)序的廣播機(jī)制,理論上,所有的接收器同時(shí)獲得該intent的消息,接受器之間不存在先后順序,不能截?cái)?修改intent的數(shù)據(jù)。

  2. sendOrderedBroadcast():有序的發(fā)送廣播的機(jī)制,所有接受器可以設(shè)置priority ,按照priority的大小順序進(jìn)行傳遞,上一個(gè)優(yōu)先級(jí)的接受器可以截?cái)嗪托薷膇ntent里面的數(shù)據(jù)。同時(shí),也可以設(shè)置一個(gè)結(jié)果接收器(總是在最后一個(gè)接收到這個(gè)intent,用來(lái)實(shí)現(xiàn)一些特定的功能)。

  3. sendStickyBroadcast():是一種粘性廣播。所謂的粘性是指,這個(gè)intent沒(méi)有周期限制,一般廣播只能將intent發(fā)送給當(dāng)前已經(jīng)注冊(cè)了的BroadcastReceiver,一旦發(fā)送完畢就失去作用,而粘性廣播沒(méi)有這個(gè)限制,即便后來(lái)注冊(cè)的BroadcastReceiver也可以收到這個(gè)廣播。從android 5.0開(kāi)始,出于安全性的考慮,官方已經(jīng)正式廢棄了粘性廣播

public void sendMyBroadcast(View v) {
    Intent intent = new Intent();
    intent.setAction("a.b.c");  // a.b.c是我們自己的action
    sendBroadcast(intent);      // 發(fā)送普通廣播
}

無(wú)序廣播(普通廣播)和有序廣播

  • 對(duì)于無(wú)序廣播(普通廣播),所有與廣播Intent匹配的BroadcastReceiver,都可以收到這條廣播,并且不分先后順序,視為同時(shí)收到,上面的sendBroadcast()發(fā)送的就是無(wú)序廣播。這種廣播的效率比較高,但缺點(diǎn)是接收器不能將處理結(jié)果傳遞給下一個(gè)接收器,并且無(wú)法在中途終止廣播。

  • 對(duì)于有序廣播,所有與廣播Intent匹配的BroadcastReceiver,不一定都會(huì)收到這條廣播,因?yàn)橛行驈V播的接收是分先后順序的,優(yōu)先級(jí)高的先收到,優(yōu)先級(jí)低的后收到,通過(guò)sendOrderedBroadcast()可以發(fā)送有序廣播。

    • 對(duì)于靜態(tài)注冊(cè)的BroadcastReceiver,優(yōu)先級(jí)聲明在<intent-filter.../>標(biāo)簽內(nèi)的android:priority屬性中;對(duì)于動(dòng)態(tài)注冊(cè)的BroadcastReceiver,通過(guò)調(diào)用IntentFilter對(duì)象的setPriority()也可以設(shè)置優(yōu)先級(jí)。優(yōu)先級(jí)用一個(gè)整數(shù)來(lái)表示,值越大代表優(yōu)先級(jí)越高,取值范圍為-1000~1000
    • abortBroadCast():終止廣播,類似攔截,只有有序廣播可以被攔截(Andorid 4.4之后只有用戶設(shè)置的默認(rèn)短信應(yīng)用調(diào)用這個(gè)方法可以攔截短信廣播)
    • 優(yōu)先接收到廣播的接收器可以通過(guò)setResultXxx()修改廣播內(nèi)容,然后下一個(gè)接收器通過(guò)相應(yīng)的getResultXxx()獲取上一個(gè)接收器修改后的數(shù)據(jù)
    • 結(jié)果接收器resultReceiver:在通過(guò)sendOrderedBroadcast()發(fā)送有序廣播時(shí)可以在第三個(gè)參數(shù)處指定這條有序廣播的結(jié)果接收器,在這種情況下,當(dāng)所有匹配的BroadcastReceiver都接收到該有序廣播后,結(jié)果接收器才會(huì)收到,并且一定會(huì)收到該有序廣播(即使使用abortBroadCast()攔截也會(huì)收到)。在前面IP撥號(hào)器的例子中,打電話應(yīng)用中的BroadcastReceiver就是一個(gè)結(jié)果接收器,所以我們的IP撥號(hào)器中的BroadcastReceiver即使沒(méi)設(shè)置優(yōu)先級(jí)也會(huì)在打電話應(yīng)用之前收到撥號(hào)廣播,且打電話應(yīng)用不能被攔截

BroadcastReceiver和Service

如果BroadcastReceiver的onReceive()方法不能在5s內(nèi)執(zhí)行完成,就會(huì)拋出ANR,所以不能在此方法中執(zhí)行一些耗時(shí)的操作。

如果確實(shí)需要根據(jù)Broadcast來(lái)完成一項(xiàng)比較耗時(shí)的操作,則可以考慮在onReceive()中啟動(dòng)一個(gè)IntentService來(lái)完成該操作。

不應(yīng)考慮在onReceive()中啟動(dòng)新線程去完成耗時(shí)操作,因?yàn)锽roadcastReceiver本身的生命周期很短(靜態(tài)注冊(cè)下當(dāng)onReceive()執(zhí)行完生命周期即結(jié)束),很可能出現(xiàn)的情況是子線程還沒(méi)有執(zhí)行結(jié)束,BroadcastReceiver就已經(jīng)被銷毀了,此時(shí)應(yīng)用進(jìn)程可能由于不含有任何活動(dòng)的應(yīng)用組件而變?yōu)榭者M(jìn)程,Android系統(tǒng)在內(nèi)存緊張時(shí)會(huì)優(yōu)先結(jié)束空進(jìn)程,從而導(dǎo)致執(zhí)行耗時(shí)操作的子線程不能執(zhí)行完成。

拋出ANR的條件

對(duì)于Activity、BroadcastReceiver、Service這三大組件,如果在主線程(UI線程)中進(jìn)行耗時(shí)操作,都有可能導(dǎo)致應(yīng)用拋出ANR(Application Not Responding)異常,下面給出在這三大組件中拋出ANR的條件,源碼基于Nougat - 7.1.1_r6

  1. 對(duì)于Activity,如果在主線程中執(zhí)行耗時(shí)操作,并且從用戶按鍵或觸摸屏幕開(kāi)始算起5s內(nèi)耗時(shí)操作仍然未執(zhí)行完,就會(huì)拋出ANR,這類ANR會(huì)有提示框彈出,用戶可以選擇force close或者繼續(xù)等待。對(duì)應(yīng)ActivityManagerService.java源碼:

    // How long we wait until we timeout on key dispatching.
    static final int KEY_DISPATCHING_TIMEOUT = 5 * 1000;
    
    // How long we wait until we timeout on key dispatching during instrumentation.
    static final int INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT = 60 * 1000;
    
  2. 對(duì)于BroadcastReceiver的onReceive(),如果在主線程中執(zhí)行耗時(shí)操作,并且在60s內(nèi)(默認(rèn)后臺(tái)隊(duì)列廣播)耗時(shí)操作仍然未執(zhí)行完,就會(huì)拋出ANR,這類ANR沒(méi)有提示框彈出。對(duì)應(yīng)ActivityManagerService.java源碼:

    // How long we allow a receiver to run before giving up on it.
    static final int BROADCAST_FG_TIMEOUT = 10 * 1000;
    static final int BROADCAST_BG_TIMEOUT = 60 * 1000;
    

    關(guān)于前臺(tái)隊(duì)列廣播和后臺(tái)隊(duì)列廣播的區(qū)別,詳見(jiàn)Android廣播機(jī)制——廣播的發(fā)送以及說(shuō)說(shuō)Android的廣播(4) - 前臺(tái)廣播為什么比后臺(tái)廣播快?

  3. 對(duì)于前臺(tái)Service,如果在主線程中執(zhí)行耗時(shí)操作,并且在20s內(nèi)耗時(shí)操作仍然未執(zhí)行完,就會(huì)拋出ANR,這類ANR同樣沒(méi)有提示框彈出。對(duì)應(yīng)ActiveServices.java源碼:

    // How long we wait for a service to finish executing.
    static final int SERVICE_TIMEOUT = 20 * 1000;
    
    // How long we wait for a service to finish executing.
    static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10;
    

關(guān)于ANR問(wèn)題的詳細(xì)分析,這里有一篇不錯(cuò)的文章,ANR問(wèn)題分析指南

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

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

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