廣播機(jī)制簡介
什么是廣播,就是字面意思,我們生活中有很多廣播的例子。Android提供了廣播機(jī)制,便于進(jìn)行系統(tǒng)級別的消息通知。Android中的每個應(yīng)用程序都可以對自己感興趣的廣播進(jìn)行注冊,這樣該程序就只會接收到自己所關(guān)心的廣播內(nèi)容,這些廣播可能來自系統(tǒng),也可能來自其他程序。Android提供了一套完整的API,允許應(yīng)用程序自由的發(fā)送和接受廣播。發(fā)送廣播借助我們第一章里面學(xué)習(xí)的Intent,而接受廣播需要使用廣播接收器(Broadcast Receiver)。
我們先介紹廣播的兩種類型:
-
標(biāo)準(zhǔn)廣播:
一次發(fā)送,完全異步,幾乎所有接收器會同時接到,廣播效率高,但無法截斷。 -
有序廣播:
一次發(fā)送,同步執(zhí)行,接收器有優(yōu)先級順序,同時只有一個接收器能接收到,并且優(yōu)先級高的接收器還可以截斷廣播的傳遞。
接收系統(tǒng)廣播
Android內(nèi)置了很多系統(tǒng)級別的廣播,我們可以在應(yīng)用程序中通過監(jiān)聽這些廣播來得到系統(tǒng)的狀態(tài)信息。系統(tǒng)級別的廣播有一些例子如下:手機(jī)開機(jī)后發(fā)出一條廣播,電池沒電時發(fā)出一條廣播等等,如果想要接收到這類廣播,就需要使用廣播接收器。
廣播接收器可以自由的對自己感興趣的廣播進(jìn)行注冊,當(dāng)有相應(yīng)的廣播發(fā)出時,廣播接收器就能收到該廣播,并處理相應(yīng)邏輯。注冊廣播的方式兩種:
在AM中
靜態(tài)注冊在Java代碼中
動態(tài)注冊
動態(tài)注冊監(jiān)聽網(wǎng)絡(luò)變化
創(chuàng)建一個廣播接收器(后面用BR代替了,Broadcasts Receiver),需要新建一個類,繼承自BroadcastReceiver,并且重寫超類的onReceive()方法。有廣播到來時候,onReceive方法就會被執(zhí)行,具體的邏輯可以在這個方法中處理。
按照書上的例子進(jìn)行學(xué)習(xí),我們通過動態(tài)注冊的方式編寫一個能夠監(jiān)聽網(wǎng)絡(luò)變化的程序,借此學(xué)習(xí)一下BR的基本用法,新建一個BroadcastTest項目,修改MainActivity代碼如下:

動態(tài)注冊的細(xì)節(jié)在注釋中已經(jīng)解釋了很多了,要點就是
一個類重寫onReceive+IntentFilter+intentFilter.addAction指定監(jiān)聽什么廣播+registerReceiver,再次提醒,最后一定要取消注冊unregisterReceiver。
還有一點需要說明,Android為了保護(hù)用戶設(shè)備的安全和隱私,當(dāng)程序進(jìn)行一些敏感操作的時候,必須在AM中配置一下,聲明權(quán)限,否則程序會崩潰。此處獲取網(wǎng)絡(luò)狀態(tài)就是需要聲明的,打開AM,修改如下:

我們運(yùn)行,不要按Back返回,否則會銷毀活動,而是按Home返回到桌面,然后打開settings->Data usage界面,進(jìn)入數(shù)據(jù)使用界面,按下開關(guān)Cellular data,來啟動和禁用網(wǎng)絡(luò),我們看到了設(shè)置的Toast:

當(dāng)然,我們還不滿足于提示網(wǎng)絡(luò)變化了,希望能夠提示網(wǎng)絡(luò)的狀態(tài)是連接還是斷開的。把代碼里面的Toast改一下:

這些接口也沒辦法背下來。。注釋中也解釋了,我們獲取了網(wǎng)絡(luò)信息,然后進(jìn)行了判斷網(wǎng)絡(luò)是否連接。運(yùn)行如下:


靜態(tài)注冊實現(xiàn)開機(jī)啟動
動態(tài)注冊的一個顯著特點是,很靈活,我們可以在代碼中自由控制注冊和注銷。但是有一個缺點:由于注冊邏輯寫在onCreate方法中,必須要程序啟動之后才能接受廣播。下面介紹靜態(tài)注冊方法,可以在沒有程序啟動的情況下,接收廣播。一個典型的例子就是在手機(jī)開機(jī)時候,還沒有任何程序啟動,但是我們希望接收到廣播提示我們開機(jī)了。就簡單實現(xiàn)一個這樣的Toast。
靜態(tài)注冊的執(zhí)行邏輯,同樣需要建立一個繼承自BroadcastReceiver的類,重寫onReceive方法。也就是廣播接收器,不論靜態(tài)動態(tài)都需要接收器。我們使用AS提供的快捷方式建立這個廣播接收器:

廣播接收器名稱我們設(shè)置為BootCompleteReceiver,Exported屬性表示是否允許廣播接收器接收本程序之外的廣播,Enabled屬性表示是否啟用這個廣播接收器。按提示Finish:

得到了一個Java類,繼承自BR,修改onReceive方法如下,讓其提示一個Toast:

靜態(tài)廣播接收器接下來一定要在AM中注冊才能使用。但是AS已經(jīng)幫助我們注冊好了:

就是這個receiver,我們在手動建立Java類去一步一步寫的時候,會需要手動在這里注冊,如果像剛剛那樣,利用AS提供的快捷方式,AS就幫我們完成了這一切。我們看到這個receiver通過android:name指定了是哪一個廣播接收器,而下面兩個屬性,如果沒有忘記的話就是我們剛剛勾選的屬性。接下來還要對AM修改:

在receiver標(biāo)簽里面添加這樣的語句,因為Android系統(tǒng)開機(jī)之后,會發(fā)出一條值為
android.intent.action.BOOT_COMPLETED的廣播,我們這里就是設(shè)置了對應(yīng)的action,BR就會特定的接受發(fā)出這種信息的廣播。然后,監(jiān)聽系統(tǒng)開機(jī)廣播也屬于敏感行為,需要聲明權(quán)限:
要點就是
一個類重寫onReceive+AM中聲明receiver標(biāo)簽+intent-filter標(biāo)簽指定action
重新運(yùn)行,將模擬器打開,讓我一直理解不了的是。。模擬器打開我們也算是開機(jī)行為吧,可是他并沒有提示開機(jī)的那個廣播,只是打開了我們在動態(tài)注冊那一節(jié)用的那個活動。然后關(guān)機(jī)重啟,依舊沒有接收到開機(jī)的Toast,長按那個電源:

也只有Power off按鈕關(guān)機(jī)后,模擬器就消失了,運(yùn)行程序,又是老樣子,會開始那個動態(tài)注冊的活動,而沒有我們靜態(tài)注冊的消息顯示。這難道不算開機(jī)嗎。。問題先留下。不過后來算是找到了如果顯示那條Toast,當(dāng)Power off之后,不要用運(yùn)行程序的那個綠色三角啟動,而是從Tools->AVD Manager點開的頁面中的三角啟動,這個不會觸發(fā)動態(tài)注冊那個活動,正常開機(jī)之后,顯示了那條Toast:

我們的onReceive方法里面還可以執(zhí)行其他的邏輯,這里只是用Toast來演示原理。還要注意,不要再onReceive中添加過多的邏輯或者耗時的操作,這個方法運(yùn)行了較長時間還沒有結(jié)束,程序就會報錯。
這一節(jié)學(xué)習(xí),還可以感受到的是,這塊廣播的設(shè)置,和Intent的顯式隱式那些操作隱隱約約好像是一樣的或者說是相通的。。難怪這一章開始說使用Intent實現(xiàn)的,我們往后學(xué)習(xí)就明白了。
發(fā)送和接收我們自定義的廣播(標(biāo)準(zhǔn)和有序)
剛剛我們所有實驗都是系統(tǒng)廣播,系統(tǒng)的網(wǎng)絡(luò)信息啊,系統(tǒng)的開機(jī)信息等。接下來學(xué)習(xí)如何在應(yīng)用程序發(fā)送自定義的廣播。
發(fā)送標(biāo)準(zhǔn)廣播
還是老樣子,先建立一個類繼承自BR,記得上一節(jié)的話,應(yīng)該使用AS的快捷方法建立。我們這里就用靜態(tài)注冊。
修改代碼如下:

接收到廣播時候,提示一條Toast。接下來指定這個BR能接收什么樣的廣播,在AM中AS為我們建好的receiver標(biāo)簽里面,添加如下:

當(dāng)我們發(fā)送值為上面那樣的廣播時候,接收器就會響應(yīng)。接下來修改activity_main.xml文件中的代碼,添加一個button,作為發(fā)送廣播的觸發(fā)點:

接下來,當(dāng)然是注冊按鈕點擊事件,讓他能夠發(fā)出值為com.example.broadcasttest.MY_BROADCAST的廣播。修改MainActivity如下:

這....難道不是和我們之前學(xué)過的隱式intent很像嗎。。當(dāng)時的SecondActivity中,設(shè)置了能夠響應(yīng)的action。。挺像的。運(yùn)行效果如下,點擊按鈕:

所以Intent不光可以穿梭于活動之間,還能傳遞信息,還能發(fā)送廣播,無所不能。
發(fā)送有序廣播
剛剛那個自定義的廣播,我們在注釋里面提到,一發(fā)送之后,所有標(biāo)簽里設(shè)置的值為發(fā)送的值的廣播接收器會同時接到廣播,就是典型的標(biāo)準(zhǔn)廣播。我們這里來學(xué)習(xí)一下發(fā)送有序廣播。
廣播是一種可以跨進(jìn)程的通信方式,我們在應(yīng)用程序內(nèi)部發(fā)出的廣播,其他應(yīng)用程序也可以接收到,在系統(tǒng)廣播那一節(jié)就可以看到了。為了進(jìn)一步驗證這一點,我們新建BroadcastTest2項目。在里面新建一個廣播接收器AnotherBroadcastReceiver,看這個廣播接收器能不能接收到我們在上個BroadcastTest項目里面發(fā)出的廣播,都是剛剛學(xué)過的,不再解釋了:


我們要驗證的是,廣播是否可以跨進(jìn)程傳播?即當(dāng)回到第一個BroadcastTest項目,按按鈕發(fā)送廣播后,會不會提示received in AnotherBroadcastReceiver的Toast?我們先運(yùn)行一下BroadcastTest2項目,把這個程序安裝在模擬器上面。然后會到BroadcastTest項目按按鈕,效果如下:


屏幕上先后顯示了,這兩個Toast,那也就驗證了,廣播確實可以跨進(jìn)程通信,我們應(yīng)用程序發(fā)送的廣播是可以被其他應(yīng)用程序接收到的。但這里發(fā)送的都是標(biāo)準(zhǔn)廣播(這個例子也是標(biāo)準(zhǔn)廣播,不過由于Toast的屬性,他不能同時顯示在屏幕上,至于哪個先顯示,,目前還沒有學(xué)到原理,先挖個坑),我們下面開始嘗試有序廣播,回到第一個項目,修改MainActivity代碼如下:

只修改了一條,sendBroadcast變成了sendOrderedBroadcast,里面接收兩個參數(shù),第一個是構(gòu)建的Intent對象,第二個是一個與權(quán)限相關(guān)的字符串,此處不做了解,傳入null即可。這是運(yùn)行程序,按按鈕,發(fā)現(xiàn)好像沒有什么區(qū)別,但實際上這是的BR接收已經(jīng)有了先后順序了,前面的接收器還有截斷功能,那么如何設(shè)定先后順序。我們需要在注冊文件AM中修改:

在intent-filter標(biāo)簽欄里面添加android:priority屬性,給廣播接收器設(shè)置了優(yōu)先級,優(yōu)先級更高的廣播接收器可以先收到廣播,默認(rèn)值為0,值越大,優(yōu)先級越高,這樣就可以讓MyBroadcastReceiver先于AnotherBroadcastReceiver收到廣播,而截斷廣播,只需要在高優(yōu)先廣播中調(diào)用abortBroadcast方法即可:

后面低優(yōu)先級的廣播將無法收到廣播。運(yùn)行按下按鈕后,沒有出現(xiàn)AnotherBroadcastReceiver的Toast出現(xiàn)。
所以有序廣播就涉及兩個方法,一個sendOrderedBroadcast,一個abortBroadcast,以及一個屬性android:priority設(shè)置優(yōu)先級。
使用本地廣播
首先明確一下什么叫本地廣播,我們之前介紹的所有廣播都屬于系統(tǒng)全局廣播,發(fā)出的廣播可以被其他任何應(yīng)用程序接收到,并且也可以接收來自于其他任何應(yīng)用程序的廣播(除非有序廣播里面abortBroadcast截斷廣播。)這樣存在一些安全性問題,比如我們發(fā)送的一些攜帶關(guān)鍵信息的廣播有可能被其他應(yīng)用程序捕獲,或者某些程序不斷給我們的廣播接收器發(fā)送垃圾廣播。為了解決這個問題,Android引入了一套本地廣播機(jī)制,使用這個機(jī)制發(fā)出的廣播只能在應(yīng)用程序內(nèi)部進(jìn)行傳遞,廣播接收器也只能接收來自本應(yīng)用程序發(fā)出的廣播,就解決了這個安全性問題。本地廣播的用法可以說和動態(tài)注冊的廣播接收器用法是極其相似的,我們修改MainActivity如下:


我們看到主要就是使用了一個LocalBroadcastManager來對廣播進(jìn)行管理,先使用LocalBroadcastManager.getInstance方法獲取它的一個實例,注冊廣播時候時候使用localBroadcastManager.registerReceiver方法,發(fā)送廣播時候用的localBroadcastManager.sendBroadcast(intent),注銷廣播時候使用localBroadcastManager.unregisterReceiver方法。按鈕中我們構(gòu)造Intent注冊了發(fā)送com.example.broadcasttest.LOCAL_BROADCAST廣播事件,然后在LocalReceiver中addAction方法設(shè)置參數(shù),使其接收這條廣播。運(yùn)行效果如下:

我們再打開BroadcastTest2項目,讓那個接收器去接收com.example.broadcasttest.LOCAL_BROADCAST這條廣播,再回到BroadcastTest項目啟動程序,發(fā)現(xiàn)AnotherReceiver并沒有接收到這條廣播,這就是本地廣播只在本程序內(nèi)部傳播的體現(xiàn)。
本地廣播的注冊方式基本上和動態(tài)注冊一致,那么有沒有靜態(tài)注冊方法呢?沒有,因為靜態(tài)注冊主要為了讓程序在未啟動的情況下也能受到廣播,而本地廣播就是為了讓其只在本程序內(nèi)部傳播,我們的程序在發(fā)送本地廣播的時候肯定已經(jīng)啟動了,因此不能有靜態(tài)注冊方法。
至此,體驗了系統(tǒng)全局廣播,我們可以動態(tài)注冊也可以靜態(tài)注冊,除了系統(tǒng)廣播,我們還可以自定義廣播,自定義的廣播有標(biāo)準(zhǔn)廣播還有有序廣播,以上廣播都存在安全問題。本地廣播則解決了這個缺陷。

