發(fā)送無序廣播
Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
sendBroadcast(intent);
隨機將給定的意圖廣播給所有感興趣的BroadcastReceivers。 這個調(diào)用是異步的; 它立即返回,您將繼續(xù)在接收器運行時執(zhí)行。任何app注冊都可以接收,可以使用
intent.setPackage(String)指定接收應用。沒有傳播任何結(jié)果來自接收者,接收者不能中止廣播。 如果你想要允許接收方傳播結(jié)果或中止廣播,您必須使用以下命令發(fā)送有序廣播sendOrderedBroadcast(Intent,String)
發(fā)送有序廣播
Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
sendOrderedBroadcast(intent,null);
一次向一個接收器發(fā)送廣播。當接收器逐個順序執(zhí)行時,接收器可以向下傳遞結(jié)果,也可以完全中止廣播,使其不再傳遞給其他接收器。接收器的運行順序可以通過匹配的 intent-filter 的 android:priority 屬性來控制;具有相同優(yōu)先級的接收器將按隨機順序運行。
關(guān)于 android:priority 的取值范圍,官網(wǎng)給出的是 -1000 ~ 1000 ,但是看到很多人設(shè)置成2147483647(Integer.MAX_VALUE)這個值,可能因為 android:priority 的屬性值是 integer 類型,系統(tǒng)會拿這個值和其他值做比較,結(jié)果怎么都是它最大了。
<receiver android:name=".receiver.MyReceiver1"
android:protectionLevel="normal"
>
<intent-filter
android:priority="100">
<action android:name="com.sqw.testBroadcastReceiver.say.hi"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
代碼中設(shè)置priority
IntentFilter intentFilter = new IntentFilter("com.sqw.testBroadcastReceiver.say.hi");
intentFilter.setPriority(100);
中斷有序廣播
public class MyReceiver2 extends BroadcastReceiver {
private static final String TAG = "MyReceiver2";
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context,"MyReceiver2 收到廣播",Toast.LENGTH_SHORT).show();
Log.e(TAG, "onReceive: MyReceiver2 收到廣播");
//終止廣播像低優(yōu)先級傳遞
abortBroadcast();
}
}
發(fā)送本地廣播
Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi.local");
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
BroadcastReceiver的設(shè)計初衷是全局性,可接收來自本應用和其他應用發(fā)過來的intent廣播。這也同時給app帶來了一定的安全風險。為了解決這個問題,LocalBroadcastManager橫空出世。LocalBroadcastManager 只會將廣播限定在當前應用程序中。LocalBroadcastManager 發(fā)送的廣播不會離開你的應用程序,同樣也不會接收來自其它應用程序的廣播,因此你可以放心的在 LocalBroadcastManager 中傳播敏感信息。同時由于LocalBroadcastManager不需要用到跨進程機制,因此相對 BroadcastReceiver 而言要更為高效。LocalBroadcastManager只在動態(tài)廣播時使用,靜態(tài)廣播不能使用LocalBroadcastManager。
另外本地廣播注冊和反注冊方式與有序無序廣播不一樣,需要用到 LocalBroadcastManager.getInstance(context).registerReceiver()
LocalReceiver1 localReceiver1 = new LocalReceiver1();
IntentFilter intentFilter = new IntentFilter("com.sqw.testBroadcastReceiver.say.hi.local");
//注冊本地廣播
LocalBroadcastManager.getInstance(context).registerReceiver(localReceiver1,intentFilter);
if(localReceiver1 != null){
//反注冊本地廣播
LocalBroadcastManager.getInstance(context).unregisterReceiver(localReceiver1);
}
粘性廣播
Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi.sticky");
sendStickyBroadcast(intent);
粘性廣播通過 Context.sendStickyBroadcast() 函數(shù)來發(fā)送,用此函數(shù)發(fā)送的廣播會一直滯留,當有匹配此廣播的廣播接收器被注冊后,該廣播接收器就會收到此條廣播。使用此函數(shù)發(fā)送廣播時,需要獲得
BROADCAST_STICKY權(quán)限:<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
sendStickyBroadcast() 只保留最后一條廣播,并且一直保留下去,這樣即使已經(jīng)有廣播接收器處理了該廣播,當再有匹配的廣播接收器被注冊時,此廣播仍會被接收。如果你只想處理一遍該廣播,可以通過removeStickyBroadcast()函數(shù)實現(xiàn)。
粘性廣播在
Android5.0(API 21)的時候就不推薦使用了,他們不提供安全性(任何人可以訪問它們),沒有保護(任何人都可以修改它們)以及許多其他問題。推薦使用非粘性廣播,所以下文不再討論粘性廣播。
廣播的安全性
因為只要注冊了廣播接收者,就可以收到廣播,比如A、B接收者都注冊了廣播,當你只想向A接收者發(fā)送廣播時,結(jié)果B接收者也能收到廣播,也許數(shù)據(jù)就被泄露了,這是我們都不愿意看到的。所以BroadcastRecevier提供了限制接收者的方法,就是在發(fā)送時指定接收者權(quán)限receiverPermission。
Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
String receiverPermission = "com.sqw.testBroadcastReceiver.send.RECEIVE_PERMISSION";
sendBroadcast(intent,receiverPermission);
當然有序廣播也可以指定接收者權(quán)限r(nóng)eceiverPermission, 本地廣播是不可以的。
Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
String receiverPermission = "com.sqw.testBroadcastReceiver.send.RECEIVE_PERMISSION";
sendOrderedBroadcast(intent,receiverPermission);
要說明一下的是這里的receiverPermission,是一個自定義權(quán)限,需要在清單文件中定義后才能使用。如果沒有聲明自定義權(quán)限,發(fā)送的廣播將沒有符合要求接收者。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.noahedu.my">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--自定義權(quán)限-->
<permission android:name="com.sqw.testBroadcastReceiver.send.RECEIVE_PERMISSION"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:name="MyApplication"
android:theme="@style/AppTheme">
...
</application>
</manifest>
這里的 <uses-permission />、 <permission />可能大家會不怎么明白,這里說一下。
<uses-permission />:這個是申請權(quán)限。
比如要訪問網(wǎng)絡就使用<uses-permission android:name="android.permission.INTERNET"/>之后應用才有訪問網(wǎng)絡的權(quán)限。
<uses-permission android:name="android.permission.SEND_SMS"/>聲明要使用這個權(quán)限后就可以發(fā)送短信了,當然這是個危險權(quán)限,android6.0之后就需要動態(tài)申請了。
<permission />這個是定義權(quán)限。就是聲明一個新的權(quán)限,使用方法就如上面發(fā)送有權(quán)限限定的廣播,應該還有其他使用方法,這里我暫時不知道。
當然這里也可以直接寫系統(tǒng)定義的權(quán)限,這樣可以不需要自定義權(quán)限,當然自定義權(quán)限安全性更高。
Intent intent = new Intent("com.sqw.testBroadcastReceiver.say.hi");
sendBroadcast(intent,Manifest.permission.SEND_SMS);
這里不討論系統(tǒng)權(quán)限,還是使用自定義權(quán)限,那既然有定義權(quán)限,就有使用權(quán)限的地方,是的,如果你想接收到有權(quán)限限定的廣播,就必須申請使用發(fā)送廣播時定義的權(quán)限,也就是使用 <uses-permission />,這里申請上面發(fā)送廣播時定義的權(quán)限。
<uses-permission android:name="com.sqw.testBroadcastReceiver.send.RECEIVE_PERMISSION"/>
這里你會想到一個問題,即使是在應用內(nèi)發(fā)送廣播,應用內(nèi)難道也要申請權(quán)限嗎?是的,也要申請權(quán)限,不然也是收不到廣播的。
這樣之后,再按正常流程注冊廣播,就可以收到有權(quán)限的廣播了。
限制廣播發(fā)送者
可以限制接收者是否能收到廣播,當然也有限制發(fā)送者的。
限制接收者是,發(fā)送的時候限定了權(quán)限。就是接收的地方,要聲明權(quán)限,才能收到廣播。
那么限制發(fā)送者,也就是在接收的地方定義權(quán)限了,在發(fā)送的地方就要申請權(quán)限。具體請看
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.noahedu.testmyservice">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!--定義權(quán)限-->
<permission android:name="com.sqw.testBroadcastReceiver.receive.RECEIVE_PERMISSION"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
...
<!--android:permission 在廣播接收者中使用定義的權(quán)限-->
<receiver android:name=".receiver.ServiceReceiver"
android:permission="com.sqw.testBroadcastReceiver.receive.RECEIVE_PERMISSION"
>
<intent-filter>
<action android:name="com.sqw.testBroadcastReceiver.say.hi"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
</application>
</manifest>
這里receiver的android:permission屬性可以使用定義的權(quán)限。這樣發(fā)送者必須申明使用這個權(quán)限,發(fā)送的廣播這里才能收到。
<uses-permission android:name="com.sqw.testBroadcastReceiver.receive.RECEIVE_PERMISSION"/>
發(fā)送的應用,清單文件這樣申請使用這個權(quán)限之后,發(fā)送廣播,接收方就可以收到了。
簡單總結(jié)一下就是:
- (發(fā)/收)自定義權(quán)限
(<permission />) - (發(fā)/收)設(shè)置自定義權(quán)限
sendOrderedBroadcast(Intent,String) 或 android:permission - (收/發(fā))申請使用自定義權(quán)限
<uses-permission />
到這里,其實大家也會想到一個問題,那就是可以雙向綁定,這樣安全性會更高!代碼就不貼了
protectionLevel
但是這樣還是不夠安全,如果有人反編譯了代碼,就會發(fā)現(xiàn)自定義權(quán)限了,數(shù)據(jù)還是可能會泄漏,那有沒有更高級的方法呢,如果是一個公司的兩個app,使用相同的簽名,可以將安全級別提升到簽名級別,使用android:protectionLevel
<receiver android:name=".receiver.ServiceReceiver"
android:permission="com.sqw.testBroadcastReceiver.receive.RECEIVE_PERMISSION"
android:protectionLevel="signature">
<intent-filter>
<action android:name="com.sqw.testBroadcastReceiver.say.hi"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</receiver>
這樣只有具有相同簽名的app,并且申請了對應權(quán)限,這里才會收到廣播了。安全性會提高很多。
如果只是在本應用內(nèi)使用,推薦使用本地廣播LocalBroadcastManager,效率會更高。
廣播的性能問題
如果很多app注冊了開機廣播<action android:name="android.intent.action.BOOT_COMPLETED"/>,那么系統(tǒng)會一個一個啟動這些app進程,這樣勢必會消耗很多系統(tǒng)資源,對用戶體驗造成嚴重影響。這種情況建議動態(tài)注冊。
安全注意事項和最佳做法
如果您不需要向應用以外的組件發(fā)送廣播,則可以使用支持庫中提供的 LocalBroadcastManager 來收發(fā)本地廣播。LocalBroadcastManager 效率更高(無需進行進程間通信),并且您無需考慮其他應用在收發(fā)您的廣播時帶來的任何安全問題。本地廣播可在您的應用中作為通用的發(fā)布/訂閱事件總線,而不會產(chǎn)生任何系統(tǒng)級廣播開銷。
如果有許多應用在其清單中注冊接收相同的廣播,可能會導致系統(tǒng)啟動大量應用,從而對設(shè)備性能和用戶體驗造成嚴重影響。為避免發(fā)生這種情況,請優(yōu)先使用上下文注冊而不是清單聲明。有時,Android 系統(tǒng)本身會強制使用上下文注冊的接收器。例如,CONNECTIVITY_ACTION 廣播只會傳送給上下文注冊的接收器。
-
請勿使用隱式 intent 廣播敏感信息。任何注冊接收廣播的應用都可以讀取這些信息。您可以通過以下三種方式控制哪些應用可以接收您的廣播:
- 您可以在發(fā)送廣播時指定權(quán)限。
- 在 Android 4.0 及更高版本中,您可以在發(fā)送廣播時使用 setPackage(String) 指定軟件包。系統(tǒng)會將廣播限定到與該軟件包匹配的一組應用。
- 您可以使用 LocalBroadcastManager 發(fā)送本地廣播。
-
當您注冊接收器時,任何應用都可以向您應用的接收器發(fā)送潛在的惡意廣播。您可以通過以下三種方式限制您的應用可以接收的廣播:
- 您可以在注冊廣播接收器時指定權(quán)限。
- 對于清單聲明的接收器,您可以在清單中將 android:exported 屬性設(shè)置為“false”。這樣一來,接收器就不會接收來自應用外部的廣播。
- 您可以使用 LocalBroadcastManager 限制您的應用只接收本地廣播。
廣播操作的命名空間是全局性的。請確保在您自己的命名空間中編寫操作名稱和其他字符串,否則可能會無意中與其他應用發(fā)生沖突。
-
由于接收器的 onReceive(Context, Intent) 方法在主線程上運行,因此它會快速執(zhí)行并返回。如果您需要執(zhí)行長時間運行的工作,請謹慎生成線程或啟動后臺服務,因為系統(tǒng)可能會在 onReceive() 返回后終止整個進程。如需了解詳情,請參閱對進程狀態(tài)的影響。要執(zhí)行長時間運行的工作,我們建議:
- 在接收器的
onReceive()方法中調(diào)用 goAsync(),并將 BroadcastReceiver.PendingResult 傳遞給后臺線程。這樣,在從onReceive()返回后,廣播仍可保持活躍狀態(tài)。不過,即使采用這種方法,系統(tǒng)仍希望您非??焖俚赝瓿蓮V播(在 10 秒以內(nèi))。為避免影響主線程,它允許您將工作移到另一個線程。 - 使用 JobScheduler 調(diào)度作業(yè)。如需了解詳情,請參閱智能作業(yè)調(diào)度。
- 在接收器的
請勿從廣播接收器啟動 Activity,否則會影響用戶體驗,尤其是有多個接收器時。相反,可以考慮顯示通知。
參考:
goole官方-廣播概覽
Android四大組件之——廣播
Android 廣播權(quán)限保護
Android 基礎(chǔ)知識3:四大組件之 Broadcast(廣播)