參考承香墨影的兩篇博客
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>
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>
效果展示:
六、使用本地廣播
本地廣播不需要擔(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>