目录
二、BroadcastReceiver事件分类 和 生命周期
四、BroadcastReceiver启动Activity和Service
BroadcastReceiver(广播接收器)是Android中的四大组件之一。
一、概述
下面是Android Doc中关于BroadcastReceiver的概述:
①广播接收器是一个专注于接收广播通知信息,并做出对应处理的组件。很多广播是源自于系统代码的──比如,通知时区改变、电池电量低、拍摄了一张照片或者用户改变了语言选项。应用程序也可以进行广播──比如说,通知其它应用程序一些数据下载完成并处于可用状态。
②应用程序可以拥有任意数量的广播接收器以对所有它感兴趣的通知信息予以响应。所有的接收器均继承自BroadcastReceiver基类。
③广播接收器没有用户界面。然而,它们可以启动一个activity来响应它们收到的信息,或者用NotificationManager来通知用户。通知可以用很多种方式来吸引用户的注意力──闪动背灯、震动、播放声音等等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。
二、BroadcastReceiver事件分类 和 生命周期
- Android中的广播事件有两种
- 一种就是系统广播事件,比如:ACTION_BOOT_COMPLETED(系统启动完成后触发),ACTION_TIME_CHANGED(系统时间改变时触发),ACTION_BATTERY_LOW(电量低时触发)等等。
- 另外一种是我们自定义的广播事件。
- 一个BroadcastReceiver 对象只有在被调用onReceive(Context, Intent)的才有效的,当从该函数返回后,该对象就无效的了,结束生命周期。
- 因此从这个特征可以看出,在所调用的onReceive(Context, Intent)函数里,不能有过于耗时的操作(会弹出Application No Response错误对话框),不能使用线程来执行。对于耗时的操作,请start service来完成。因为当得到其他异步操作所返回的结果时,BroadcastReceiver 可能已经无效了。
- 一个Broadcast receiver只有一个回调方法:void onReceive(Context curContext, Intent broadcastMsg)
- 广播接收者的生命周期是非常短暂的,在接收到广播的时候创建,onReceive()方法结束之后销毁。当Broadcast receiver接收到一条广播信息,android会调用它的onReceive()方法,并传递给它一个包含广播信息的intent对象。当Broadcast receiver在执行这个方法时可以认为它是活动的,onReceive()方法返回时,它便终止了。
- 一个包含活动Broadcast receiver的进程会被系统保护以避免被终止。但是如果进程不包含任何活动组件,那么当它占用的内存要用于其它进程时,系统任何时候都可以终止运行该进程。
- 当广播中启动线程是很危险的,最好也不要在广播接收者中创建子线程做耗时的工作,因为广播接收者被销毁后进程就成为了空进程,很容易被系统杀掉。试想一下,如果onReceive()方法产生一个线程然后返回,那么整个进程,包括新的线程会被认为是不活动的(除非在进程中还有其它活动的组件),该进程就有被系统终止运行的危险。解决这个问题的办法就是在onReceive()方法中启动一个服务,让这个服务做那样的工作,这样系统就会知道进程中有活动的组件而不会停止进程。
三、使用
广播事件的流程
- 注册广播事件:
- 一种是静态注册,就是在AndroidManifest.xml文件中定义,注册的广播接收器必须要继承BroadcastReceiver;
- 另一种是动态注册,是在程序中使用Context.registerReceiver注册,注册的广播接收器相当于一个匿名类。两种方式都需要IntentFIlter。
- 发送广播事件:通过Context.sendBroadcast来发送,由Intent来传递注册时用到的Action。
- sendBroadcast()
- sendOrderedBroadcast()
- sendStickyBroadcast()
- 接收广播事件:当发送的广播被接收器监听到后,会调用它的onReceive()方法,并将包含消息的Intent对象传给它。onReceive中代码的执行时间不要超过5s,否则Android会弹出超时dialog。
3.1 注册广播的两种方式
- 在AndroidManifest.xml中用标签生命注册,并在标签内用标签设置过滤器。
<receiver android:name="myRecevice"> //继承BroadcastReceiver,重写onReceiver方法
<intent-filter>
<action android:name="com.dragon.net"></action> //使用过滤器,接收指定action广播
</intent-filter>
</receiver>
-
在程序中使用 Context.registerReceiver注册,注册的广播接收器相当于一个匿名类。一般:在onStart中注册,onStop中取消unregisterReceiver
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(String); //为BroadcastReceiver指定action,使之用于接收同action的广播
registerReceiver(BroadcastReceiver,intentFilter);
3.2 使用示例
下面我通过代码演示自定义广播事件和系统广播事件的使用。完整代码下载地址:android_broadcastreceiver.rar
Step1:在MainActivity的onStart方法中注册广播事件。静态注册方式是在AndroidManifest.xml文件中。
Step2: 点击相应按钮后会触发相应的方式来发送广播消息。
/**
* MainActivity
* @author zuolongsnail
*
*/
public class MainActivity extends Activity {
private Button sendStaticBtn;
private Button sendDynamicBtn;
private Button sendSystemBtn;
private static final String STATICACTION = "com.byread.static";
private static final String DYNAMICACTION = "com.byread.dynamic";
// USB设备连接
private static final String SYSTEMACTION = Intent.ACTION_POWER_CONNECTED;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
sendStaticBtn = (Button) findViewById(R.id.send_static);
sendDynamicBtn = (Button) findViewById(R.id.send_dynamic);
sendSystemBtn = (Button) findViewById(R.id.send_system);
sendStaticBtn.setOnClickListener(new MyOnClickListener());
sendDynamicBtn.setOnClickListener(new MyOnClickListener());
sendSystemBtn.setOnClickListener(new MyOnClickListener());
}
class MyOnClickListener implements OnClickListener{
@Override
public void onClick(View v) {
// 发送自定义静态注册广播消息
if(v.getId() == R.id.send_static){
Log.e("MainActivity", "发送自定义静态注册广播消息");
Intent intent = new Intent();
intent.setAction(STATICACTION);
intent.putExtra("msg", "接收静态注册广播成功!");
sendBroadcast(intent);
}
// 发送自定义动态注册广播消息
else if(v.getId() == R.id.send_dynamic){
Log.e("MainActivity", "发送自定义动态注册广播消息");
Intent intent = new Intent();
intent.setAction(DYNAMICACTION);
intent.putExtra("msg", "接收动态注册广播成功!");
sendBroadcast(intent);
}
// 发送系统动态注册广播消息。当手机连接充电设备时会由系统自己发送广播消息。
else if(v.getId() == R.id.send_system){
Log.e("MainActivity", "发送系统动态注册广播消息");
Intent intent = new Intent();
intent.setAction(SYSTEMACTION);
intent.putExtra("msg", "正在充电。。。。");
}
}
}
@Override
protected void onStart() {
super.onStart();
Log.e("MainActivity", "注册广播事件");
// 注册自定义动态广播消息
IntentFilter filter_dynamic = new IntentFilter();
filter_dynamic.addAction(DYNAMICACTION);
registerReceiver(dynamicReceiver, filter_dynamic);
// 注册系统动态广播消息
IntentFilter filter_system = new IntentFilter();
filter_system.addAction(SYSTEMACTION);
registerReceiver(systemReceiver, filter_system);
}
private BroadcastReceiver dynamicReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.e("MainActivity", "接收自定义动态注册广播消息");
if(intent.getAction().equals(DYNAMICACTION)){
String msg = intent.getStringExtra("msg");
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
}
};
private BroadcastReceiver systemReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Log.e("MainActivity", "接收系统动态注册广播消息");
if(intent.getAction().equals(SYSTEMACTION)){
String msg = intent.getStringExtra("msg");
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
}
};
}
Step3:接收广播消息。以下为两个静态注册的广播接收器。
/**
* 自定义静态注册广播消息接收器
* @author zuolongsnail
*
*/
public class StaticReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra("msg");
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
}
/**
* 系统静态注册广播消息接收器
*
* @author zuolongsnail
*
*/
public class SystemReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_BATTERY_LOW)) {
Log.e("SystemReceiver", "电量低提示");
Toast.makeText(context, "您的手机电量偏低,请及时充电", Toast.LENGTH_SHORT).show();
}
}
}
下面是AndroidManifest.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.byread" android:versionCode="1" android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".MainActivity" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 注册自定义静态广播接收器 -->
<receiver android:name=".StaticReceiver">
<intent-filter>
<action android:name="com.byread.static" />
</intent-filter>
</receiver>
<!-- 注册系统静态广播接收器 -->
<receiver android:name=".SystemReceiver">
<intent-filter>
<action android:name="android.intent.action.BATTERY_LOW" />
</intent-filter>
</receiver>
</application>
</manifest>
界面布局文件main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="@string/hello" />
<Button android:id="@+id/send_static" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="发送自定义静态注册广播" />
<Button android:id="@+id/send_dynamic" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="发送自定义动态注册广播" />
<Button android:id="@+id/send_system" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:text="发送系统动态注册广播" />
</LinearLayout>
运行界面:
四、BroadcastReceiver启动Activity和Service
先编写一个服务类,这个服务类没什么特别的,仍然使用前面两节编写的MyService类即可。在AndroidManifest.xml文件中配置MyService类的代码也相同。
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class StartupReceiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent)
{
// 启动一个Service
Intent serviceIntent = new Intent(context, MyService.class);
context.startService(serviceIntent);
Intent activityIntent = new Intent(context, MessageActivity.class);
// 要想在Service中启动Activity,必须设置如下标志
activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(activityIntent);
}
}
在StartupReceiver类的onReceive方法中完成了两项工作:启动服务和显示一个Activity来提示服务启动成功。
五、普通广播和有序广播
上面的例子只是一个接收者来接收广播,如果有多个接收者都注册了相同的广播地址,又会是什么情况呢,能同时接收到同一条广播吗,相互之间会不会有干扰呢?
这就涉及到普通广播和有序广播的概念了。
1、Normal Broadcast(普通广播):Normal Broadcast是完全异步的,可以在同一时刻(逻辑上)被所有接收者接收到,消息传递的效率比较高。但缺点是接受者不能将处理结果传递给下一个接收者,并且无法终止Broadcast Intent的广播。
2、Ordered Broadcast(有序广播):Ordered Broadcast的接收者将按预先声明的优先级依次接受Broadcast。如:A的级别高于B、B的级别高于C,那么Broadcast先传给A,再传给B,最后传给C。优先级别声明在<intent-filter.../>元素的android:priority属性中,数越大优先级别越高,取值范围为-1000-1000,优先级别也可以调用IntentFilter对象的setPriority()进行设置。OrderedBroadcast接收者可以终止Broadcast Intent的传播,BroadcastIntent的传播一旦终止,后面的接收者就无法接收到Broadcast。另外,OrderedBroadcast的接收者可以将数据传递给下一个接收者。如:A得到Broadcast后,可以往它的结果对象中存入数据,当Broadcast传给B时,B可以从A的结果对象中得到A存入的数据。
3、context提供的如下两个方法用于发送广播:
sendBroadcast():发送Normal Broadcast
sendOrderedBroadcast():发送OrderedBroadcast。
4、对于OrderedBroadcast而言,系统会根据接收者生命的优先级别顺序逐个执行接收者,优先接收到Broadcast的接收者可以终止Broadcast,调用BroadcastReceiver的abortBroadcast()方法即可终止Broadcast。如果Broadcast被前面的接收者终止,后面的接收者就再也无法获取到Broadcast。
5、不仅如此,对于OrderBroadcast而言,优先接收到Broadcast的接收者可以通过setResultExtras(Bundle)方法将处理结果存入Broadcast中,然后传给下一个接收者,下一个接收者通过代码: Bundle bundle=getResultExtras(true)可以获取上一个接收者存入的数据。
4.1 示例
实例:点击按钮,两个Receiver接收同一条广播,在logcat中打印出数据(按照Receiver的优先顺序,Receiver2先,Receiver1后)
Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.song"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="8" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<activity
android:label="@string/app_name"
android:name=".C48_BroadcastActivity" >
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!--优先级的设定 MyReceiver2大于MyReceiver1,优先级的范围-1000~1000 -->
</activity>
<receiver android:name=".MyReceiver1">
<intent-filter android:priority="200">
<action android:name="com.song.123"/>
</intent-filter>
</receiver>
<receiver android:name=".MyReceiver2">
<intent-filter android:priority="1000">
<action android:name="com.song.123"/>
</intent-filter>
</receiver>
</application>
</manifest>
//发送广播,bundle绑上key为a的数据
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 C48_BroadcastActivity extends Activity {
/** Called when the activity is first created. */
Button button;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
button=(Button)findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent=new Intent("com.song.123");
Bundle bundle=new Bundle();
bundle.putString("a", "aaa");
intent.putExtras(bundle);
//有序广播
sendOrderedBroadcast(intent, null);
}
});
}
}
Receiver1和2
package com.song;
//优先接到广播,bundle绑上key为b的数据
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class MyReceiver2 extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
System.out.println("receiver2");
// context.getSystemService(name);
Bundle bundle=intent.getExtras();
bundle.putString("b", "bbb");
System.out.println("a="+bundle.get("a"));
setResultExtras(bundle);
//切断广播
// abortBroadcast();
}
}
package com.song;
//接收从receiver2传来的广播,包含key为a和b的数据
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class MyReceiver1 extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
System.out.println("receiver1");
//要不要接受上一个广播接收器receiver2传来的的数据
Bundle bundle=getResultExtras(true);
System.out.println("a="+bundle.getString("a")+",b="+bundle.getString("b"));
}
}
根据上面的配置可以看出,该程序包括两个receiver,其中Receiver2高,Receiver1低。如果Receiver2中的Receiver2zhabortBroadcast()注释了,那么程序Receiver1中将可以看到完整的信息了。
六、粘性广播
粘性消息:粘性消息在发送后就一直存在于系统的消息容器里面,等待对应的处理器去处理,如果暂时没有处理器处理这个消息则一直在消息容器里面处于等待状态,粘性广播的Receiver如果被销毁,那么下次重建时会自动接收到消息数据。
- 粘性广播一直存在,可以无限获取
- 这种粘性广播也可以被移除 ,我们可以接收到 广播后调用 removeStickyBroadcast(intent);
注意:普通广播和粘性消息不同被截获,而有序广播是可以被截获的。
在Android系统粘性广播一般用来确保重要的状态改变后的信息被持久保存,并且能随时广播给新的广播接收器,比如电源的改变,因为耗电需要一个过程,前一个过程必须提前得到,否则可能遇到下次刚好接收到的广播后系统自动关机了,随之而来的是kill行为,所以对某些未处理完的任务来说,后果很严重。
- 发送粘性广播需要权限(这里的权限是保存信息的权限和由系统发送未处理的广播的权限)
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
- 发送使用
Intent i = new Intent();
i.setAction(StickyBroadcastReceiver.Action);
i.putExtra("info", "sticky broadcast has been receiver");
sendStickyBroadcast(i);
Log.i("Other","sticky broadcast send ok!");
- 需要知道的是粘性广播是普通广播的一种,因此也可以使用普通广播接收器来接收,当然粘性广播还有另一种常用的接收方式
- 使用普通广播接收器,注意,必须在Manifiest或者发送之前接收(这和定义有点违背,因为这种方式不是正确的接收方式)
- 正确的接收方式不应该使用BroadcastReceiver就可以接收到
IntentFilter intentFilter = new IntentFilter(StickyBroadcastReceiver.Action);
Intent data = registerReceiver(null, intentFilter);//注意,不需要接收器,否则可能无法接收到
if(data!=null&&StickyBroadcastReceiver.Action.equals(data.getAction()))
{
Toast.makeText(this, data.getStringExtra("info"), Toast.LENGTH_SHORT).show();
}
七、开机启动广播
我们经常会有这样的应用场合,比如消息推送服务,需要实现开机启动的功能。要实现这个功能,我们就可以订阅系统“启动完成(BOOT_COMPLETED)”这条广播,接收到这条广播后我们就可以启动自己的服务了;
上面实例其实和开机启动服务差不多了,下面我们在说说开机启动服务。
public class BootCompleteReceiver extends BroadcastReceiver {
private static final String TAG = "BootCompleteReceiver";
@Override
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, MsgPushService.class);
context.startService(service);
Log.i(TAG, "Boot Complete. Starting MsgPushService...");
}
}
<!-- 开机广播接受者 -->
<receiver android:name=".BootCompleteReceiver">
<intent-filter>
<!-- 注册开机广播地址-->
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<!-- 消息推送服务 -->
<service android:name=".MsgPushService"/>