現(xiàn)實(shí)中的廣播:電臺(tái)為了傳達(dá)一些消息而發(fā)送廣播,通過(guò)廣播攜帶要傳達(dá)的消息辅髓,群眾只要買(mǎi)一個(gè)收音機(jī)俯树,就可以收到廣播了。
Android中的廣播:Android系統(tǒng)在運(yùn)行過(guò)程中哄陶,會(huì)發(fā)生很多事件,比如電量改變莫鸭、耳機(jī)插入铣缠、收到短信、撥打電話(huà)鹦付、屏幕解鎖尚粘、系統(tǒng)開(kāi)機(jī)等,為了讓App知道事件的發(fā)生敲长,系統(tǒng)會(huì)發(fā)送該事件的廣播郎嫁,App只要注冊(cè)一個(gè)BroadcastReceiver,就可以接收到對(duì)應(yīng)的廣播祈噪,以便做出響應(yīng)泽铛。
在A(yíng)ndroid系統(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)器舀透,在A(yíng)ndroid 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è)類(lèi)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è)就是在A(yíng)ndroidManifest中注冊(cè)BroadcastReceiver链烈,并指定它所接收的廣播種類(lèi)厉斟,如下面配置的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,在A(yíng)ndroid 4.0以前强衡,只要app被安裝擦秽,它就會(huì)一直生效;而Android 4.0之后漩勤,在app安裝后還需要先啟動(dòng)一次感挥,它才會(huì)生效,并且如果用戶(hù)在應(yīng)用管理界面手動(dòng)殺死了這個(gè)BroadcastReceiver所在進(jìn)程越败,那么它將不在生效触幼,直到用戶(hù)下一次啟動(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ì)被銷(xiāo)毀(生命周期結(jié)束)。
-
動(dòng)態(tài)注冊(cè)(非常駐)
動(dòng)態(tài)注冊(cè)是指在Java代碼中注冊(cè)BroadcastReceiver酬滤,并通過(guò)IntentFilter來(lái)指定它接收的廣播種類(lèi)签餐。
使用動(dòng)態(tài)注冊(cè)BroadcastReceiver,通常是在onResume()
中使用registerReceiver(xxxReceiver, intentFilter)
注冊(cè)它敏晤,在onPause()
使用unregisterReceiver(xxxReceiver)
注銷(xiāo)它贱田,注銷(xiāo)之后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(); // 用于指定接收廣播的類(lèi)型 intentFilter.addAction(Intent.ACTION_SCREEN_ON); // 屏幕點(diǎn)亮 intentFilter.addAction(Intent.ACTION_SCREEN_OFF); // 屏幕熄滅 registerReceiver(receiver, intentFilter); // 注冊(cè)廣播接收器 } @Override protected void onPause() { unregisterReceiver(receiver); // 注銷(xiāo)廣播接收器 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)器
撥打電話(huà)時(shí),系統(tǒng)會(huì)發(fā)出一個(gè)廣播奏司,廣播中攜帶著用戶(hù)所要撥打的號(hào)碼乔询。
我們可以創(chuàng)建一個(gè)BroadcastReceiver接收這個(gè)廣播,然后取出廣播中攜帶的號(hào)碼進(jìn)行修改(這里是加上線(xiàn)路號(hào)碼)韵洋,然后把修改后的號(hào)碼放回廣播竿刁。
在IP撥號(hào)器中定義廣播接收器接收打電話(huà)廣播
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ù)撥打的號(hào)碼
setResultData("17951" + number); // 修改廣播內(nèi)的號(hào)碼
}
}
在清單文件中靜態(tài)注冊(cè)該receiver并定義它接收的廣播類(lèi)型
<receiver android:name=".CallReceiver">
<intent-filter >
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
不要忘了申請(qǐng)接收打電話(huà)廣播所需要的權(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)用收不到短信廣播呻待,用戶(hù)也就看不到被攔截的短信了打月。
在短信攔截器中定義廣播接收器接收短信廣播
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接收的廣播類(lèi)型鱼冀,注意要設(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è)廣播接收器,并指定它所接收的廣播類(lèi)型梗脾。
一個(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ā)送我們自己的廣播:
sendBroadcast()
:最普通的發(fā)送intent的方式滞诺,是一種無(wú)序的廣播機(jī)制形导,理論上环疼,所有的接收器同時(shí)獲得該intent的消息,接受器之間不存在先后順序朵耕,不能截?cái)?修改intent的數(shù)據(jù)炫隶。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)一些特定的功能)。:是一種粘性廣播锰霜。所謂的粘性是指筹误,這個(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)正式廢棄了粘性廣播屡立。sendStickyBroadcast()
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():終止廣播伶唯,類(lèi)似攔截,只有有序廣播可以被攔截(Andorid 4.4之后只有用戶(hù)設(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)器的例子中嫡霞,打電話(huà)應(yīng)用中的BroadcastReceiver就是一個(gè)結(jié)果接收器瓶埋,所以我們的IP撥號(hào)器中的BroadcastReceiver即使沒(méi)設(shè)置優(yōu)先級(jí)也會(huì)在打電話(huà)應(yīng)用之前收到撥號(hào)廣播,且打電話(huà)應(yīng)用不能被攔截
- 對(duì)于靜態(tài)注冊(cè)的BroadcastReceiver,優(yōu)先級(jí)聲明在<intent-filter.../>標(biāo)簽內(nèi)的
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)新線(xiàn)程去完成耗時(shí)操作,因?yàn)锽roadcastReceiver本身的生命周期很短(靜態(tài)注冊(cè)下當(dāng)onReceive()
執(zhí)行完生命周期即結(jié)束)渐裸,很可能出現(xiàn)的情況是子線(xiàn)程還沒(méi)有執(zhí)行結(jié)束兵多,BroadcastReceiver就已經(jīng)被銷(xiāo)毀了,此時(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í)操作的子線(xiàn)程不能執(zhí)行完成衅斩。
拋出ANR的條件
對(duì)于A(yíng)ctivity盆顾、BroadcastReceiver、Service這三大組件畏梆,如果在主線(xiàn)程(UI線(xiàn)程)中進(jìn)行耗時(shí)操作您宪,都有可能導(dǎo)致應(yīng)用拋出ANR(Application Not Responding)異常,下面給出在這三大組件中拋出ANR的條件奠涌,源碼基于Nougat - 7.1.1_r6
-
對(duì)于A(yíng)ctivity宪巨,如果在主線(xiàn)程中執(zhí)行耗時(shí)操作,并且從用戶(hù)按鍵或觸摸屏幕開(kāi)始算起5s內(nèi)耗時(shí)操作仍然未執(zhí)行完溜畅,就會(huì)拋出ANR捏卓,這類(lèi)ANR會(huì)有提示框彈出,用戶(hù)可以選擇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;
-
對(duì)于BroadcastReceiver的onReceive()怠晴,如果在主線(xiàn)程中執(zhí)行耗時(shí)操作遥金,并且在60s內(nèi)(默認(rèn)后臺(tái)隊(duì)列廣播)耗時(shí)操作仍然未執(zhí)行完,就會(huì)拋出ANR蒜田,這類(lèi)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)廣播快冲粤?
-
對(duì)于前臺(tái)Service美莫,如果在主線(xiàn)程中執(zhí)行耗時(shí)操作,并且在20s內(nèi)耗時(shí)操作仍然未執(zhí)行完梯捕,就會(huì)拋出ANR厢呵,這類(lèi)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)于A(yíng)NR問(wèn)題的詳細(xì)分析科阎,這里有一篇不錯(cuò)的文章述吸,ANR問(wèn)題分析指南