1.Android廣播機制概述
Android廣播分為兩個方面:廣播發(fā)送者和廣播接收者,通常情況下谒亦,BroadcastReceiver指的就是廣播接收者(廣播接收器)。廣播作為Android組件間的通信方式空郊,可以使用的場景如下:
1.同一app內(nèi)部的同一組件內(nèi)的消息通信(單個或多個線程之間)份招;
2.同一app內(nèi)部的不同組件之間的消息通信(單個進(jìn)程);
3.同一app具有多個進(jìn)程的不同組件之間的消息通信狞甚;
4.不同app之間的組件之間消息通信锁摔;
5.Android系統(tǒng)在特定情況下與App之間的消息通信。
從實現(xiàn)原理看上哼审,Android中的廣播使用了觀察者模式谐腰,基于消息的發(fā)布/訂閱事件模型孕豹。因此,從實現(xiàn)的角度來看十气,Android中的廣播將廣播的發(fā)送者和接受者極大程度上解耦励背,使得系統(tǒng)能夠方便集成,更易擴(kuò)展砸西。具體實現(xiàn)流程要點粗略概括如下:
1.廣播接收者BroadcastReceiver通過Binder機制向AMS(Activity Manager Service)進(jìn)行注冊叶眉;
2.廣播發(fā)送者通過binder機制向AMS發(fā)送廣播;
3.AMS查找符合相應(yīng)條件(IntentFilter/Permission等)的BroadcastReceiver籍胯,將廣播發(fā)送到BroadcastReceiver(一般情況下是Activity)相應(yīng)的消息循環(huán)隊列中竟闪;
4.消息循環(huán)執(zhí)行拿到此廣播,回調(diào)BroadcastReceiver中的onReceive()方法杖狼。
對于不同的廣播類型炼蛤,以及不同的BroadcastReceiver注冊方式,具體實現(xiàn)上會有不同蝶涩。但總體流程大致如上理朋。
由此看來,廣播發(fā)送者和廣播接收者分別屬于觀察者模式中的消息發(fā)布和訂閱兩端绿聘,AMS屬于中間的處理中心嗽上。廣播發(fā)送者和廣播接收者的執(zhí)行是異步的,發(fā)出去的廣播不會關(guān)心有無接收者接收熄攘,也不確定接收者到底是何時才能接收到兽愤。顯然,整體流程與EventBus非常類似挪圾。
在上文說列舉的廣播機制具體可以使用的場景中浅萧,現(xiàn)分析實際應(yīng)用中的適用性:
第一種情形:同一app內(nèi)部的同一組件內(nèi)的消息通信(單個或多個線程之間),實際應(yīng)用中肯定是不會用到廣播機制的(雖然可以用)哲思,無論是使用擴(kuò)展變量作用域洼畅、基于接口的回調(diào)還是Handler-post/Handler-Message等方式,都可以直接處理此類問題棚赔,若適用廣播機制帝簇,顯然有些“殺雞牛刀”的感覺,會顯太“重”靠益;
第二種情形:同一app內(nèi)部的不同組件之間的消息通信(單個進(jìn)程)丧肴,對于此類需求,在有些教復(fù)雜的情況下單純的依靠基于接口的回調(diào)等方式不好處理捆毫,此時可以直接使用EventBus等闪湾,相對而言,EventBus由于是針對統(tǒng)一進(jìn)程绩卤,用于處理此類需求非常適合途样,且輕松解耦江醇。可以參見文件《Android各組件/控件間通信利器之EventBus》何暇。
第三陶夜、四、五情形:由于涉及不同進(jìn)程間的消息通信裆站,此時根據(jù)實際業(yè)務(wù)使用廣播機制會顯得非常適宜条辟。下面主要針對Android廣播中的具體知識點進(jìn)行總結(jié)。
2.BroadcastReceiver
自定義BroadcastReceiver
自定義廣播接收器需要繼承基類BroadcastReceivre宏胯,并實現(xiàn)抽象方法onReceive(context,
intent)方法羽嫡。廣播接收器接收到相應(yīng)廣播后,會自動回到onReceive(..)方法肩袍。默認(rèn)情況下杭棵,廣播接收器也是運行在UI線程,因此氛赐,onReceive方法中不能執(zhí)行太耗時的操作魂爪。否則將因此ANR。一般情況下艰管,根據(jù)實際業(yè)務(wù)需求滓侍,onReceive方法中都會涉及到與其他組件之間的交互,如發(fā)送Notification牲芋、啟動service等撩笆。
下面代碼片段是一個簡單的廣播接收器的自定義:
```
publicclassMyBroadcastReceiverextendsBroadcastReceiver {2publicstaticfinalString TAG = "MyBroadcastReceiver";3publicstaticintm = 1;45@Override6publicvoidonReceive(Context context, Intent intent) {7Log.w(TAG, "intent:" +intent);8String name = intent.getStringExtra("name");9Log.w(TAG, "name:" + name + " m=" +m);10m++;1112Bundle bundle =intent.getExtras();1314}15}
BroadcastReceiver注冊類型
BroadcastReceiver總體上可以分為兩種注冊類型:靜態(tài)注冊和動態(tài)注冊。
1).靜態(tài)注冊:
直接在AndroidManifest.xml文件中進(jìn)行注冊缸浦。規(guī)則如下:
其中浇衬,需要注意的屬性
——此broadcastReceiver能否接收其他App的發(fā)出的廣播,這個屬性默認(rèn)值有點意思餐济,其默認(rèn)值是由receiver中有無intent-filter決定的,如果有intent-filter胆剧,默認(rèn)值為true絮姆,否則為false。(同樣的秩霍,activity/service中的此屬性默認(rèn)值一樣遵循此規(guī)則)同時篙悯,需要注意的是,這個值的設(shè)定是以application或者application
user id為界的铃绒,而非進(jìn)程為界(一個應(yīng)用中可能含有多個進(jìn)程)鸽照;
android:name ?—— 此broadcastReceiver類名;
android:permission ?——如果設(shè)置颠悬,具有相應(yīng)權(quán)限的廣播發(fā)送方發(fā)送的廣播才能被此broadcastReceiver所接收矮燎;
android:process ?——broadcastReceiver運行所處的進(jìn)程定血。默認(rèn)為app的進(jìn)程〉猓可以指定獨立的進(jìn)程(Android四大基本組件都可以通過此屬性指定自己的獨立進(jìn)程)
常見的注冊形式有:
其中澜沟,intent-filter由于指定此廣播接收器將用于接收特定的廣播類型。本示例中給出的是用于接收網(wǎng)絡(luò)狀態(tài)改變或開啟啟動時系統(tǒng)自身所發(fā)出的廣播峡谊。當(dāng)此App首次啟動時茫虽,系統(tǒng)會自動實例化MyBroadcastReceiver,并注冊到系統(tǒng)中既们。
之前常說:靜態(tài)注冊的廣播接收器即使app已經(jīng)退出濒析,主要有相應(yīng)的廣播發(fā)出,依然可以接收到啥纸,但此種描述自Android 3.1開始有可能不再成立号杏,具體分析詳見本文后面部分。
2).動態(tài)注冊:
動態(tài)注冊時脾拆,無須在AndroidManifest中注冊組件馒索。直接在代碼中通過調(diào)用Context的registerReceiver函數(shù),可以在程序中動態(tài)注冊BroadcastReceiver名船。registerReceiver的定義形式如下:
1registerReceiver(BroadcastReceiver receiver,IntentFilter filter)
2registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)
典型的寫法示例如下:
1publicclassMainActivityextendsActivity {2publicstaticfinalString BROADCAST_ACTION = "com.example.corn";3privateBroadcastReceiver mBroadcastReceiver;45@Override6protectedvoidonCreate(Bundle savedInstanceState) {7super.onCreate(savedInstanceState);8setContentView(R.layout.activity_main);910mBroadcastReceiver =newMyBroadcastReceiver();11IntentFilter intentFilter =newIntentFilter();12intentFilter.addAction(BROADCAST_ACTION);13registerReceiver(mBroadcastReceiver, intentFilter);14}1516@Override17protectedvoidonDestroy() {18super.onDestroy();19unregisterReceiver(mBroadcastReceiver);20}2122}
注:Android中所有與觀察者模式有關(guān)的設(shè)計中绰上,一旦涉及到register,必定在相應(yīng)的時機需要unregister渠驼。因此蜈块,上例在onDestroy()回到中需要unregisterReceiver(mBroadcastReceiver)。
當(dāng)此Activity實例化時迷扇,會動態(tài)將MyBroadcastReceiver注冊到系統(tǒng)中百揭。當(dāng)此Activity銷毀時,動態(tài)注冊的MyBroadcastReceiver將不再接收到相應(yīng)的廣播蜓席。
3.廣播發(fā)送及廣播類型
經(jīng)常說”發(fā)送廣播“和”接收“器一,表面上看廣播作為Android廣播機制中的實體,實際上這一實體本身是并不是以所謂的”廣播“對象存在的厨内,而是以”意圖“(Intent)去表示祈秕。定義廣播的定義過程,實際就是相應(yīng)廣播”意圖“的定義過程雏胃,然后通過廣播發(fā)送者將此”意圖“發(fā)送出去请毛。被相應(yīng)的BroadcastReceiver接收后將會回調(diào)onReceive()函數(shù)。
下段代碼片段顯示的是一個普通廣播的定義過程瞭亮,并發(fā)送出去方仿。其中setAction(..)對應(yīng)于BroadcastReceiver中的intentFilter中的action。
?Intent intent = new Intent();
?intent.setAction(BROADCAST_ACTION);
?intent.putExtra("name", "qqyumidi");
?sendBroadcast(intent);
根據(jù)廣播的發(fā)送方式,可以將其分為以下幾種類型:
1.Normal Broadcast:普通廣播
2.System Broadcast: 系統(tǒng)廣播
3.Ordered broadcast:有序廣播
4.Sticky Broadcast:粘性廣播(在 android 5.0/api 21中deprecated,不再推薦使用仙蚜,相應(yīng)的還有粘性有序廣播此洲,同樣已經(jīng)deprecated)
5.Local Broadcast:App應(yīng)用內(nèi)廣播
下面分別總結(jié)下各種類型的發(fā)送方式及其特點。
1).Normal Broadcast:普通廣播
此處將普通廣播界定為:開發(fā)者自己定義的intent鳍征,以context.sendBroadcast_"AsUser"(intent, ...)形式黍翎。具體可以使用的方法有:
sendBroadcast(intent)/sendBroadcast(intent,
receiverPermission)/sendBroadcastAsUser(intent,
userHandler)/sendBroadcastAsUser(intent,
userHandler,receiverPermission)。
普通廣播會被注冊了的相應(yīng)的感興趣(intent-filter匹配)接收艳丛,且順序是無序的匣掸。如果發(fā)送廣播時有相應(yīng)的權(quán)限要求,BroadCastReceiver如果想要接收此廣播氮双,也需要有相應(yīng)的權(quán)限碰酝。
2).System Broadcast: 系統(tǒng)廣播
Android系統(tǒng)中內(nèi)置了多個系統(tǒng)廣播,只要涉及到手機的基本操作戴差,基本上都會發(fā)出相應(yīng)的系統(tǒng)廣播送爸。如:開啟啟動,網(wǎng)絡(luò)狀態(tài)改變暖释,拍照袭厂,屏幕關(guān)閉與開啟,點亮不足等等球匕。每個系統(tǒng)廣播都具有特定的intent-filter纹磺,其中主要包括具體的action,系統(tǒng)廣播發(fā)出后亮曹,將被相應(yīng)的BroadcastReceiver接收橄杨。系統(tǒng)廣播在系統(tǒng)內(nèi)部當(dāng)特定事件發(fā)生時,有系統(tǒng)自動發(fā)出照卦。
3)Ordered broadcast:有序廣播
有序廣播的有序廣播中的“有序”是針對廣播接收者而言的式矫,指的是發(fā)送出去的廣播被BroadcastReceiver按照先后循序接收。有序廣播的定義過程與普通廣播無異役耕,只是其的主要發(fā)送方式變?yōu)椋簊endOrderedBroadcast(intent,
receiverPermission, ...)采转。
對于有序廣播,其主要特點總結(jié)如下:
1>多個具當(dāng)前已經(jīng)注冊且有效的BroadcastReceiver接收有序廣播時瞬痘,是按照先后順序接收的氏义,先后順序判定標(biāo)準(zhǔn)遵循為:將當(dāng)前系統(tǒng)中所有有效的動態(tài)注冊和靜態(tài)注冊的BroadcastReceiver按照priority屬性值從大到小排序,對于具有相同的priority的動態(tài)廣播和靜態(tài)廣播图云,動態(tài)廣播會排在前面。
2>先接收的BroadcastReceiver可以對此有序廣播進(jìn)行截斷邻邮,使后面的BroadcastReceiver不再接收到此廣播竣况,也可以對廣播進(jìn)行修改,使后面的BroadcastReceiver接收到廣播后解析得到錯誤的參數(shù)值。當(dāng)然丹泉,一般情況下情萤,不建議對有序廣播進(jìn)行此類操作,尤其是針對系統(tǒng)中的有序廣播摹恨。
4)Sticky Broadcast:粘性廣播(在 android 5.0/api 21中deprecated,不再推薦使用筋岛,相應(yīng)的還有粘性有序廣播,同樣已經(jīng)deprecated)晒哄。
既然已經(jīng)deprecated睁宰,此處不再多做總結(jié)。
5)Local Broadcast:App應(yīng)用內(nèi)廣播(此處的App應(yīng)用以App應(yīng)用進(jìn)程為界)
由前文闡述可知寝凌,Android中的廣播可以跨進(jìn)程甚至跨App直接通信柒傻,且注冊是exported對于有intent-filter的情況下默認(rèn)值是true,由此將可能出現(xiàn)安全隱患如下:
1.其他App可能會針對性的發(fā)出與當(dāng)前App intent-filter相匹配的廣播较木,由此導(dǎo)致當(dāng)前App不斷接收到廣播并處理红符;
2.其他App可以注冊與當(dāng)前App一致的intent-filter用于接收廣播,獲取廣播具體信息伐债。
無論哪種情形预侯,這些安全隱患都確實是存在的。由此峰锁,最常見的增加安全性的方案是:
1.對于同一App內(nèi)部發(fā)送和接收廣播萎馅,將exported屬性人為設(shè)置成false,使得非本App內(nèi)部發(fā)出的此廣播不被接收祖今;
2.在廣播發(fā)送和接收時校坑,都增加上相應(yīng)的permission,用于權(quán)限驗證千诬;
3.發(fā)送廣播時耍目,指定特定廣播接收器所在的包名,具體是通過intent.setPackage(packageName)指定在徐绑,這樣此廣播將只會發(fā)送到此包中的App內(nèi)與之相匹配的有效廣播接收器中邪驮。
App應(yīng)用內(nèi)廣播可以理解成一種局部廣播的形式,廣播的發(fā)送者和接收者都同屬于一個App傲茄。實際的業(yè)務(wù)需求中毅访,App應(yīng)用內(nèi)廣播確實可能需要用到。同時盘榨,之所以使用應(yīng)用內(nèi)廣播時喻粹,而不是使用全局廣播的形式,更多的考慮到的是Android廣播機制中的安全性問題草巡。
相比于全局廣播守呜,App應(yīng)用內(nèi)廣播優(yōu)勢體現(xiàn)在:
1.安全性更高;
2.更加高效。
為此查乒,Android
v4兼容包中給出了封裝好的LocalBroadcastManager類弥喉,用于統(tǒng)一處理App應(yīng)用內(nèi)的廣播問題,使用方式上與通常的全局廣播幾乎相同玛迄,只是注冊/取消注冊廣播接收器和發(fā)送廣播時將主調(diào)context變成了LocalBroadcastManager的單一實例由境。
代碼片段如下:
1//registerReceiver(mBroadcastReceiver, intentFilter);
2//注冊應(yīng)用內(nèi)廣播接收器3localBroadcastManager = LocalBroadcastManager.getInstance(this);
4localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);
56//unregisterReceiver(mBroadcastReceiver);
7//取消注冊應(yīng)用內(nèi)廣播接收器8localBroadcastManager.unregisterReceiver(mBroadcastReceiver);
910Intent intent =newIntent();
11intent.setAction(BROADCAST_ACTION);12intent.putExtra("name", "qqyumidi");
13//sendBroadcast(intent);14//發(fā)送應(yīng)用內(nèi)廣播
15localBroadcastManager.sendBroadcast(intent);
4.不同注冊方式的廣播接收器回調(diào)onReceive(context, intent)中的context具體類型
1).對于靜態(tài)注冊的ContextReceiver,回調(diào)onReceive(context, intent)中的context具體指的是ReceiverRestrictedContext蓖议;
2).對于全局廣播的動態(tài)注冊的ContextReceiver虏杰,回調(diào)onReceive(context, intent)中的context具體指的是Activity Context;
3).對于通過LocalBroadcastManager動態(tài)注冊的ContextReceiver拒担,回調(diào)onReceive(context, intent)中的context具體指的是Application Context嘹屯。
注:對于LocalBroadcastManager方式發(fā)送的應(yīng)用內(nèi)廣播,只能通過LocalBroadcastManager動態(tài)注冊的ContextReceiver才有可能接收到(靜態(tài)注冊或其他方式動態(tài)注冊的ContextReceiver是接收不到的)从撼。
5.不同Android API版本中廣播機制相關(guān)API重要變遷
1).Android5.0/API level 21開始粘滯廣播和有序粘滯廣播過期州弟,以后不再建議使用;
2).”靜態(tài)注冊的廣播接收器即使app已經(jīng)退出低零,主要有相應(yīng)的廣播發(fā)出婆翔,依然可以接收到,但此種描述自Android 3.1開始有可能不再成立“
Android 3.1開始系統(tǒng)在Intent與廣播相關(guān)的flag增加了參數(shù),分別是FLAG_INCLUDE_STOPPED_PACKAGES和FLAG_EXCLUDE_STOPPED_PACKAGES。
FLAG_INCLUDE_STOPPED_PACKAGES:包含已經(jīng)停止的包(停止:即包所在的進(jìn)程已經(jīng)退出)
FLAG_EXCLUDE_STOPPED_PACKAGES:不包含已經(jīng)停止的包
主要原因如下:
自Android3.1開始合砂,系統(tǒng)本身則增加了對所有app當(dāng)前是否處于運行狀態(tài)的跟蹤械姻。在發(fā)送廣播時渤弛,不管是什么廣播類型,系統(tǒng)默認(rèn)直接增加了值為FLAG_EXCLUDE_STOPPED_PACKAGES的flag,導(dǎo)致即使是靜態(tài)注冊的廣播接收器,對于其所在進(jìn)程已經(jīng)退出的app瘟则,同樣無法接收到廣播。
詳情參加Android官方文檔:http://developer.android.com/about/versions/android-3.1.html#launchcontrols
由此枝秤,對于系統(tǒng)廣播醋拧,由于是系統(tǒng)內(nèi)部直接發(fā)出,無法更改此intent flag值淀弹,因此丹壕,3.1開始對于靜態(tài)注冊的接收系統(tǒng)廣播的BroadcastReceiver,如果App進(jìn)程已經(jīng)退出薇溃,將不能接收到廣播菌赖。
但是對于自定義的廣播,可以通過復(fù)寫此flag為FLAG_INCLUDE_STOPPED_PACKAGES沐序,使得靜態(tài)注冊的BroadcastReceiver盏袄,即使所在App進(jìn)程已經(jīng)退出忿峻,也能能接收到廣播,并會啟動應(yīng)用進(jìn)程辕羽,但此時的BroadcastReceiver是重新新建的。
Intent intent =newIntent();
intent.setAction(BROADCAST_ACTION);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
intent.putExtra("name", "qqyumidi");
sendBroadcast(intent);
注1:對于動態(tài)注冊類型的BroadcastReceiver垄惧,由于此注冊和取消注冊實在其他組件(如Activity)中進(jìn)行刁愿,因此,不受此改變影響到逊。
注2:在3.1以前铣口,相信不少app可能通過靜態(tài)注冊方式監(jiān)聽各種系統(tǒng)廣播,以此進(jìn)行一些業(yè)務(wù)上的處理(如即時app已經(jīng)退出觉壶,仍然能接收到脑题,可以啟動service等..),3.1后,靜態(tài)注冊接受廣播方式的改變铜靶,將直接導(dǎo)致此類方案不再可行叔遂。于是,通過將Service與App本身設(shè)置成不同的進(jìn)程已經(jīng)成為實現(xiàn)此類需求的可行替代方案