Android Broadcast

參考承香墨影的兩篇博客
Android--廣播BroadcastReceiver
Android--攔截系統(tǒng)BroadcastReceiver

一、什么是BroadcastReceiver?

BroadcastReceiver,廣播接收者,它是一個系統(tǒng)全局的監(jiān)聽器,用于監(jiān)聽系統(tǒng)全局的Broadcast消息,所以它可以很方便的進行系統(tǒng)組件之間的通信。
  BroadcastReceiver雖然是一個監(jiān)聽器,但是它和之前用到的OnXxxListener不同,那些只是程序級別的監(jiān)聽器,運行在指定程序的所在進程中,當(dāng)程序退出的時候,OnXxxListener監(jiān)聽器也就隨之關(guān)閉了,但是BroadcastReceiver屬于系統(tǒng)級的監(jiān)聽器,它擁有自己的進程,只要存在與之匹配的Broadcast被以Intent的形式發(fā)送出來,BroadcastReceiver就會被激活。
  雖然同屬Android的四大組件,BroadcastReceiver也有自己獨立的聲明周期,但是和Activity、Service又不同。當(dāng)在系統(tǒng)注冊一個BroadcastReceiver之后,每次系統(tǒng)以一個Intent的形式發(fā)布Broadcast的時候,系統(tǒng)都會創(chuàng)建與之對應(yīng)的BroadcastReceiver廣播接收者實例,并自動觸發(fā)它的onReceive()方法,當(dāng)onReceive()方法被執(zhí)行完成之后,BroadcastReceiver的實例就會被銷毀。雖然它獨自享用一個單獨的進程,但也不是沒有限制的,如果BroadcastReceiver.onReceive()方法不能在10秒內(nèi)執(zhí)行完成,Android系統(tǒng)就會認(rèn)為該BroadcastReceiver對象無響應(yīng),然后彈出ANR(Application No Response)對話框,所以不要在BroadcastReceiver.onReceive()方法內(nèi)執(zhí)行一些耗時的操作。
  如果需要根據(jù)廣播內(nèi)容完成一些耗時的操作,一般考慮通過Intent啟動一個Service來完成該操作,而不應(yīng)該在BroadcastReceiver中開啟一個新線程完成耗時的操作,因為BroadcastReceiver本身的生命周期很短,可能出現(xiàn)的情況是子線程還沒有結(jié)束,BroadcastReceiver就已經(jīng)退出的情況,而如果BroadcastReceiver所在的進程結(jié)束了,該線程就會被標(biāo)記為一個空線程,根據(jù)Android的內(nèi)存管理策略,在系統(tǒng)內(nèi)存緊張的時候,會按照優(yōu)先級,結(jié)束優(yōu)先級低的線程,而空線程無異是優(yōu)先級最低的,這樣就可能導(dǎo)致BroadcastReceiver啟動的子線程不能執(zhí)行完成。

二、BroadcastReceiver的種類

上面提到,當(dāng)系統(tǒng)以一個Intent的形式發(fā)送一個Broadcast出去之后,所有與之匹配的BroadcastReceiver都會被實例化,但是這里是有區(qū)別的,根據(jù)Broadcast的傳播方式區(qū)別,在系統(tǒng)中有如下兩種Broadcast:

  • 普通廣播
    Normal Broadcast,它是完全異步的,也就是說,在邏輯上,當(dāng)一個Broadcast被發(fā)出之后,所有的與之匹配的BroadcastReceiver都同時接收到Broadcast。優(yōu)點是傳遞效率比較高,但是也有缺點,就是一個BroadcastReceiver不能影響其他響應(yīng)這條Broadcast的BroadcastReceiver。
  • 有序廣播
    Ordered Broadcast,它是同步執(zhí)行的,也就是說有序廣播的接收器將會按照預(yù)先聲明的優(yōu)先級依次接受Broadcast,是鏈?zhǔn)浇Y(jié)構(gòu),優(yōu)先級越高(-1000~1000),越先被執(zhí)行。因為是順序執(zhí)行,所有優(yōu)先級高的接收器,可以把執(zhí)行結(jié)果傳入下一個接收器中,也可以終止Broadcast的傳播(通過abortBroadcast()方法),一旦Broadcast的傳播被終止,優(yōu)先級低于它的接收器就不會再接收到這條Broadcast了。

雖然系統(tǒng)存在兩種類型的Broadcast,但是一般系統(tǒng)發(fā)送出來的Broadcast均是有序廣播,所以可以通過優(yōu)先級的控制,在系統(tǒng)內(nèi)置的程序響應(yīng)前,對Broadcast提前進行響應(yīng)。這就是市場上一些攔截器類(如:短信攔截器、電話攔截器)的軟件的原理。

三、如何發(fā)送一個廣播

上面已經(jīng)介紹了系統(tǒng)中兩種不同的Broadcast,而根據(jù)Broadcast傳播的方式,Context提供了不同的方法來發(fā)布它們:
sendBroadcast():發(fā)送普通廣播。
sendOrderedBroadcast():發(fā)送有序廣播。
  以上兩個方法都有多個重載方法,根據(jù)不同的場景使用,最簡單的莫過于直接傳遞一個Intent來發(fā)送一個廣播。
<pre>
Intent intent = new Intent("com.example.boadcasttest.MY_BROADCAST");
sendBroadcast(intent);
</pre>

四、如何使用BroadcastReceiver

BroadcastReceiver本質(zhì)上還是一個監(jiān)聽器,所以使用BroadcastReceiver的方法也是非常簡單,只需要繼承BroadcastReceiver,在其中重寫onReceive(Context context,Intent intent)即可。一旦實現(xiàn)了BroadcastReceiver,并部署到系統(tǒng)中后,就可以在系統(tǒng)的任何位置,通過sendBroadcast、sendOrderedBroadcast方法發(fā)送Broadcast給這個BroadcastReceiver。
  但是僅僅繼承BroadcastReceiver和實現(xiàn)onReceive()方法是不夠的,同為Android系統(tǒng)組件,它也必須在Android系統(tǒng)中注冊,注冊一個BroadcastReceiver有兩種方式:

  • 動態(tài)注冊
    在代碼中使用Content.registerReceiver(BroadcastReceiver receiver, IntentFilter filter)進行注冊,在使用完畢使用Content.unregisterReceiver(BroadcastReceiver receiver)方法進行注銷。
    <pre>
    public class MainActivity extends Activity{
    private IntentFilter intentFilter;
    private NeworkChangeReceiver networkChangeReceiver;

    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 NeworkChangeReceiver();
    registerReceiver(networkChangeReceiver ,intentFilter);
    }

    protected void onDestroy(){
    super.onDestroy();
    unregisterReceiver(networkChangeReceiver);
    }

    class NeworkChangeReceiver extends BroadcastReceiver{
    public void onReceive(Context context,Intent intent){
    ConnectivityManger connectivityManger = new (ConnectivityManger) getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connectivityManger.getActiveNetworkInfo();
    if(networkInfo != null && networkInfo .isAvailable()){
    Toast.makeText(context,"net is available,Toast.LENGTH_SHORT").show();
    }else{
    Toast.makeText(context,"net is unavailable,Toast.LENGTH_SHORT").show();
    }
    }
    }
    }

</pre>
注意在AndroidMainfest.xml聲明查詢系統(tǒng)網(wǎng)絡(luò)狀態(tài)的權(quán)限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

  • 靜態(tài)注冊
    使用清單文件AndroidManifest.xml注冊,在<application/>節(jié)點中,使用<receiver/>節(jié)點注冊,并用android:name屬性中指定注冊的BroadcastReceiver對象,一般還會通過<Intent-filter/>指定<action/>和<category/>,并在<Intent-filter/>節(jié)點中通過android:priority屬性設(shè)置BroadcastReceiver的優(yōu)先級,在-1000~1000范圍內(nèi),數(shù)值越到優(yōu)先級越高。
      雖然Android系統(tǒng)提供了兩種方式注冊BroadcastReceiver,但動態(tài)注冊必須要在程序啟動之后才能接收廣播,如果想在程序未啟動情況下就接收廣播,就只能使用靜態(tài)注冊了。一般在實際開發(fā)中,還是會使用清單文件進行靜態(tài)注冊:
    <pre>
    <receiver android:name="cn.bgxt.Broadcastdemo.Basic.BasicBroadcast">
    <intent-filter android:priority="100">
    <action android:name="cn.bgxt.Broadcastdemo.Basic.broadcast"/>
    </intent-filter>
    </receiver>
    </pre>
    下面通過一個簡單的示例,講解一下BroadcastReceiver的聲明,以及如何向這個BroadcastReceiver發(fā)送消息。
      首先先聲明一個BroadcastReceiver,BasicBroadcast.java:
    <pre>
    package cn.bgxt.Broadcastdemo.Basic;
    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    import android.widget.Toast;
    public class BasicBroadcast extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
    Toast.makeText(context,
    "接收到Broadcast,消息為:" + intent.getStringExtra("msg"),
    Toast.LENGTH_SHORT).show();
    }
    }
    </pre>

再聲明一個Activity,用于發(fā)送Broadcast:BasicActivity.java:
<pre>
package cn.bgxt.Broadcastdemo.Basic;

import com.bgxt.datatimepickerdemo.R;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class BasicActivity extends Activity {
Button btnBasicSendNormal, btnBasicSendOrdered;

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

    btnBasicSendNormal = (Button) findViewById(R.id.btnBasicSendNormal);
    btnBasicSendOrdered = (Button) findViewById(R.id.btnBasicSendOrdered);
    btnBasicSendNormal.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            Intent broadcast=new Intent();
            broadcast.setAction("cn.bgxt.Broadcastdemo.Basic.broadcast");
            broadcast.putExtra("msg", "這是一個普通廣播");
            sendBroadcast(broadcast);
        }
    });

    btnBasicSendOrdered.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            Intent broadcast=new Intent();
            broadcast.setAction("cn.bgxt.Broadcastdemo.Basic.broadcast");
            broadcast.putExtra("msg", "這是一個有序廣播");
            sendOrderedBroadcast(broadcast, null);
        }
    });
}

}
</pre>

在實際開發(fā)當(dāng)中,大部分情況下是不需要自己發(fā)布一個Broadcast或者接收自己定義的Broadcast的,一般而言,都是攔截系統(tǒng)在做某個操作而發(fā)布的Broadcast,對其進行相應(yīng)的處理。

五、系統(tǒng)中的廣播

在Android系統(tǒng)中,內(nèi)置了很多Action常量,在觸發(fā)這些Action的時候,均會發(fā)布相應(yīng)的Broadcast。一般而言,查看Android的API文檔中,關(guān)于Intent的說明即可找到對應(yīng)Action的Broadcast,但是列舉的還不是很全,最好還是下載Android的源代碼,通過查看源代碼的方式查看需要攔截的Broadcast。下面列舉一些常用的廣播:

  • android.intent.action.TIME_SET:系統(tǒng)時間被修改。
  • android.intent.action.DATE_CHANGED:系統(tǒng)日期被修改。
  • android.intent.action.BOOT_COMPLETED:系統(tǒng)啟動完成。
  • android.intent.action.BATTERY_CHANGED:設(shè)備電量改變。
  • android.intent.action.BATTERY_LOW:設(shè)備電量低。
  • android.intent.action.ACTION_POWER_CONNECTED:設(shè)備連接電源。
  • android.intent.action.ACTION_POWER_DISCONNECTED:設(shè)備斷開電源。
  • android.provider.Telephony.SMS_RECEIVED:系統(tǒng)收到短信。
  • android.intent.action.NEW_OUTGOING_CALL:撥打電話。

下面通過兩個例子,來講解如何在Android下,攔截系統(tǒng)Broadcast并對其進行處理。

1.通過關(guān)鍵字?jǐn)r截短信

從上面列舉的一些動作會發(fā)布的Broadcast,可以找到,當(dāng)系統(tǒng)接收到一條短信的時候,會發(fā)布一個“android.provider.Telephony.SMS_RECEIVED”的Broadcast,之前已經(jīng)介紹過了,一般系統(tǒng)Broadcast都是有序廣播,如果不被高優(yōu)先級的BroadcastReceiver停止傳遞,會按照優(yōu)先級順序傳遞下去。
  而在這個示例中,通過監(jiān)聽接收短信的廣播,當(dāng)其內(nèi)容有黑名單中的關(guān)鍵字的話,則阻止Broadcast繼續(xù)傳播,并使用Toast提示,否則正常提示短信信息。
  通過上一篇博客了解到,onReceive方法的Intent參數(shù)包含了這條廣播傳遞的參數(shù),對于短信信息而言,需要獲取key為"pdus"的數(shù)組,取出數(shù)組中每一項,它的每一項代表了一個byte[]格式的短信,需要使用SmsMessage類解析短信內(nèi)容。
  當(dāng)然,攔截短信的Broadcast侵犯了隱私,需要注冊接收短信的權(quán)限:
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
  下面直接展示源代碼了,關(guān)鍵注釋已經(jīng)寫的很清楚了,這里不再累述:
  MessageBroadcast.java:
<pre>
package cn.bgxt.Broadcastdemo.MessageWarn;

import java.text.SimpleDateFormat;
import java.util.Date;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.widget.Toast;

public class MessageBroadcast extends BroadcastReceiver {
// 在模擬器上,通過DDMS發(fā)送短信會產(chǎn)生亂碼,所以使用拼音代替
//在真機上不存在亂碼的問題
private final String[] blackKeyWord = new String[] { "baoxian", "chuxiao",
"jiangjia" };

@Override
public void onReceive(Context context, Intent intent) {
    // 判斷當(dāng)前接收到的Broadcast是否是收到短信的action
    if (intent.getAction()
            .equals("android.provider.Telephony.SMS_RECEIVED")) {
        StringBuilder sb = new StringBuilder();
        // 獲取Broadcast傳遞的數(shù)據(jù)
        Bundle bundle = intent.getExtras();
        if (bundle != null) {
            Object[] pdus = (Object[]) bundle.get("pdus");
            for (Object p : pdus) {
                byte[] pud = (byte[]) p;
                // 聲明一個SmsMessage,用于解析短信的byte[]數(shù)組
                SmsMessage message = SmsMessage.createFromPdu(pud);
                boolean flag = false;
                for (String str : blackKeyWord) {
                    if (message.getMessageBody().contains(str) ) {
                        // 發(fā)現(xiàn)黑名單關(guān)鍵字,則標(biāo)記為true
                        flag = true;
                        break;
                    }
                }
                if (flag) {
                    sb.append("發(fā)件人:\n");
                    sb.append(message.getOriginatingAddress());
                    sb.append("\n發(fā)送時間:\n");
                    Date date = new Date(message.getTimestampMillis());
                    SimpleDateFormat format = new SimpleDateFormat(
                            "yyyy-MM-dd HH:mm:ss");
                    sb.append(format.format(date));
                    sb.append("\n短信內(nèi)容:\n");
                    sb.append(message.getMessageBody());

                    Toast.makeText(context, sb.toString(),
                            Toast.LENGTH_SHORT).show();
                    // 如果存在黑名單關(guān)鍵字內(nèi)容,停止Broadcast傳播
                    abortBroadcast();
                }
                
            }
        }
    }

}

}
</pre>

在AndroidManifest.xml中配置Receiver。
<pre>
<receiver android:name="cn.bgxt.Broadcastdemo.MessageWarn.MessageBroadcast">

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

Paste_Image.png

2.IP撥號
再來看看IP撥號的示例,在Android中,如果觸發(fā)撥打電話的Action,則會發(fā)布一個"android.intent.action.NEW_OUTGOING_CALL"的Broadcast出來,只需要針對它進行攔截即可,然后在加上IP前綴,把處理過的號碼添加到數(shù)據(jù)傳遞給下一個Receiver。
  處理接收撥打電話的Broadcast,需要對Android增加權(quán)限:
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
 下面直接上代碼了,注釋寫的很清楚,這里不再累述了。
  IpCallPhone.java:
<pre>
package cn.bgxt.Broadcastdemo.IpCall;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class IpCallPhone extends BroadcastReceiver {
private final String STARTS="17951";
@Override
public void onReceive(Context context, Intent intent) {
// 獲取當(dāng)前撥號的號碼
String number=getResultData();
// 此號碼沒有被加IP撥號的前綴
if(!number.startsWith(STARTS)){
// 設(shè)置加了IP號碼的號碼
String newnumber=STARTS+number;
// 把新號碼增加到返回結(jié)果數(shù)據(jù)中,用于傳遞給后面的Receiver
setResultData(newnumber);
}
}
}
</pre>

AndroidManifest.xml配置Receiver:

<pre><receiver android:name="cn.bgxt.Broadcastdemo.IpCall.IpCallPhone">
<intent-filter android:priority="200">
<action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
</intent-filter>
</receiver>
</pre>

效果展示:

Paste_Image.png

Paste_Image.png
六、使用本地廣播

本地廣播不需要擔(dān)心機密數(shù)據(jù)泄露,因為發(fā)送的廣播不會離開本地程序。同樣,其他程序也無法將廣播發(fā)送到我們程序內(nèi)部,因此不用擔(dān)心安全漏洞隱患。另外,本地廣播比系統(tǒng)全局廣播更高效。
需要注意的是,本地廣播無法通過靜態(tài)注冊方式使用。這是因為靜態(tài)注冊主要是為了讓程序未啟動也能接收廣播,而發(fā)送本地廣播時,程序肯定已經(jīng)啟動了。
<pre>
public class MainActivity extends Activity{
private IntentFilter intentFilter;
private LocalReceiver localReceiver;
private LocalBroadcastManager localBroadcastManager;

private NeworkChangeReceiver networkChangeReceiver;

protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
localBroadcastManager = LocalBroadcastManager.getInstance(this);

Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener(){
public void onClick(View v){
Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
localBroadcastManager.sendBroadcast(intent);
}
});
intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
localReceiver = new LocalReceiver ();
localBroadcastManager.registerReceiver(localReceiver ,intentFilter);
}

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內(nèi)容