1.廣播的分類
(1)按照發(fā)送的方式分類
- 標(biāo)準(zhǔn)廣播
是一種異步的方式來進(jìn)行傳播的,廣播發(fā)出去之后,所有的廣播接收者幾乎是同一時(shí)間收到消息的。他們之間沒有先后順序可言,而且這種廣播是沒法被截?cái)嗟摹?/li>- 有序廣播
是一種同步執(zhí)行的廣播,在廣播發(fā)出去之后,同一時(shí)刻只有一個(gè)廣播接收器可以收到消息。當(dāng)廣播中的邏輯執(zhí)行完成后,廣播才會(huì)繼續(xù)傳播。
(2)按照注冊(cè)的方式分類
- 動(dòng)態(tài)注冊(cè)廣播
顧名思義,就是在代碼中注冊(cè)的。- 靜態(tài)注冊(cè)廣播
動(dòng)態(tài)注冊(cè)要求程序必須在運(yùn)行時(shí)才能進(jìn)行,有一定的局限性,如果我們需要在程序還沒啟動(dòng)的時(shí)候就可以接收到注冊(cè)的廣播,就需要靜態(tài)注冊(cè)了。主要是在AndroidManifest中進(jìn)行注冊(cè)。
(3)按照定義的方式分類
- 系統(tǒng)廣播
Android系統(tǒng)中內(nèi)置了多個(gè)系統(tǒng)廣播,每個(gè)系統(tǒng)廣播都具有特定的intent-filter,其中主要包括具體的action,系統(tǒng)廣播發(fā)出后,將被相應(yīng)的BroadcastReceiver接收。系統(tǒng)廣播在系統(tǒng)內(nèi)部當(dāng)特定事件發(fā)生時(shí),由系統(tǒng)自動(dòng)發(fā)出。- 自定義廣播
由應(yīng)用程序開發(fā)者自己定義的廣播
2.動(dòng)態(tài)注冊(cè)廣播的實(shí)現(xiàn)
一段比較典型的實(shí)現(xiàn)代碼為:
(1)實(shí)現(xiàn)一個(gè)廣播接收器
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show();
abortBroadcast();
}
}
主要就是繼承一個(gè)BroadcastReceiver,實(shí)現(xiàn)onReceive方法,在其中實(shí)現(xiàn)自己的業(yè)務(wù)邏輯就可以了。
(2)注冊(cè)廣播
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private MyBroadcastReceiver myBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
myBroadcastReceiver = new MyBroadcastReceiver();
registerReceiver(myBroadcastReceiver, intentFilter);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("android.net.conn.CONNECTIVITY_CHANGE");
sendBroadcast(intent); // 發(fā)送廣播
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(myBroadcastReceiver);
}
}
這樣MyBroadcastReceiver就可以收到相應(yīng)的廣播消息了。
3.靜態(tài)注冊(cè)廣播的實(shí)現(xiàn)
還是用上面的按個(gè)MyBroadcastReceiver,只不過這次采用靜態(tài)注冊(cè)的方式
在manifest文件中增加如下的代碼:
<receiver
android:name=".MyBroadcastReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
幾個(gè)相關(guān)解釋:
- android:exported
此BroadcastReceiver能否接收其他App發(fā)出的廣播(其默認(rèn)值是由receiver中有無intent-filter決定的,如果有intent-filter,默認(rèn)值為true,否則為false);- android:name
此broadcastReceiver類名;- android:permission
如果設(shè)置,具有相應(yīng)權(quán)限的廣播發(fā)送方發(fā)送的廣播才能被此broadcastReceiver所接收;- android:process
broadcastReceiver運(yùn)行所處的進(jìn)程。默認(rèn)為App的進(jìn)程。可以指定獨(dú)立的進(jìn)程(Android四大組件都可以通過此屬性指定自己的獨(dú)立進(jìn)程);
4.靜態(tài)注冊(cè)廣播與動(dòng)態(tài)注冊(cè)廣播的區(qū)別
- 靜態(tài)注冊(cè)即使App退出,仍然能接收到廣播
- 動(dòng)態(tài)注冊(cè)時(shí),當(dāng)Activity退出,就接收不到廣播了
- 但是靜態(tài)注冊(cè)即使App退出,仍然能接收到廣播這種說法自Android 3.1開始有可能不再成立。
說明:
Android 3.1開始系統(tǒng)在Intent與廣播相關(guān)的flag增加了參數(shù):
A. FLAG_INCLUDE_STOPPED_PACKAGES:包含已經(jīng)停止的包(停止:即包所在的進(jìn)程已經(jīng)退出)
B. FLAG_EXCLUDE_STOPPED_PACKAGES:不包含已經(jīng)停止的包
自Android3.1開始,系統(tǒng)本身增加了對(duì)所有App當(dāng)前是否處于運(yùn)行狀態(tài)的跟蹤。在發(fā)送廣播時(shí),不管是什么廣播類型,系統(tǒng)默認(rèn)直接增加了值為FLAG_EXCLUDE_STOPPED_PACKAGES的flag,導(dǎo)致即使是靜態(tài)注冊(cè)的廣播接收器,對(duì)于其所在進(jìn)程已經(jīng)退出的App,同樣無法接收到廣播。
因此對(duì)于系統(tǒng)廣播,由于是系統(tǒng)內(nèi)部直接發(fā)出的,無法更改此intent的flag值,因此,從3.1開始對(duì)于靜態(tài)注冊(cè)的接收系統(tǒng)廣播的BroadcastReceiver,如果App進(jìn)程已經(jīng)退出,將不能接收到廣播。
但是對(duì)于自定義的廣播,可以通過覆寫此flag為FLAG_INCLUDE_STOPPED_PACKAGES,使得靜態(tài)注冊(cè)的BroadcastReceiver,即使所在App進(jìn)程已經(jīng)退出,也能接收到廣播,并會(huì)啟動(dòng)應(yīng)用進(jìn)程,但此時(shí)的BroadcastReceiver是新建的。
實(shí)現(xiàn)代碼為:
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
sendBroadcast(intent);
在3.1以前,不少App可能通過靜態(tài)注冊(cè)方式監(jiān)聽各種系統(tǒng)廣播,以此進(jìn)行一些業(yè)務(wù)上的處理(如即使App已經(jīng)退出,仍然能接收到,可以啟動(dòng)service等..),3.1后,靜態(tài)注冊(cè)接受廣播方式的改變,將直接導(dǎo)致此類方案不再可行。于是,通過將Service與App本身設(shè)置成不同的進(jìn)程已經(jīng)成為實(shí)現(xiàn)此類需求的可行替代方案。
API變化詳情參見Android官方文檔
5.有序廣播
前面介紹過,有序廣播是異步方式傳播的。指的是發(fā)送出去的廣播被BroadcastReceiver按照先后循序接收。有序廣播的定義過程與普通廣播無異,只是其的主要發(fā)送方式變?yōu)椋?/p>
/**
* Broadcast the given intent to all interested BroadcastReceivers, delivering
* them one at a time to allow more preferred receivers to consume the
* broadcast before it is delivered to less preferred receivers. This
* call is asynchronous; it returns immediately, and you will continue
* executing while the receivers are run.
* @param intent The Intent to broadcast; all receivers matching this
* Intent will receive the broadcast.
* @param receiverPermission (optional) String naming a permissions that
* a receiver must hold in order to receive your broadcast.
* If null, no permission is required.
*/
public abstract void sendOrderedBroadcast(Intent intent,
@Nullable String receiverPermission);
其他的幾種重載的方法可以參見官方文檔。
有序廣播的主要特點(diǎn):
- 同級(jí)別接收是隨機(jī)的(結(jié)合下一條)
- 同級(jí)別動(dòng)態(tài)注冊(cè)(代碼中注冊(cè))高于靜態(tài)注冊(cè)(AndroidManifest中注冊(cè))
- 排序規(guī)則為:將當(dāng)前系統(tǒng)中所有有效的動(dòng)態(tài)注冊(cè)和靜態(tài)注冊(cè)的BroadcastReceiver按照priority屬性值從大到小排序
- 先接收的BroadcastReceiver可以對(duì)此有序廣播進(jìn)行截?cái)啵购竺娴腂roadcastReceiver不再接收到此廣播,也可以對(duì)廣播進(jìn)行修改,使后面的BroadcastReceiver接收到廣播后解析得到錯(cuò)誤的參數(shù)值。當(dāng)然,一般情況下,不建議對(duì)有序廣播進(jìn)行此類操作,尤其是針對(duì)系統(tǒng)中的有序廣播。實(shí)現(xiàn)截?cái)嗟拇a為:
abortBroadcast();
6.標(biāo)準(zhǔn)廣播
標(biāo)準(zhǔn)廣播的主要特點(diǎn)為:
- 同級(jí)別接收先后是隨機(jī)的(無序的)
- 級(jí)別低的后接收到廣播
- 接收器不能截?cái)鄰V播的繼續(xù)傳播,也不能處理廣播
- 同級(jí)別動(dòng)態(tài)注冊(cè)(代碼中注冊(cè))高于靜態(tài)注冊(cè)(AndroidManifest中注冊(cè))
7.廣播的安全性問題
由前文闡述可知,Android中的廣播可以跨進(jìn)程甚至跨App直接通信,且exported屬性在有intent-filter的情況下默認(rèn)值是true,由此將可能出現(xiàn)的安全隱患如下:
- 其他App可能會(huì)針對(duì)性的發(fā)出與當(dāng)前App intent-filter相匹配的廣播,由此導(dǎo)致當(dāng)前App不斷接收到廣播并處理;
- 其他App可以注冊(cè)與當(dāng)前App一致的intent-filter用于接收廣播,獲取廣播具體信息。
無論哪種情形,這些安全隱患都確實(shí)是存在的。由此,業(yè)界常見的一些增加安全性的方案包括:
- 對(duì)于同一App內(nèi)部發(fā)送和接收廣播,將exported屬性人為設(shè)置成false,使得非本App內(nèi)部發(fā)出的此廣播不被接收;
- 在廣播發(fā)送和接收時(shí),都增加上相應(yīng)的permission,用于權(quán)限驗(yàn)證;
- 發(fā)送廣播時(shí),指定特定廣播接收器所在的包名,具體是通過intent.setPackage(packageName)指定,這樣此廣播將只會(huì)發(fā)送到此包中的App內(nèi)與之相匹配的有效廣播接收器中。
- 采用LocalBroadcastManager的方式
下文分析幾種常見的處理方案。
8.本地廣播LocalBroadcastManager
(1)LocalBroadcastManager的概念
為了解決安全性問題,Android在android.support.v4.content包中引入了LocalBroadcastManager。按照官方文檔的描述,使用LocalBroadcastManager有如下的優(yōu)勢(shì):
Helper to register for and send broadcasts of Intents to local objects within your process. This has a number of advantages over sending global broadcasts with sendBroadcast(Intent):
(1)You know that the data you are broadcasting won't leave your app, so don't need to worry about leaking private data.
(2)It is not possible for other applications to send these broadcasts to your app, so you don't need to worry about having security holes they can exploit.
(3)It is more efficient than sending a global broadcast through the system.
也就是說,使用該機(jī)制發(fā)出的廣播只能夠在應(yīng)用程序內(nèi)部進(jìn)行傳遞,并且廣播接收器也只能接收來自本地應(yīng)用程序發(fā)出的廣播,這樣所有的安全性問題都不存在了。
(2)LocalBroadcastManager使用范例
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private LocalReceiver localReceiver;
private LocalBroadcastManager localBroadcastManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
localBroadcastManager = LocalBroadcastManager.getInstance(this); // 獲取實(shí)例
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent("com.example.broadcasttest.LOCAL_BROADCAST");
localBroadcastManager.sendBroadcast(intent); // 發(fā)送本地廣播
}
});
intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST");
localReceiver = new LocalReceiver();
localBroadcastManager.registerReceiver(localReceiver, intentFilter); // 注冊(cè)本地廣播監(jiān)聽器
}
@Override
protected void onDestroy() {
super.onDestroy();
localBroadcastManager.unregisterReceiver(localReceiver);
}
class LocalReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show();
}
}
}
LocalBroadcastManager的官方文檔鏈接
9.自定義廣播權(quán)限
(1)一個(gè)自定義權(quán)限的示例
<permission
android:name="com.self.define.permission.test"
android:label="BroadcastReceiverPermission"
android:protectionLevel="signature">
</permission>
在自定義權(quán)限時(shí),通常會(huì)指定protectionLevel屬性,常用的如下:
- normal:默認(rèn)的,應(yīng)用安裝前,用戶可以看到相應(yīng)的權(quán)限,但無需用戶主動(dòng)授權(quán)。
- dangerous:normal安全級(jí)別控制以外的任何危險(xiǎn)操作。需要dangerous級(jí)別權(quán)限時(shí),Android會(huì)明確要求用戶進(jìn)行授權(quán)。常見的如:網(wǎng)絡(luò)使用權(quán)限,相機(jī)使用權(quán)限及聯(lián)系人信息使用權(quán)限等。
- signature:它要求權(quán)限聲明應(yīng)用和權(quán)限使用應(yīng)用使用相同的keystore進(jìn)行簽名。如果使用同一keystore,則該權(quán)限由系統(tǒng)授予,否則系統(tǒng)會(huì)拒絕。并且權(quán)限授予時(shí),不會(huì)通知用戶。它常用于應(yīng)用內(nèi)部。把protectionLevel聲明為signature。如果別的應(yīng)用使用的不是同一個(gè)簽名文件,就沒辦法使用該權(quán)限,從而保護(hù)了自己的接收者。
(2)廣播接收者
如果采用靜態(tài)注冊(cè)的方式:
<receiver
android:name=".common.MyBroadcastReceiver"
android:exported="false"
android:permission="com.self.define.permission.test">
<intent-filter>
<action android:name="action.name"/>
</intent-filter>
</receiver>
如果采用動(dòng)態(tài)注冊(cè)的方式
相應(yīng)的API有:
(1)registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)
Register to receive intent broadcasts, to run in the context of scheduler.
(2)sendBroadcast(Intent intent, String receiverPermission)
Broadcast the given intent to all interested BroadcastReceivers, allowing an optional required permission to be enforced.
(3)sendOrderedBroadcast(Intent intent, String receiverPermission)
Broadcast the given intent to all interested BroadcastReceivers, delivering them one at a time to allow more preferred receivers to consume the broadcast before it is delivered to less preferred receivers.
receiver是動(dòng)態(tài)注冊(cè)時(shí),需要?jiǎng)?chuàng)建自己的使用權(quán)限,并且將protectionLevel設(shè)置為signature。這樣,當(dāng)別的應(yīng)用和receiver所在的應(yīng)用使用的簽名不一樣時(shí),便不會(huì)啟動(dòng)該receiver。例如:
MyBroadcastReceiver receiver = new MyBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcast.test");
intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
//注冊(cè)receiver時(shí),直接指定發(fā)送者應(yīng)該具有的權(quán)限。不然外部應(yīng)用依舊可以觸及到receiver
registerReceiver(receiver, intentFilter, "com.self.define.permission.test", null);
在注冊(cè)的時(shí)候,最關(guān)鍵的一點(diǎn)是用registerReceiver(BroadcastReceiver, IntentFilter, String, Handler)進(jìn)行注冊(cè),而不是平常用的是registerReceiver(BroadcastReceiver, IntentFilter)。相較于后者,前者在注冊(cè)的時(shí)候要求了發(fā)送者必須具有的權(quán)限。如果發(fā)送者沒有該權(quán)限,那么發(fā)送者發(fā)送的廣播即使經(jīng)過IntentFilter的過濾,也不會(huì)被receiver接收。此時(shí)如果再自定義一個(gè)權(quán)限,并且將權(quán)限的protectionLevel設(shè)置為signature,那么外部應(yīng)用便無法使用該權(quán)限,也就無法觸及到該receiver。
發(fā)送廣播的代碼為:
Intent intent = new Intent("com.example.broadcast.test.permission");
sendBroadcast(intent,"com.self.define.permission.test");
另外需要在manifest文件中定義權(quán)限并聲明
<permission
android:name="com.self.define.permission.test"
android:label="BroadcastReceiverPermission"
android:protectionLevel="signature">
</permission>
<uses-permission android:name="com.self.define.permission.test"/>
切記要在<application>同級(jí)的位置配置使用到的權(quán)限
10.其他幾點(diǎn)補(bǔ)充
(1)不同注冊(cè)方式onReceive(context, intent)中的context具體類型
兩個(gè)參數(shù)的官方定義為:
Context: The Context in which the receiver is running.
Intent: The Intent being received.
BroadcastReceiver本身不是Context,其內(nèi)部也不含有Context,但在onReceive(Context context, Intent intent)中有context參數(shù)。這個(gè)context隨著receiver的注冊(cè)方式的不同而不同:
靜態(tài)注冊(cè):context為ReceiverRestrictedContext
動(dòng)態(tài)注冊(cè):context為Activity的context
LocalBroadcastManager的動(dòng)態(tài)注冊(cè):context為Application的context
(2)ANR問題
官方文檔的描述:
When it runs on the main thread you should never perform long-running operations in it (there is a timeout of 10 seconds that the system allows before considering the receiver to be blocked and a candidate to be killed). You cannot launch a popup dialog in your implementation of onReceive().