參考承香墨影的兩篇博客
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)聽器千埃,運行在指定程序的所在進程中,當程序退出的時候忆植,OnXxxListener監(jiān)聽器也就隨之關(guān)閉了放可,但是BroadcastReceiver屬于系統(tǒng)級的監(jiān)聽器,它擁有自己的進程朝刊,只要存在與之匹配的Broadcast被以Intent的形式發(fā)送出來耀里,BroadcastReceiver就會被激活。
雖然同屬Android的四大組件拾氓,BroadcastReceiver也有自己獨立的聲明周期冯挎,但是和Activity、Service又不同咙鞍。當在系統(tǒng)注冊一個BroadcastReceiver之后房官,每次系統(tǒng)以一個Intent的形式發(fā)布Broadcast的時候,系統(tǒng)都會創(chuàng)建與之對應(yīng)的BroadcastReceiver廣播接收者實例奶陈,并自動觸發(fā)它的onReceive()方法易阳,當onReceive()方法被執(zhí)行完成之后,BroadcastReceiver的實例就會被銷毀吃粒。雖然它獨自享用一個單獨的進程潦俺,但也不是沒有限制的,如果BroadcastReceiver.onReceive()方法不能在10秒內(nèi)執(zhí)行完成徐勃,Android系統(tǒng)就會認為該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é)束了秒啦,該線程就會被標記為一個空線程熬粗,根據(jù)Android的內(nèi)存管理策略,在系統(tǒng)內(nèi)存緊張的時候余境,會按照優(yōu)先級驻呐,結(jié)束優(yōu)先級低的線程,而空線程無異是優(yōu)先級最低的芳来,這樣就可能導(dǎo)致BroadcastReceiver啟動的子線程不能執(zhí)行完成含末。
二、BroadcastReceiver的種類
上面提到即舌,當系統(tǒng)以一個Intent的形式發(fā)送一個Broadcast出去之后佣盒,所有與之匹配的BroadcastReceiver都會被實例化,但是這里是有區(qū)別的顽聂,根據(jù)Broadcast的傳播方式區(qū)別沼撕,在系統(tǒng)中有如下兩種Broadcast:
- 普通廣播
Normal Broadcast,它是完全異步的芜飘,也就是說,在邏輯上磨总,當一個Broadcast被發(fā)出之后嗦明,所有的與之匹配的BroadcastReceiver都同時接收到Broadcast。優(yōu)點是傳遞效率比較高蚪燕,但是也有缺點娶牌,就是一個BroadcastReceiver不能影響其他響應(yīng)這條Broadcast的BroadcastReceiver。 - 有序廣播
Ordered Broadcast馆纳,它是同步執(zhí)行的诗良,也就是說有序廣播的接收器將會按照預(yù)先聲明的優(yōu)先級依次接受Broadcast,是鏈式結(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ā)當中,大部分情況下是不需要自己發(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)鍵字攔截短信
從上面列舉的一些動作會發(fā)布的Broadcast旱幼,可以找到,當系統(tǒng)接收到一條短信的時候突委,會發(fā)布一個“android.provider.Telephony.SMS_RECEIVED”的Broadcast柏卤,之前已經(jīng)介紹過了冬三,一般系統(tǒng)Broadcast都是有序廣播,如果不被高優(yōu)先級的BroadcastReceiver停止傳遞缘缚,會按照優(yōu)先級順序傳遞下去勾笆。
而在這個示例中,通過監(jiān)聽接收短信的廣播桥滨,當其內(nèi)容有黑名單中的關(guān)鍵字的話窝爪,則阻止Broadcast繼續(xù)傳播,并使用Toast提示齐媒,否則正常提示短信信息蒲每。
通過上一篇博客了解到,onReceive方法的Intent參數(shù)包含了這條廣播傳遞的參數(shù)里初,對于短信信息而言啃勉,需要獲取key為"pdus"的數(shù)組,取出數(shù)組中每一項双妨,它的每一項代表了一個byte[]格式的短信淮阐,需要使用SmsMessage類解析短信內(nèi)容。
當然刁品,攔截短信的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) {
// 判斷當前接收到的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)鍵字膏孟,則標記為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) {
// 獲取當前撥號的號碼
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>