Android App可以接收來(lái)自系統(tǒng)和其他App的廣播消息入录,也可以向它們發(fā)送廣播消息,比較類似于“發(fā)布-訂閱”的設(shè)計(jì)模式佳镜,本文主要介紹廣播的類型僚稿,如何注冊(cè)廣播,如何發(fā)送廣播以及使用廣播需要注意的一些事兒蟀伸。
I. 廣播的分類
無(wú)序廣播
沒(méi)有順序的廣播蚀同,廣播的接收方?jīng)]有嚴(yán)格的順序可言,不可中斷啊掏。有序廣播
在注冊(cè)時(shí)可指定優(yōu)先級(jí)蠢络,優(yōu)先級(jí)高的廣播接收者優(yōu)先收到廣播,優(yōu)先級(jí)以一個(gè)整數(shù)來(lái)標(biāo)識(shí)迟蜜,數(shù)值越大優(yōu)先級(jí)越高刹孔。可中斷娜睛,可再修飾髓霞。粘滯廣播
發(fā)出的廣播會(huì)滯留卦睹,注冊(cè)時(shí)間可晚于發(fā)送時(shí)間,其他功能與無(wú)序廣播相同方库。
II. 注冊(cè)廣播
編寫一個(gè)廣播接收者分预,通常是繼承BroadcastReceiver并重寫onReceive(Context,Intent)方法。
public class MyReceiver extends BroadcastReceiver {
private static final String TAG = "MyReceiver";
@Override
public void onReceive(Context context, Intent intent) {
// TODO: do somethings
// 這個(gè)方法運(yùn)行在主線程下
String action = intent.getAction();
Log.d(TAG, "onReceive: " + action);
}
}
編寫完廣播接收者后薪捍,就需要進(jìn)行注冊(cè)(訂閱)笼痹,告訴系統(tǒng)這個(gè)廣播接收者對(duì)哪些廣播感興趣。注冊(cè)的方式有靜態(tài)注冊(cè)和動(dòng)態(tài)注冊(cè)兩種:
- 在AndroidManifest.xml文件中聲明(靜態(tài)注冊(cè))
<receiver android:name=".MyReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="jdqm.intent.action.TEST"/>
</intent-filter>
</receiver>
- 通過(guò)Java代碼注冊(cè)(動(dòng)態(tài)注冊(cè))
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.BOOT_COMPLETED");
intentFilter.addAction("jdqm.intent.action.TEST");
registerReceiver(myReceiver, intentFilter);
通過(guò)動(dòng)態(tài)注冊(cè)的廣播接收者酪穿,在宿主(注冊(cè)時(shí)所使用的Context)的生命周期期間都是有效的凳干。當(dāng)然你也可以在適當(dāng)?shù)臅r(shí)間調(diào)用unregisterReceiver(BroadcastReceiver)來(lái)解除注冊(cè),這個(gè)“適當(dāng)”取決于具體的業(yè)務(wù)需求被济。例如使用Activity的Context在onCreate(Bundle) 中注冊(cè)的一個(gè)廣播接收者救赐,可以在onDestory()方法回調(diào)時(shí)解除注冊(cè)來(lái)防止廣播接收者泄漏。原則:不重復(fù)注冊(cè)只磷,不泄露经磅。
以上注冊(cè)的廣播接收者對(duì) android.intent.action.BOOT_COMPLETED 和 jdqm.intent.action.TEST 這兩種action的廣播感興趣,后者是自定義的廣播钮追,前者是開機(jī)完成時(shí)由系統(tǒng)發(fā)出(通常自啟動(dòng)的應(yīng)用會(huì)注冊(cè)這個(gè)廣播)预厌,但注冊(cè)這個(gè)廣播須要以下權(quán)限:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
III. 發(fā)送廣播
- 發(fā)送無(wú)序廣播
Intent intent = new Intent("jdqm.intent.action.TEST");
sendBroadcast(intent);
- 發(fā)送有序廣播
Intent intent = new Intent("jdqm.intent.action.TEST");
//第二個(gè)參數(shù)是權(quán)限
sendOrderedBroadcast(intent, null);
- 發(fā)送本地廣播
Intent intent = new Intent("jdqm.intent.action.TEST");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
本地廣播只有本應(yīng)用內(nèi)通過(guò)LocalBroadcastManager.getInstance(this).registerReceiver方法注冊(cè)的廣播接收者能收到,具有更高的安全性元媚,效率也更高(不用跨進(jìn)程通信)轧叽。
- 發(fā)送粘滯廣播
Intent intent = new Intent("jdqm.intent.action.TEST");
sendStickyBroadcast(intent);
這種類型廣播在Android6.0中已經(jīng)被標(biāo)記被過(guò)時(shí), 它有不安全(任何App都能訪問(wèn)), 沒(méi)有保護(hù) (任何App都能修改)等問(wèn)題刊棕。另外發(fā)送這種廣播需要以下權(quán)限
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
IV. 接收順序
- 對(duì)于無(wú)序廣播炭晒,動(dòng)態(tài)注冊(cè)的廣播接收者會(huì)先收到,可以從源碼中得到理論支撐甥角。在BroadcastQueue類中有兩個(gè)集合
//存儲(chǔ)所有動(dòng)態(tài)注冊(cè)的無(wú)序廣播接受者
final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<>();
//存儲(chǔ)所有靜態(tài)注冊(cè)和動(dòng)態(tài)注冊(cè)的有序廣播接收者
final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<>();
然后在其processNextBroadcast(boolean fromMsg)方法中网严,首先是處理了mParallelBroadcasts集合。
- 對(duì)于有序廣播嗤无,優(yōu)先級(jí)高的接受者先收到震束,如果優(yōu)先級(jí)相同,順序就是不確定的翁巍。先收到的接收者可以調(diào)用abortBroadcast()來(lái)中斷此廣播驴一,后續(xù)優(yōu)先級(jí)較低的接受者將無(wú)法收到。除了中斷還可以調(diào)用setResultXxx()方法來(lái)往廣播添加數(shù)據(jù)灶壶,后續(xù)的接收者可以讀取這些數(shù)據(jù)肝断。
IV. 安全性與實(shí)踐
如果你的廣播不需要發(fā)送給本應(yīng)用以外的組件,使用LocalBroadcastManager來(lái)發(fā)送廣播,這樣安全性和效率都比較高
靜態(tài)注冊(cè)有可能造成大量的App啟動(dòng)胸懈,這將會(huì)影響系統(tǒng)的性能担扑,所以盡量使用動(dòng)態(tài)注冊(cè)來(lái)替代靜態(tài)注冊(cè)。這一點(diǎn)Android系統(tǒng)就做出了很好的示范趣钱,比如 CONNECTIVITY_ACTION 這個(gè)廣播只發(fā)送給動(dòng)態(tài)注冊(cè)的廣播接收者涌献。
不在廣播的Intent中包含敏感的信息,因?yàn)橹灰?cè)了這個(gè)廣播就能讀取到這些信息首有。你可以通過(guò)以下3中方式來(lái)獲得一定的安全性燕垃。
- 通過(guò)使用權(quán)限來(lái)發(fā)送廣播,這樣只有聲明了該權(quán)限的應(yīng)用才能收到廣播井联。但是你很難確保你的權(quán)限不被泄漏卜壕。
- Android4.0及以上版本,在發(fā)送廣播的時(shí)候可以通過(guò)setPackage來(lái)指定package(可以指定多個(gè))烙常,這樣只有匹配的package能接受到轴捎。
- 使用LocalBroadcastManager來(lái)發(fā)送本地廣播。
- 當(dāng)你注冊(cè)了一個(gè)廣播蚕脏,意味著任何App都可以給你發(fā)送廣播侦副,以下有三點(diǎn)可以限制接收者:
- 注冊(cè)的時(shí)候增加權(quán)限。
- 在AndroidManifest.xml注冊(cè)receivers時(shí)驼鞭,將android:exported屬性設(shè)為false秦驯。
- 使用LocalBroadcastManager來(lái)注冊(cè)。
action的命名空間是全局的终议,這意味著action有可能會(huì)與其他App沖突汇竭,所以最好是有一個(gè)自己的命名空間葱蝗。
因?yàn)閺V播接收者是運(yùn)行在主線程穴张,它應(yīng)該快速地被執(zhí)行并且return,所以不要在onReveive方法中做比較耗時(shí)的操作两曼。
不要在廣播接收者中啟動(dòng)activitys皂甘,這違背了用戶的使用習(xí)慣,特別是不止一個(gè)接收者時(shí)悼凑。這種情況下可以考慮展示一個(gè)notification來(lái)替代偿枕。
V. 其他
系統(tǒng)廣播的action的完整列表在Android SDK下的 BROADCAST_ACTIONS.TXT,路徑為: Android/sdk/platforms/android-26/data/BROADCAST_ACTIONS.TXT
Android3.1開始户辫,系統(tǒng)的package manager將記錄處于停止?fàn)顟B(tài)的應(yīng)用渐夸。默認(rèn)情況下,靜態(tài)注冊(cè)了廣播的處于“停止”狀態(tài)的應(yīng)用渔欢,是不會(huì)被啟動(dòng)的墓塌,即不會(huì)收到廣播。當(dāng)然你也可以為Intent指定flag來(lái)該變這個(gè)行為:
- FLAG_INCLUDE_STOPPED_PACKAGES:包含處于“停止”狀態(tài)的應(yīng)用;
- FLAG_EXCLUDE_STOPPED_PACKAGES:不包含處于“停止”狀態(tài)的應(yīng)用苫幢;
如果Intent不包含(或都包含)這兩個(gè)flag访诱,則表現(xiàn)形式是包含處于“停止”轉(zhuǎn)態(tài)的應(yīng)用,但是系統(tǒng)默認(rèn)添加了FLAG_EXCLUDE_STOPPED_PACKAGES這個(gè)flag韩肝,這一點(diǎn)在源碼中有所體現(xiàn):
ActivityManagerService#broadcastIntentLocked
final int broadcastIntentLocked(ProcessRecord callerApp,
String callerPackage, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData,
Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) {
intent = new Intent(intent);
// By default broadcasts do not go to stopped apps.
intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
...
}
這就意味著如果你想啟動(dòng)處于“停止”狀態(tài)的應(yīng)用触菜,必須添加FLAG_INCLUDE_STOPPED_PACKAGES這個(gè)flag。那么一個(gè)應(yīng)用在什么情況下會(huì)處于停止?fàn)顟B(tài)哀峻?①應(yīng)用首次安裝并且沒(méi)有啟動(dòng)過(guò)涡相;②被人為地強(qiáng)制停止。開機(jī)完成的廣播就是FLAG_EXCLUDE_STOPPED_PACKAGES這種類型的Intent剩蟀,這意味著如果你的應(yīng)用被停止了漾峡,開機(jī)自啟就會(huì)失效。下一篇文章將從源碼的角度來(lái)分析廣播的工作流程喻旷。