Android四大组件Broadcast Receiver详解

文章来源:http://www.itnose.net/detail/6030479.html

更多文章:http://www.itnose.net/type/85.html

本文主要讲述了:

一、BroadcastReceiver概述:

二、BroadcastReceiver事件分类

三、BroadcastReceiver事件的编程流程

四、两类BroadcastReceiver

五、普通广播和有序广播

六、Service与BroadcastReceiver如何交互?

七、开机自动运行service

八、BroadcastReceiver的生命周期

一、BroadcastReceiver概述:

1、广播接收器是一个专注于接收广播通知信息,并做出对应处理的组件。很多广播是源自于系统代码的──比如,通知时区改变、电池电量低、拍摄了一张照片或者用户改变了语言选项。应用程序也可以进行广播──比如说,通知其它应用程序一些数据下载完成并处于可用状态。 
2、应用程序可以拥有任意数量的广播接收器以对所有它感兴趣的通知信息予以响应。所有的接收器均继承自BroadcastReceiver基类。 
2、广播接收器没有用户界面。然而,它们可以启动一个activity来响应它们收到的信息,或者用NotificationManager来通知用户。通知可以用很多种方式来吸引用户的注意力──闪动背灯、震动、播放声音等等。一般来说是在状态栏上放一个持久的图标,用户可以打开它并获取消息。 


二、BroadcastReceiver事件分类 

1、系统广播事件,比如:ACTION_BOOT_COMPLETED(系统启动完成后触发),ACTION_TIME_CHANGED(系统时间改变时触发),ACTION_BATTERY_LOW(电量低时触发)等等。 

2、用户自定义的广播事件。 


三、BroadcastReceiver事件的编程流程 

1、注册广播事件:注册方式有两种,

一种是静态注册,就是在 AndroidManifest.xml文件中定义,注册的广播接收器必须要继承BroadcastReceiver类;

在AndroidManifest.xml中用标签生命注册,并在标签内用标签设置过滤器。

<receiver android:name="myRecevice">    //继承BroadcastReceiver,重写onReceiver方法


    <intent-filter>    


      <action android:name="com.dragon.net"></action> //使用过滤器,接收指定action广播


      </intent-filter>


  </receiver>

另一种是动态注册,是在程序中使用 Context.registerReceiver注册,注册的广播接收器相当于一个匿名类。两种方式都需要IntentFIlter。
IntentFilter intentFilter = new IntentFilter();


intentFilter.addAction(String);   //为BroadcastReceiver指定action,使之用于接收同action的广播


registerReceiver(BroadcastReceiver,intentFilter);

  一般:在onStart中注册,onStop中取消unregisterReceiver 

  指定广播目标Action:Intent intent = new Intent(actionString); 

  并且可通过Intent携带消息 :intent.putExtra("msg", "hello,我通过广播发送消息了"); 

发送广播消息:Context.sendBroadcast(intent )

2、发送广播事件:通过Context.sendBroadcast来发送,由Intent来传递注册时用到的Action。 

3、 接收广播事件:当发送的广播被接收器监听到后,会调用它的onReceive()方法,并将包含消息的Intent对象传给它。onReceive中代码的执行时间不要超过5s,否则Android会弹出超时dialog。

四、两类BroadcastReceiver

1、正常广播 Normal broadcasts(用 Context.sendBroadcast()发送)是完全异步的。它们都运行在一个未定义的顺序,通常是在同一时间。这样会更有效,但意味着receiver不能包含所要使用的结果或中止的API。  
2、有序广播 Ordered broadcasts(用 Context.sendOrderedBroadcast()发送)每次被发送到一个receiver。所谓有序,就是每个receiver执行后可以传播到下一个receiver,也可以完全中止传播??不传播给其他receiver。 而receiver运行的顺序可以通过matched intent-filter 里面的android:priority来控制,当priority优先级相同的时候,Receiver以任意的顺序运行。

PS:

下面举例说明了4种情况的广播事件:静态注册的系统广播事件、静态注册的用户自定义广播事件、动态注册的系统广播事件和动态注册的用户自定义广播事件。

1、创建广播接受者

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class MyReceiver extends BroadcastReceiver {
	
	private static final String TAG = "MyReceiver";
	
	@Override
	public void onReceive(Context context, Intent intent) {
		String msg = intent.getStringExtra("msg");
		Log.i(TAG, msg);
	}

}
2、广播注册 

1)静态注册
静态注册是在AndroidManifest.xml文件中配置的,我们就来为MyReceiver注册一个广播地址:

<receiver android:name=".MyReceiver">
        	<intent-filter>
        		<action android:name="android.intent.action.MY_BROADCAST"/>
        		<category android:name="android.intent.category.DEFAULT" />
        	</intent-filter>
        </receiver>
配置了以上信息之后,只要是android.intent.action.MY_BROADCAST这个地址的广播,MyReceiver都能够接收的到。


2)动态注册
动态注册需要在代码中动态的指定广播地址并注册,通常我们是在Activity或Service注册一个广播,下面我们就来看一下注册的代码:

MyReceiver receiver = new MyReceiver();
        
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.MY_BROADCAST");
        
registerReceiver(receiver, filter);

3)发送广播
public void send(View view) {
    	Intent intent = new Intent("android.intent.action.MY_BROADCAST");
    	intent.putExtra("msg", "hello receiver.");
    	sendBroadcast(intent);
    }

PS:

下面简单介绍下系统广播

下面是android系统中定义了很多标准的Broadcast Action来响应系统的广播事件(只列出一部分)

①ACTION_TIME_CHANGED(时间改变时触发)
②ACTION_BOOT_COMPLETED(系统启动完成后触发)--比如有些程序开机后启动就是用这种方式来实现的
③ACTION_PACKAGE_ADDED(添加包时触发)
④ACTION_BATTERY_CHANGED(电量低时触发)

/**
 * 系统静态注册广播消息接收器
 * 
 * @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();
		}
	}
}

<?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=".SystemReceiver">
			<intent-filter>
				<action android:name="android.intent.action.BATTERY_LOW" />
			</intent-filter>
		</receiver>
	</application>
</manifest>

五、普通广播和有序广播

上面的例子只是一个接收者来接收广播,如果有多个接收者都注册了相同的广播地址,又会是什么情况呢,能同时接收到同一条广播吗,相互之间会不会有干扰呢?

这就涉及到普通广播和有序广播的概念了。

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)可以获取上一个接收者存入的数据。

实例:点击按钮,两个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>

activity代码:
//发送广播,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);
			}
		});
        
    }
}
Receiver2
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();     
	}

}

Receiver1
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中将可以看到完整的信息了。

六、Service与BroadcastReceiver如何交互?

我们之前都是先启动了一个Activity,然后在Activity中启动服务。如果是这样,在启动服务时必须要先启动一个Activity。在很多时候这样做有些多余,我们现在可以利用Broadcast Receiver在Android系统启动时运行一个Activity。也许我们会从中得到一些启发,既然可以在Broadcast Receiver中启动Activity,为什么不能启动Service呢?说做就做,现在让我们来验证一下这个想法。

先编写一个服务类,这个服务类没什么特别的,仍然使用前面两节编写的MyService类即可。在AndroidManifest.xml文件中配置MyService类的代码也相同。

下面来完成最关键的一步,就是建立一个BroadcastReceiver,代码如下:

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来提示服务启动成功。 

AndroidManifest.xml文件的完整代码。

<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
    package="net.blogjava.mobile.startupservice"
 android:versionCode="1" 
    android:versionName="1.0"> 
    <application android:icon="@drawable/icon" 
android:label="@string/app_name"> 
        <activity android:name=".MessageActivity" 
android:theme="@android:style/Theme.Dialog"> 
            <intent-filter>                  
                <category android:name="android.
intent.category.LAUNCHER" /> 
            </intent-filter> 
        </activity> 
        <receiver android:name="StartupReceiver"> 
            <intent-filter> 
                <action android:name="android.
intent.action.BOOT_COMPLETED" /> 
                <category android:name="android.
intent.category.LAUNCHER" /> 
            </intent-filter> 
        </receiver> 
        <service android:enabled="true" android:name=".MyService" /> 
    </application> 
    <uses-sdk android:minSdkVersion="3" /> 
    <uses-permission android:name="android.
permission.RECEIVE_BOOT_COMPLETED" /> 
</manifest>

PS:

开机自动运行service

我们经常会有这样的应用场合,比如消息推送服务,需要实现开机启动的功能。要实现这个功能,我们就可以订阅系统“启动完成(BOOT_COMPLETED)”这条广播,接收到这条广播后我们就可以启动自己的服务了;

上面实例其实和开机启动服务差不多了,下面我们在说说开机启动服务。
Receiver :

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...");
	}

}

Service :

public class MsgPushService extends Service {

	private static final String TAG = "MsgPushService";
	
	@Override
	public void onCreate() {
		super.onCreate();
		Log.i(TAG, "onCreate called.");
	}
	
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.i(TAG, "onStartCommand called.");
		return super.onStartCommand(intent, flags, startId);
	}

	@Override
	public IBinder onBind(Intent arg0) {
		return null;
	}
}
AndroidManifest 
<!-- 开机广播接受者 -->
        <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"/>

PS:

最后在说说Broadcast的生命周期

1、一个BroadcastReceiver 对象只有在被调用onReceive(Context, Intent)的才有效的,当从该函数返回后,该对象就无效的了,结束生命周期。

  因此从这个特征可以看出,在所调用的onReceive(Context, Intent)函数里,不能有过于耗时的操作,不能使用线程来执行。对于耗时的操作,请start service来完成。因为当得到其他异步操作所返回的结果时,BroadcastReceiver 可能已经无效了。

2、一个Broadcast receiver只有一个回调方法:

void onReceive(Context curContext, Intent broadcastMsg)

当Broadcast receiver接收到一条广播信息,android会调用它的onReceive()方法,并传递给它一个包含广播信息的intent对象。当Broadcast receiver在执行这个方法时可以认为它是活动的,onReceive()方法返回时,它便终止了。

一个包含活动Broadcast receiver的进程会被系统保护以避免被终止。但是如果进程不包含任何活动组件,那么当它占用的内存要用于其它进程时,系统任何时候都可以终止运行该进程。

当应答一条广播信息十分耗时,而另一个线程必须执行某个任务时会出现一个问题。试想一下,如果onReceive()方法产生一个线程然后返回,那么整个进程,包括新的线程会被认为是不活动的(除非在进程中还有其它活动的组件),该进程就有被系统终止运行的危险。解决这个问题的办法就是在onReceive()方法中启动一个服务,让这个服务做那样的工作,这样系统就会知道进程中有活动的组件而不会停止进程。

相关推荐