Broadcast Receiver用於接收並處理廣播通知(broadcastannouncements)。
多數的廣播是系統發起的,如地區變換、電量不足、來電來信等。
程式可以有任意數量的Broadcast Receivers來響應它覺得重要的通知。
Broadcast Receiver可以通過多種方式通知使用者:啟動activity、使用NotificationManager、開啟背景燈、震動裝置、播放聲音等,最典型的是在狀態列顯示一個表徵圖,這樣使用者就可以點它開啟看通知內容。
通常我們的某個應用或系統本身在某些事件(電池電量不足、來電來簡訊)來臨時會廣播一個Intent出去,我們可以利用註冊一個Broadcast Receiver來監聽到這些Intent並擷取Intent中的資料。
在程式中接受系統發的廣播
我們舉一個例子說明,一個接受系統Date改變的廣播的例子。
先建立一個HelloBroadcastReceiver.java類,繼承自BroadcastReceiver並複寫它的onReceive方法。這樣就建立了一個專門用來接收廣播的類。
package com.tianjf;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.util.Log;public class HelloBroadcastReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {Log.d("debug", "Date has been changed!"); Log.d("debug", ""+intent.getAction()); }}
但是上面的Broadcast Receiver沒有指定接收哪個廣播,我們要指定某個Receiver接收哪個廣播,必須在AndroidManifest.xml中註冊此Receiver
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.tianjf" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="4" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".BroadcastDemoActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- 定義Broadcast Receiver指定監聽的Action(系統Date改變action) --> <receiver android:name="HelloBroadcastReceiver" > <intent-filter> <action android:name="android.intent.action.DATE_CHANGED" /> </intent-filter> </receiver> </application></manifest>
OK,運行之後,更改系統Date看看,果然列印了兩行Log。(著兩行Log有時候打得出來,有時候打不出來,不知道神馬原因)
接收自己發的廣播
直接上代碼
BroadcastDemoActivity.java
package com.tianjf;import android.app.Activity;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;public class BroadcastDemoActivity extends Activity implements OnClickListener {private Button button;/** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);button = (Button) findViewById(R.id.button);button.setOnClickListener(this);}@Overridepublic void onClick(View v) {// 定義一個intentIntent intent = new Intent();intent.setAction("MY_BROADCAST_ACTION");intent.putExtra("yaoyao","yaoyao is 189 days old ,27 weeks -- 2010-08-10");// 廣播出去sendBroadcast(intent);}}
HelloBroadcastReceiver.java
package com.tianjf;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.util.Log;public class HelloBroadcastReceiver extends BroadcastReceiver {@Overridepublic void onReceive(Context context, Intent intent) {Log.d("debug", "" + intent.getAction());if (intent.getAction().equals("android.intent.action.DATE_CHANGED")) {Log.d("debug", "Date has been changed!");Log.d("debug", "" + intent.getAction());} else if (intent.getAction().equals("MY_BROADCAST_ACTION")) {Log.d("debug", "Say Hello to Yaoyao!");Log.d("debug", intent.getStringExtra("yaoyao"));}}}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.tianjf" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="4" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".BroadcastDemoActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- 定義Broadcast Receiver指定監聽的Action(系統Date改變action和自動義的廣播) --> <receiver android:name="HelloBroadcastReceiver" > <intent-filter> <action android:name="MY_BROADCAST_ACTION" /> <action android:name="android.intent.action.DATE_CHANGED" /> </intent-filter> </receiver> </application></manifest>
註冊廣播的2種方式
BroadcastReceiver在沒有註冊廣播位址之前是使用不了的。沒有註冊廣播位址的BroadcastReceiver就像一個缺少選台按鈕的收音機,雖然功能俱備,但也無法收到電台的訊號。有兩種註冊方法:
- 靜態註冊。在AndroidManifest.xml檔案中配置(上文兩個例子已經介紹了)
- 動態註冊。要在代碼中動態指定廣播位址並註冊,通常我們是在Activity或Service註冊一個廣播
靜態註冊的方法參照以上兩個例子。
至於動態註冊的方法,我們看以下例子:
將以上的BroadcastDemoActivity做如下修改
package com.tianjf;import android.app.Activity;import android.content.Intent;import android.content.IntentFilter;import android.os.Bundle;import android.view.View;import android.view.View.OnClickListener;import android.widget.Button;public class BroadcastDemoActivity extends Activity implements OnClickListener {private Button button;HelloBroadcastReceiver receiver;/** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);button = (Button) findViewById(R.id.button);button.setOnClickListener(this);receiver = new HelloBroadcastReceiver();IntentFilter filter = new IntentFilter();filter.addAction("android.intent.action.DATE_CHANGED");filter.addAction("MY_BROADCAST_ACTION");registerReceiver(receiver, filter);}@Overridepublic void onClick(View v) {// 定義一個intentIntent intent = new Intent();intent.setAction("MY_BROADCAST_ACTION");intent.putExtra("yaoyao","yaoyao is 189 days old ,27 weeks -- 2010-08-10");// 廣播出去sendBroadcast(intent);}@Overrideprotected void onDestroy() {super.onDestroy();unregisterReceiver(receiver);}}
注意:動態註冊廣播的方法必須在onDestory方法裡面登出,如果不登出的話,系統會報一個異常(如下)
12-19 07:16:04.186: E/ActivityThread(23244): Activity com.tianjf.BroadcastDemoActivity has leaked IntentReceiver com.tianjf.HelloBroadcastReceiver@40cef150 that was originally registered here. Are you missing a call to unregisterReceiver()?
異常也提示了要登出廣播。
兩種廣播註冊方式的區別:
- 靜態註冊的方法,不管應用處於什麼狀態,甚至於退出應用,都能接收廣播並作相應的處理。當然,相對來說是比較耗電的
- 動態註冊的方法,因為動態註冊必須要登出廣播,所以,應用退出也就不接收廣播了。所以是省電的
有序廣播(Ordered Broadcast)
有序廣播比較特殊,它每次只發送到優先順序較高的接收者那裡,然後由優先順序高的接受者再傳播到優先順序低的接收者那裡,優先順序高的接收者有能力終止這個廣播
為了示範有序廣播的流程,我們建立三個接收者的代碼,如下:
package com.tianjf;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.os.Bundle;import android.util.Log;public class FirstBroadcastReceiver extends BroadcastReceiver {private static final String TAG = "TestOrderdBroadcastReceiver";@Overridepublic void onReceive(Context context, Intent intent) {String msg = intent.getStringExtra("msg");Log.i(TAG, "FirstBroadcastReceiver: " + msg);Bundle bundle = new Bundle();bundle.putString("msg", msg + "@FirstBroadcastReceiver");setResultExtras(bundle);}}
package com.tianjf;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.os.Bundle;import android.util.Log;public class SecondBroadcastReceiver extends BroadcastReceiver {private static final String TAG = "TestOrderdBroadcastReceiver";@Overridepublic void onReceive(Context context, Intent intent) {String msg = getResultExtras(true).getString("msg");Log.i(TAG, "SecondBroadcastReceiver: " + msg);Bundle bundle = new Bundle();bundle.putString("msg", msg + "@SecondBroadcastReceiver");setResultExtras(bundle);}}
package com.tianjf;import android.content.BroadcastReceiver;import android.content.Context;import android.content.Intent;import android.util.Log;public class ThirdBroadcastReceiver extends BroadcastReceiver {private static final String TAG = "TestOrderdBroadcastReceiver";@Overridepublic void onReceive(Context context, Intent intent) {String msg = getResultExtras(true).getString("msg");Log.i(TAG, "ThirdBroadcastReceiver: " + msg);}}
我們注意到,在FirstReceiver和SecondReceiver中最後都使用了setResultExtras方法將一個Bundle對象設定為結果集對象,傳遞到下一個接收者那裡,這樣以來,優先順序低的接收者可以用getResultExtras擷取到最新的經過處理的資訊集合。
之後,我們需要為三個接收者註冊廣播位址,我們修改一下AndroidMainfest.xml檔案:
<receiver android:name=".FirstBroadcastReceiver" > <intent-filter android:priority="1000" > <action android:name="android.intent.action.MY_BROADCAST" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver> <receiver android:name=".SecondBroadcastReceiver" > <intent-filter android:priority="999" > <action android:name="android.intent.action.MY_BROADCAST" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver> <receiver android:name=".ThirdBroadcastReceiver" > <intent-filter android:priority="998" > <action android:name="android.intent.action.MY_BROADCAST" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver>
我們看到,現在這三個接收者的<intent-filter>多了一個android:priority屬性,並且依次減小。這個屬性的範圍在-1000到1000,數值越大,優先順序越高。
現在,我們需要寫一下發送廣播的代碼,如下:
Intent intent = new Intent("android.intent.action.MY_BROADCAST"); intent.putExtra("msg", "hello receiver."); sendOrderedBroadcast(intent, "myAndroid.permission.MY_BROADCAST_PERMISSION");
注意,使用sendOrderedBroadcast方法發送有序廣播時,需要一個許可權參數,如果為null則表示不要求接收者聲明指定的許可權,如果不為null,則表示接收者若要接收此廣播,需聲明指定許可權。這樣做是從安全形度考慮的,例如系統的簡訊就是有序廣播的形式,一個應用可能是具有攔截垃圾簡訊的功能,當簡訊到來時它可以先接受到簡訊廣播,必要時終止廣播傳遞,這樣的軟體就必須聲明接收簡訊的許可權。
所以我們在AndroidMainfest.xml中定義一個許可權:
<permission android:name="myAndroid.permission.MY_BROADCAST_PERMISSION" android:protectionLevel="normal" />
然後聲明使用了此許可權:
<uses-permission android:name="myAndroid.permission.MY_BROADCAST_PERMISSION" />
然後我們點擊發送按鈕發送一條廣播,控制台列印如下:
12-20 08:13:45.978: I/TestOrderdBroadcastReceiver(29914): FirstBroadcastReceiver: hello receiver.12-20 08:13:46.088: I/TestOrderdBroadcastReceiver(29914): SecondBroadcastReceiver: hello receiver.@FirstBroadcastReceiver12-20 08:13:46.138: I/TestOrderdBroadcastReceiver(29914): ThirdBroadcastReceiver: hello receiver.@FirstBroadcastReceiver@SecondBroadcastReceiver
我們看到接收是按照順序的,第一個和第二個都在結果集中加入了自己的標記,並且向優先順序低的接收者傳遞下去。
既然是順序傳遞,試著終止這種傳遞,看一看效果如何,我們修改FirstReceiver的代碼,在onReceive的最後一行添加以下代碼:
abortBroadcast();
然後再次運行程式,控制台列印如下:
12-20 08:13:45.978: I/TestOrderdBroadcastReceiver(29914): FirstBroadcastReceiver: hello receiver.
此次,只有第一個接收者執行了,其它兩個都沒能執行,因為廣播被第一個接收者終止了。
註:只有有序廣播可以用abortBroadcast();來終止,其他廣播是沒有效果的。
BroadcastReceiver的生命週期
BroadcastReceiver的生命週期很短,也就是onReceive的執行時間,onReceive執行結束,BroadcastReceiver的執行個體就被銷毀(不管那種註冊方法)
Broadcast Action 大全
android.intent.action.BATTERY_CHANGED
充電狀態,或者電池的電量發生變化
android.intent.action.BOOT_COMPLETED
在系統啟動後,這個動作被廣播一次(只有一次)
android.intent.action.CFF
語音電話的來電轉接狀態已經改變
android.intent.action.CONFIGURATION_CHANGED
裝置的配置資訊已經改變,參見 Resources.Configuration
android.intent.action.DATA_ACTIVITY
電話的資料活動(data activity)狀態(即收發資料的狀態)已經改變
android.intent.action.DATA_STATE
電話的資料連線狀態已經改變
android.intent.action.DATE_CHANGED
日期被改變
android.server.checkin.FOTA_CANCEL
取消所有被掛起的 (pending) 更新下載
android.server.checkin.FOTA_INSTALL
更新已經被確認,馬上就要開始安裝
android.server.checkin.FOTA_READY
更新已經被下載,可以開始安裝
android.server.checkin.FOTA_RESTART
恢複已經停止的更新下載
android.server.checkin.FOTA_UPDATE
通過 OTA 下載並安裝作業系統更新
android.intent.action.MEDIABUTTON
使用者按下了"Media Button"
android.intent.action.MEDIA_BAD_REMOVAL
擴充介質(擴充卡)已經從 SD 記憶卡插槽拔出,但是掛載點 (mount point) 還沒解除 (unmount)
android.intent.action.MEDIA_EJECT
使用者想要移除擴充介質(拔掉擴充卡)
android.intent.action.MEDIA_MOUNTED
擴充介質被插入,而且已經被掛載
android.intent.action.MEDIA_REMOVED
擴充介質被移除
android.intent.action.MEDIA_SCANNER_FINISHED
已經掃描完介質的一個目錄
android.intent.action.MEDIA_SCANNER_STARTED
開始掃描介質的一個目錄
android.intent.action.MEDIA_SHARED
擴充介質的掛載被解除 (unmount),因為它已經作為 USB 大型存放區被共用
android.intent.action.MEDIA_UNMOUNTED
擴充介質存在,但是還沒有被掛載 (mount)
android.intent.action.MWI
電話的訊息等待(語音信箱)狀態已經改變
android.intent.action.NETWORK_TICKLE_RECEIVED
裝置收到了新的網路 "tickle" 通知
android.intent.action.PACKAGE_ADDED
裝置上新安裝了一個應用程式套件組合
android.intent.action.PACKAGE_REMOVED
裝置上刪除了一個應用程式套件組合
android.intent.action.PHONE_STATE
電話狀態已經改變
android.intent.action.PROVIDER_CHANGED
更新將要(真正)被安裝
android.intent.action.PROVISIONING_CHECK
要求 polling of provisioning service 下載最新的設定
android.intent.action.SCREEN_OFF
螢幕被關閉
android.intent.action.SCREEN_ON
螢幕已經被開啟
android.intent.action.SERVICE_STATE
電話語音的狀態已經改變
android.intent.action.SIG_STR
電話的訊號強度已經改變
android.intent.action.STATISTICS_REPORT
要求 receivers 報告自己的統計資訊
android.intent.action.STATISTICS_STATE_CHANGED
統計資訊服務的狀態已經改變
android.intent.action.TIMEZONE_CHANGED
時區已經改變
android.intent.action.TIME_SET
時間已經改變(重新設定)
android.intent.action.TIME_TICK
目前時間已經變化(正常的時間流逝)
android.intent.action.UMS_CONNECTED
裝置進入 USB 大型存放區模式。
android.intent.action.UMS_DISCONNECTED
裝置從 USB 大型存放區模式退出
android.intent.action.WALLPAPER_CHANGED
系統的牆紙已經改變
android.intent.action.XMPP_CONNECTED
XMPP 串連已經被建立
android.intent.action.XMPP_DI
XMPP 串連已經被斷開