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 { @Override public 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. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); button = (Button) findViewById(R.id.button); button.setOnClickListener(this); } @Override public void onClick(View v) { // 定义一个intent Intent 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 { @Override public 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>
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. */ @Override public 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); } @Override public void onClick(View v) { // 定义一个intent Intent intent = new Intent(); intent.setAction("MY_BROADCAST_ACTION"); intent.putExtra("yaoyao", "yaoyao is 189 days old ,27 weeks -- 2010-08-10"); // 广播出去 sendBroadcast(intent); } @Override protected 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()?
异常也提示了要注销广播。
两种广播注册方式的区别:
静态注册的方法,不管应用处于什么状态,甚至于退出应用,都能接收广播并作相应的处理。当然,相对来说是比较耗电的
动态注册的方法,因为动态注册必须要注销广播,所以,应用退出也就不接收广播了。所以是省电的
有序广播比较特殊,它每次只发送到优先级较高的接收者那里,然后由优先级高的接受者再传播到优先级低的接收者那里,优先级高的接收者有能力终止这个广播
为了演示有序广播的流程,我们创建三个接收者的代码,如下:
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"; @Override public 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"; @Override public 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"; @Override public 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.@FirstBroadcastReceiver 12-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的生命周期很短,也就是onReceive的执行时间,onReceive执行结束,BroadcastReceiver的实例就被销毁(不管那种注册方法)
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 连接已经被断开