android基础总结篇之五:BroadcastReceiver应用详解

一、概述

broadcastReceiver  顾名思义,广播接收者,他是用来接收来自系统和应用中的广播。

在android系统中,广播体现在方方面面,例如开机完成后,系统会产生一条广播,接收到这条广播就能实现开机启动服务的功能;当网络状态改变时,系统会产生一条广播,接收到这条广播就能及时的作出提示和保存数据等操作;当电池电量改变时,系统会产生一条广播,接收到这条广播就能在电量低时告知用户及时保存进度,等等。

什么是广播?

广播,我的理解就是系统中消息的一个变种;就是当一个事件发生时,如系统濡染断网,系统就会发一个广播消息给所有的接收者,所有的接收者在得到这个消息之后,就知道,现在没有网络了,然后做相关的处理。

广播之间信息的传递是通过Intent对象来传递的;简单地说,Intent调用分为显示调用和隐式调用,由于这里能通知到所有的接收者,肯定不能利用显示调用,只有利用隐式调用Intent对象了。这里说的隐式调用并不是真正意义上的Intent隐式调用,因为Intent隐式调用,当出现很多匹配应用时,会以列表的形式提示用户选择一个启动,而这里不同的地方在于,当有很多匹配项时,会给所有的匹配项都发一个消息,我说隐式调用,只是方便理解。

生命周期:

它不像activity一样复杂,运行原理简单如下:

生命周期只有十秒左右,如果在onReceive()方法内做超过十秒内的事情,就会报错。

每次广播到来时,会重建BroadcastReceiver对象,并且调用onReceive()方法,执行完以后,该对象即被销毁。当onReceive()方法在10秒内没哟执行完毕,android会认为该程序无响应。所以在BroadcastReceiver中不能做一些比较耗时的操作,否则会弹出ANR(application no response)的对话框。

如果需要完成一项比较耗时的工作,应该通过发送Intent给service,由service来完成。这里不能使用子线程来解决。因为生命周期太短,子线程可能还没有结束,BR就先结束了。BR一旦结束,里面的所有进程很容易在系统需要内存的时候优先杀死。因为它属于空进程(没有任何活动组件的进程)。

android的广播机制设计的很出色,很多事情原本需要开发者亲自操作的,现在只需等待广播告知自己就可以了,大大减少了开发的工作量和开发周期。而作为开发者,就需要熟练掌握android系统提供的一个开发利器,就是BroadcastReceiver。下面对其做逐一分析和演练,了解和掌握它的各种功能和用法。

二、注册相关

1、静态注册实例程序

首先,我们演示一下创建BroadcastReceiver,并让这个BroadcastReceiver能够根据我们的需要来运行。

1·构造一个接收器,创建一个BroadcastReceiver对象,我们需要继承android.content.BroadcastReceiver,并实现其onReceive方法,在这个函数中进行处理即可。下面我们创建的名为MyReceiver的广播接收者。

[java] view
plain
 copy

  1. package com.scott.receiver;
  2. import android.content.BroadcastReceiver;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.util.Log;
  6. public class MyReceiver extends BroadcastReceiver {
  7. private static final String TAG = "MyReceiver";
  8. @Override
  9. public void onReceive(Context context, Intent intent) {
  10. String msg = intent.getStringExtra("msg");
  11. Log.i(TAG, msg);
  12. }
  13. }

我们前面说了,广播的传递是靠Intent的,OnReceive的第二个参数,就是广播传过来的Intent,因为后面我们在发送广播的时候,会利用putStringExtra放进去一个标识为msg的字符串,所以我们在这里可以将这个字符串取出来。

在onReceive方法内,我们可以获取随广播而来的Intent中的数据,就像上面的操作,这非常重要,就像无线电一样,包含很多有用的信息。

2·在创建完我们的BroadcastReceiver之后,还不能使它进入工作状态,我们还需要为它注册一个指定的广播地址。没有注册广播地址的BroadcastReceiver就像一个缺少选台按钮的收音机,虽然功能具备,但也无法收到电台的信号。下面我们来介绍如何为BroadcastReceiver注册广播地址。

静态注册

我们这里是通过隐式Intent来发送广播的,我们肯定要匹配这个Intent,在广播这里称之为注册。也就是下面所说的注册。

静态注册就是在androidManifest.xml文件中配置的,我们就来为MyReceive注册一个广播地址:(.表示包名)

  1. <receiver android:name=".MyReceiver">
  2. <intent-filter>
  3. <action android:name="android.intent.action.MY_BROADCAST"/>
  4. <category android:name="android.intent.category.DEFAULT" />
  5. </intent-filter>
  6. </receiver>

配置了以上信息,android:name 对应接收器的类名;我们自定义的类名叫MyReceiver,所以这里写“.MyReceiver”

intent-filter标签里,同样是必须的两项:action 和category;在这里我定义了action的名字,等下隐式发送通过时,就是利用匹配action来接收通知的。只要是android.intent.action.MY_BROADCAST这个地址的广播,MyReceiver都能够接收的到。这种方式的注册时常驻的,也就是当应用关闭后,如果有广播信息传来,MyReceiver也会被系统调用而自动运行。

此时的AndroidManifest.xml全部内容为:

[html] view
plain
 copy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.example.test_brocast_blog"
  4. android:versionCode="1"
  5. android:versionName="1.0" >
  6. <uses-sdk
  7. android:minSdkVersion="14"
  8. android:targetSdkVersion="14" />
  9. <application
  10. android:allowBackup="true"
  11. android:icon="@drawable/ic_launcher"
  12. android:label="@string/app_name"
  13. android:theme="@style/AppTheme" >
  14. <activity
  15. android:name=".MainActivity"
  16. android:label="@string/app_name" >
  17. <intent-filter>
  18. <action android:name="android.intent.action.MAIN" />
  19. <category android:name="android.intent.category.LAUNCHER" />
  20. </intent-filter>
  21. </activity>
  22. <receiver android:name=".MyReceiver">
  23. <intent-filter>
  24. <action android:name="android.intent.action.MY_BROADCAST"/>
  25. <category android:name="android.intent.category.DEFAULT" />
  26. </intent-filter>
  27. </receiver>
  28. </application>
  29. </manifest>

这里需要注意:receiver标签和activity标签的构造完全相同!!!所以,广播是另一种消息是可以讲得通的。

完全相同体现在:

  • 1、所处位置:都直属<application>标签;
  • 2、参数构造基本一样;都有android:name,都有<intent-filter>;
  • 3、都是通过Intent传递参数,也都是通过Intent进行匹配!!!!
  • 可以猜想receiver是activity的一个变种。

最后是发送广播:

我们在主页面加一个button,当点击button是发送广播消息。

布局文件如下:(activity_main.xml)

[html] view
plain
 copy

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools="http://schemas.android.com/tools"
  3. android:layout_width="match_parent"
  4. android:layout_height="match_parent"
  5. tools:context="com.example.test_brocast_blog.MainActivity" >
  6. <Button
  7. android:onClick="send"
  8. android:id="@+id/sent_btn"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:text="发送Broadcast" />
  12. </RelativeLayout>

代码如下:(MainActivity.java)

[java] view
plain
 copy

  1. public class MainActivity extends Activity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. Button btn= (Button)findViewById(R.id.sent_btn);
  7. btn.setOnClickListener(new View.OnClickListener() {
  8. @Override
  9. public void onClick(View v) {
  10. // TODO Auto-generated method stub
  11. send();
  12. }
  13. });
  14. }
  15. public void send() {
  16. Intent intent = new Intent("android.intent.action.MY_BROADCAST");
  17. intent.putExtra("msg", "hello receiver.");
  18. sendBroadcast(intent);
  19. }
  20. }

真正的发送代码在send()函数中,在这个函数中可以看到,我们通过传进去刚才注册的MyRecevier的action,来构造一个隐式Intent,然后利用PutExtra放进去一个额外信息(这个不是必须的,我们仅仅是为了跟踪这个消息传到了哪里去,在《详解Intnent》系统文章中有讲怎样构造一个隐式Intent),与StartActivity不同的是,这里利用的是sendBroadcast(intent)来发送这个Intent;

测试:我们可以根据以上任意一种方法完成注册,当注册完成之后,这个接收者就可以正常工作了。

效果图:

注意,sendBroadcast也是android.content.ContextWrapper类中的方法,它可以将一个指定地址和参数信息的Intent对象以广播的形式发送出去。

2、动态注册实例程序

动态注册

前面说的静态注册,就是利用xml来注册。相反利用代码来注册的就叫动态注册。

静态注册和动态注册是有区别的,主要体现的接收上。

静态注册的程序,无论该程序是否启动,都会当广播到来时接收,并处理。而动态注册的程序只有在程序运行时才会收到广播消息,程序不运行了,它就收不到了。

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

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

同样,首先要生成我们要接收的类的实例,然后利用IntentFilter来声明他可以匹配的广播类型(这里利用动作来匹配),最后利用registerReceiver(receiver,filter); 来注册,即利用当哪种类型的intent广播到来时,要调用MyReceiver类。当然,发送广播之前,要先注册,不然根本没有接收者匹配,当然不注册接收者也不会出现任何错误或警告。

注意,registerReceive是android.content.ContextWrapper类的方法,acticity和service都继承了ContextWrapper,所以可以直接调用。在实际应用中,我们是在activity或service中注册了一个BroadcastReceive,当这个activity或service被销毁时如果没有解除注册,系统会报一个异常,提醒我们是否忘记解除注册了。所以,记得在特定的地方执行解除注册操作:

  1. @Override
  2. protected void onDestroy() {
  3. super.onDestroy();
  4. unregisterReceiver(receiver);
  5. }

执行这样行代码就可以解决问题了。注意,这种注册方式与静态注册相反,不是常驻型的,也就是说广播会跟随程序的生命周期。

下面我们新建一个项目,取名叫:Test_Brocast_Blog_Dynamic

同样,写一个MyRecever类来接收广播,MyRecever类内容不变:

[java] view
plain
 copy

  1. public class MyReceiver extends BroadcastReceiver {
  2. private static final String TAG = "MyReceiver";
  3. @Override
  4. public void onReceive(Context context, Intent intent) {
  5. // TODO Auto-generated method stub
  6. String msg = intent.getStringExtra("msg");
  7. Log.i(TAG, msg);
  8. }
  9. }

然后同样,给MainActivity布局里添加一个Button,当点击Button时发送广播。布局文件与上面一样,这里我只写代码:

[java] view
plain
 copy

  1. public class MainActivity extends Activity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. //在发送之前,确定在代码的某个位置已经动态注册
  7. MyReceiver receiver = new MyReceiver();
  8. IntentFilter filter = new IntentFilter();
  9. filter.addAction("android.intent.action.MY_BROADCAST");
  10. registerReceiver(receiver, filter);
  11. //发送广播
  12. Button btn= (Button)findViewById(R.id.sent_btn);
  13. btn.setOnClickListener(new View.OnClickListener() {
  14. @Override
  15. public void onClick(View v) {
  16. // TODO Auto-generated method stub
  17. send();
  18. }
  19. });
  20. }
  21. public void send() {
  22. Intent intent = new Intent("android.intent.action.MY_BROADCAST");
  23. intent.putExtra("msg", "hello receiver.");
  24. sendBroadcast(intent);
  25. }
  26. @Override
  27. protected void onDestory(){
  28. super.onDestroy() ;
  29. unregisterReceiver(receiver) ;
  30. }
  31. }

在OnCreate()里在注册广播接收者,即告诉系统,当这个广播到来时,用MyReciver来接收。然后点击发送按钮来发送广播。

注:(在运行这个程序之前,先把静态注册的APP装到手机上,这是下面得出结论的前提)

结果如下:

操作界面

结果:

咦?怎么出来两条信息?

也就是说有两个接收者接收到了这条广播,但我们这里只注册了一个MyRecever实例啊。

对的,看应用名称就可以看得出,这是两个不同的应用,有一个是静态注册的(com.example.test_brocast_blog),所以静态注册的程序不管是否启动,都可以收得到匹配的广播的,并对这个广播操作。

如果想试一下,动态注册的代码能不能收到广播,可以反过来一下,运行静态注册的程序,把动态注册的程序关掉,看出来几条Log?答案肯定是一条!因为我们都知道动态注册的代码在程序不运行时是收不到广播的。

三、普通广播与有序广播

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

普通广播 是指大家等级都是一样的,当广播到来时,都能一块接收到,并没有接受的先后顺序。由于是以同接收到的,所以一个接收者是没有办法阻止另一个接收者接收这个广播的。

有序广播 是指按一定的优先级顺序来接收的,优先级高的先收到,并可以对广播进行操作后,再传给下一个接收者,当然也可以不传,如果不穿的话,后面的接收者都收不到这个广播。

普通广播(Normal Broadcast)

(在运行这个程序之前,先把手机上前两个APP全部删除,以免影响结果)

普通广播对于多个接收者来说是完全异步的,通常每个接收者都无需等待即可以接收到广播,接收者相互之间不会有影响。对于这种广播,接收者无法终止广播,即无法阻止其他接收者的接收动作。

为了验证以上论断,我们新建三个BroadcastReceiver,并对他们全部进行静态注册。演示一下这个过程,FirstReceiver、SecondReceiver和ThirdReceiver的代码如下:

[java] view
plain
 copy

  1. package com.scott.receiver;
  2. import android.content.BroadcastReceiver;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.util.Log;
  6. public class FirstReceiver extends BroadcastReceiver {
  7. private static final String TAG = "NormalBroadcast";
  8. @Override
  9. public void onReceive(Context context, Intent intent) {
  10. String msg = intent.getStringExtra("msg");
  11. Log.i(TAG, "FirstReceiver: " + msg);
  12. }
  13. }

[java] view
plain
 copy

  1. public class SecondReceiver extends BroadcastReceiver {
  2. private static final String TAG = "NormalBroadcast";
  3. @Override
  4. public void onReceive(Context context, Intent intent) {
  5. String msg = intent.getStringExtra("msg");
  6. Log.i(TAG, "SecondReceiver: " + msg);
  7. }
  8. }

[java] view
plain
 copy

  1. public class ThirdReceiver extends BroadcastReceiver {
  2. private static final String TAG = "NormalBroadcast";
  3. @Override
  4. public void onReceive(Context context, Intent intent) {
  5. String msg = intent.getStringExtra("msg");
  6. Log.i(TAG, "ThirdReceiver: " + msg);
  7. }
  8. }

注册代码如下:(AndroidManifest.xml)

[html] view
plain
 copy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.example.test_brodcast"
  4. android:versionCode="1"
  5. android:versionName="1.0" >
  6. <uses-sdk
  7. android:minSdkVersion="14"
  8. android:targetSdkVersion="14" />
  9. <application
  10. android:allowBackup="true"
  11. android:icon="@drawable/ic_launcher"
  12. android:label="@string/app_name"
  13. android:theme="@style/AppTheme" >
  14. <activity
  15. android:name=".MainActivity"
  16. android:label="@string/app_name" >
  17. <intent-filter>
  18. <action android:name="android.intent.action.MAIN" />
  19. <category android:name="android.intent.category.LAUNCHER" />
  20. </intent-filter>
  21. </activity>
  22. <!-- 分别注册这三个接收器 -->
  23. <receiver android:name=".ThirdReceiver">
  24. <intent-filter>
  25. <action android:name="android.intent.action.MY_BROADCAST"/>
  26. <category android:name="android.intent.category.DEFAULT" />
  27. </intent-filter>
  28. </receiver>
  29. <receiver android:name=".FirstRecever">
  30. <intent-filter>
  31. <action android:name="android.intent.action.MY_BROADCAST"/>
  32. <category android:name="android.intent.category.DEFAULT" />
  33. </intent-filter>
  34. </receiver>
  35. <receiver android:name=".SecondRecever">
  36. <intent-filter>
  37. <action android:name="android.intent.action.MY_BROADCAST"/>
  38. <category android:name="android.intent.category.DEFAULT" />
  39. </intent-filter>
  40. </receiver>
  41. </application>
  42. </manifest>

同样,我们在MainActivity中添加一个Button,当点击按钮时发送广播,MainActivity代码如下:

[html] view
plain
 copy

  1. public class MainActivity extends Activity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. Button btn= (Button)findViewById(R.id.sent_btn);
  7. btn.setOnClickListener(new View.OnClickListener() {
  8. @Override
  9. public void onClick(View v) {
  10. // TODO Auto-generated method stub
  11. send();
  12. }
  13. });
  14. }
  15. public void send() {
  16. Intent intent = new Intent("android.intent.action.MY_BROADCAST");
  17. intent.putExtra("msg", "hello receiver.");
  18. sendBroadcast(intent);
  19. }
  20. }

然后再次点击发送按钮,发送一条广播,控制台打印如下:

看来这三个接收者都接收到这条广播了,我们稍微修改一下三个接收者,在onReceive方法的最后一行添加以下代码,试图终止广播:

[java] view
plain
 copy

  1. abortBroadcast();

再次点击发送按钮,我们会发现,控制台中三个接收者仍然都打印了自己的日志,表明接收者并不能终止广播。

有序广播(无访问权限版)

有序广播比较特殊,它每次只发送到优先级较高的接收者那里,然后由优先级高的接受者再传播到优先级低的接收者那里,优先级高的接收者有能力终止这个广播。

[html] view
plain
 copy

  1. public class MainActivity extends Activity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. Button btn= (Button)findViewById(R.id.sent_btn);
  7. btn.setOnClickListener(new View.OnClickListener() {
  8. @Override
  9. public void onClick(View v) {
  10. // TODO Auto-generated method stub
  11. send();
  12. }
  13. });
  14. }
  15. public void send() {
  16. Intent intent = new Intent("android.intent.action.MY_BROADCAST");
  17. intent.putExtra("msg", "hello receiver.");
  18. sendOrderedBroadcast(intent, null);  //没有添加权限
  19. }
  20. }

在前面的各个例子中,我们发送广播都是用的:sendBroadcast(intent); 而这里却用的是:sendOrderedBroadcast(intent, null);

对这个函数的官方解释是:

public abstract void sendOrderedBroadcast (Intent intent, String receiverPermission)

Added in API level 1

Broadcast the given intent to all interested BroadcastReceivers, delivering them one at a time to allow more preferred receivers to consume the broadcast before it is delivered to less preferred
receivers. This call is asynchronous; it returns immediately, and you will continue executing while the receivers are run.

See BroadcastReceiver for
more information on Intent broadcasts.

Parameters
intent The Intent to broadcast; all receivers matching this Intent will receive the broadcast.
receiverPermission (optional) String naming a permissions that a receiver must hold in order to receive your broadcast. If null, no permission is required.

其中第二个参数是指定接收者必须拥有的接收权限,如果设为NUll,就是不需要接收权限,所有匹配的Receiver都能接收到。我们这里先不需要权限试试看,下面再举个需要权限的例子。

接收端

我们上面说了,接收端必须是有序的,是有优先级的,这种优先级是在注册时配置的,比如:

[html] view
plain
 copy

  1. <receiver android:name=".FirstRecever">
  2. <intent-filter android:priority="10">
  3. <action android:name="android.intent.action.MY_BROADCAST"/>
  4. <category android:name="android.intent.category.DEFAULT" />
  5. </intent-filter>
  6. lt;/receiver>

与上面静态注册的不同在于,在Intent-filter中添加一个android:priority="10"属性,这个就是接收器优先级,数字越大的接收器,优先级越高,越先接到广播。

我们需要为三个接收者注册广播地址,我们修改一下AndroidMainfest.xml文件:

[html] view
plain
 copy

  1. <receiver android:name=".FirstReceiver">
  2. <intent-filter android:priority="1000">
  3. <action android:name="android.intent.action.MY_BROADCAST"/>
  4. <category android:name="android.intent.category.DEFAULT" />
  5. </intent-filter>
  6. </receiver>
  7. <receiver android:name=".SecondReceiver">
  8. <intent-filter android:priority="999">
  9. <action android:name="android.intent.action.MY_BROADCAST"/>
  10. <category android:name="android.intent.category.DEFAULT" />
  11. </intent-filter>
  12. </receiver>
  13. <receiver android:name=".MyReceiver">
  14. <intent-filter android:priority="998">
  15. <action android:name="android.intent.action.MY_BROADCAST"/>
  16. <category android:name="android.intent.category.DEFAULT" />
  17. </intent-filter>
  18. </receiver>

我们看到,现在这三个接收者的<intent-filter>多了一个android:priority属性,并且依次减小。这个属性的范围在-1000到1000,数值越大,优先级越高。

同样,上面我们三个类FirstRecever、SecondRecever和ThirdReceiver 的代码如下 :

前面我也曾提到,在一个接收器收到发来的Intent后,可以对其进行更改,对发送来的广播Intent进行修改是利用setResultExtras(bundle);
 函数来实现的。

public final void setResultExtras (Bundle extras)

Added in API level 1

Change the current result extras of this broadcast; only works with broadcasts sent through Context.sendOrderedBroadcast.
This is a Bundle holding arbitrary data, whose interpretation is up to the broadcaster. Can be set to null. Calling this method completely replaces the current map (if any).

This method does not work with non-ordered broadcasts such as those sent with Context.sendBroadcast

Parameters
extras The new extra data map; may be null.

翻译一下:

这个函数是用来改变当前广播传来的Extra额外信息的;它只能通过Context.sendOrderedBroadcast.发送过来的广播有效;它使用Bundle来传递任意的数据,而这些数据只有接收器(broadcaster)才能解析。当然也可以把它设置为NULL,这样,它就把传来的数据映射全部清空了。

参数:

extras:新的数据映射,可以为空。

从上面的优先级可以看出,这里三个类的接收顺序是这样的:FirstRecever-》SecondRecever-》ThirdReceiver

下面看看FirstRecever如何利用setResultExtras来改变传来的Msg信息:

[java] view
plain
 copy

  1. package com.scott.receiver;
  2. import android.content.BroadcastReceiver;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.os.Bundle;
  6. import android.util.Log;
  7. public class FirstReceiver extends BroadcastReceiver {
  8. private static final String TAG = "OrderedBroadcast";
  9. @Override
  10. public void onReceive(Context context, Intent intent) {
  11. String msg = intent.getStringExtra("msg");
  12. Log.i(TAG, "FirstReceiver: " + msg);
  13. Bundle bundle = new Bundle();
  14. bundle.putString("msg", msg + "@FirstReceiver");
  15. setResultExtras(bundle);
  16. }
  17. }

下面看看另外两个接收器代码:SecondRecever

[java] view
plain
 copy

  1. public class SecondRecever extends BroadcastReceiver {
  2. private static final String TAG = "MyReceiver";
  3. @Override
  4. public void onReceive(Context context, Intent intent) {
  5. // TODO Auto-generated method stub
  6. //先获得广播过来的MSG
  7. String broadcast_msg = intent.getStringExtra("msg");
  8. Log.i(TAG, "SecondRecever--broadcast_msg:"+broadcast_msg);
  9. //接收通过setResultExtras传过来的msg
  10. String msg = getResultExtras(true).getString("msg");
  11. Log.i(TAG, "SecondReceiver: " + msg);
  12. //修改setResultExtras传来的结果
  13. Bundle bundle = new Bundle();
  14. bundle.putString("msg", msg + "@SecondReceiver");
  15. setResultExtras(bundle);
  16. }
  17. }

这里先通过intent.getStringExtra("msg");  获得广播过来的数据;

然后再利用getResultExtras(true).getString("msg");  获得上一级传过来的setResultExtras(bundle);  里的数据;

最后重新将bundle里的数据中添加"@SecondReceiver"做个标记;

最后,MyReceiver:

[java] view
plain
 copy

  1. public class MyReceiver extends BroadcastReceiver {
  2. private static final String TAG = "OrderedBroadcast";
  3. @Override
  4. public void onReceive(Context context, Intent intent) {
  5. String msg = getResultExtras(true).getString("msg");
  6. Log.i(TAG, "ThirdReceiver: " + msg);
  7. }
  8. }

到这就所有就序了,运行下代码:

从结果可以看出:通过setResultExtras(bundle);  传递的数据是不会更改原生广播的数据的。也只是原来广播数据中额外添加的数据。我们注意到,在FirstReceiver和SecondReceiver中最后都使用了setResultExtras方法将一个Bundle对象设置为结果集对象,传递到下一个接收者那里,这样以来,优先级低的接收者可以用getResultExtras获取到最新的经过处理的信息集合。

有序广播(添加访问权限版)

前面我们看到在sendOrderedBroadcast(intent, null);  中,第二个参数可以设定访问权限,在上个例子中,我们并没有加入访问权限,下面我们就发送一个带权限的广播:

[java] view
plain
 copy

  1. public class MainActivity extends Activity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. Button btn= (Button)findViewById(R.id.sent_btn);
  7. btn.setOnClickListener(new View.OnClickListener() {
  8. @Override
  9. public void onClick(View v) {
  10. // TODO Auto-generated method stub
  11. send();
  12. }
  13. });
  14. }
  15. public void send() {
  16. Intent intent = new Intent("android.intent.action.MY_BROADCAST");
  17. intent.putExtra("msg", "hello receiver.");
  18. sendOrderedBroadcast(intent, "harvic.broadcast.perssion");
  19. }
  20. }

这段代码中,我们利用 sendOrderedBroadcast(intent, "harvic.broadcast.perssion"); 发送一个必须拥有"harvic.broadcast.perssion"权限的接收器才能接收到我们的广播;

注意,使用sendOrderedBroadcast方法发送有序广播时,需要一个权限参数,如果为null则表示不要求接收者声明指定的权限,如果不为null,则表示接收者若要接收此广播,需声明指定权限。这样做是从安全角度考虑的,例如系统的短信就是有序广播的形式,一个应用可能是具有拦截垃圾短信的功能,当短信到来时它可以先接受到短信广播,必要时终止广播传递,这样的软件就必须声明接收短信的权限。

然后我们要在接收器中加入声明使用权限的代码:

首先创建一个"harvic.broadcast.perssion"权限

[java] view
plain
 copy

  1. <permission android:name="harvic.broadcast.perssion" android:protectionLevel="normal"></permission>

然后是底部声明,我们要使用这个权限:

[java] view
plain
 copy

  1. <uses-permission  android:name="harvic.broadcast.perssion"/>

所以总体的代码如下:

[java] view
plain
 copy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3. package="com.example.testbroadcast_order_perssion"
  4. android:versionCode="1"
  5. android:versionName="1.0" >
  6. <uses-sdk
  7. android:minSdkVersion="14"
  8. android:targetSdkVersion="14" />
  9. <permission android:name="harvic.broadcast.perssion" android:protectionLevel="normal"></permission>
  10. <application
  11. android:allowBackup="true"
  12. android:icon="@drawable/ic_launcher"
  13. android:label="@string/app_name"
  14. android:theme="@style/AppTheme" >
  15. <activity
  16. android:name=".MainActivity"
  17. android:label="@string/app_name" >
  18. <intent-filter>
  19. <action android:name="android.intent.action.MAIN" />
  20. <category android:name="android.intent.category.LAUNCHER" />
  21. </intent-filter>
  22. </activity>
  23. <receiver android:name=".FirstRecever" >
  24. <intent-filter android:priority="10">
  25. <action android:name="android.intent.action.MY_BROADCAST"/>
  26. <category android:name="android.intent.category.DEFAULT" />
  27. </intent-filter>
  28. </receiver>
  29. <receiver android:name=".SecondRecever" >
  30. <intent-filter android:priority="9">
  31. <action android:name="android.intent.action.MY_BROADCAST"/>
  32. <category android:name="android.intent.category.DEFAULT" />
  33. </intent-filter>
  34. </receiver>
  35. <receiver android:name=".MyReceiver" >
  36. <intent-filter android:priority="8">
  37. <action android:name="android.intent.action.MY_BROADCAST"/>
  38. <category android:name="android.intent.category.DEFAULT" />
  39. </intent-filter>
  40. </receiver>
  41. <!-- 接收优先级逐级降低 -->
  42. </application>
  43. <!-- 如果不添加使用权限声明,那么接收器会拒绝接受消息的,所以在Log中不会有任何显示 -->
  44. <uses-permission  android:name="harvic.broadcast.perssion"/>
  45. </manifest>

其它接收器代码不变,运行之后:

有个地方要注意:即便像我们现在这样,自己的应用发出广播给自己接收,但如果不声明使用权限,是不能接收到广播的。这点与Activity的权限机制不一样,在Activity中,只要在同一个应用中相互App跳转,是不需要声明使用权限的,权限的限制只针对其它应用调用此Activity。

我们看到接收是按照顺序的,第一个和第二个都在结果集中加入了自己的标记,并且向优先级低的接收者传递下去。

既然是顺序传递,试着终止这种传递,看一看效果如何,我们修改FirstReceiver的代码,在onReceive的最后一行添加以下代码:

[java] view
plain
 copy

  1. abortBroadcast();

然后再次运行程序,控制台打印如下:

此次,只有第一个接收者执行了,其它两个都没能执行,因为广播被第一个接收者终止了。

注意:在OnReceive中保存传过来值的问题:

如果有下面一段伪代码:

class xxxx{

private int mData=1;

public void onReceive(Context context, Intent intent)

Log.d("tag",mData + "");

mData = intent.getIntExtra("intdata",-1);

}

如果我们通过intent传过来的值保存在mData中,在下次再来的时候先打出来mData的值,会发现,每次打出来的都是1,所以根本没有办法通过成员变量保存onReceive中传过来的值。

现将相关源码列表如下:

1、静态注册源码

2、动态注册源码

3、普通接收源码

4、有序广播(无访问权限)源码

5、有序广播(添加访问权限)源码

上面就是BroadcastReceiver的介绍,下面我将会举几个常见的例子加深一下大家对广播的理解和应用:

1.开机启动服务

我们经常会有这样的应用场合,比如消息推送服务,需要实现开机启动的功能。要实现这个功能,我们就可以订阅系统“启动完成”这条广播,接收到这条广播后我们就可以启动自己的服务了。我们来看一下BootCompleteReceiver和MsgPushService的具体实现:

[java] view
plain
 copy

  1. package com.scott.receiver;
  2. import android.content.BroadcastReceiver;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.util.Log;
  6. public class BootCompleteReceiver extends BroadcastReceiver {
  7. private static final String TAG = "BootCompleteReceiver";
  8. @Override
  9. public void onReceive(Context context, Intent intent) {
  10. Intent service = new Intent(context, MsgPushService.class);
  11. context.startService(service);
  12. Log.i(TAG, "Boot Complete. Starting MsgPushService...");
  13. }
  14. }

[java] view
plain
 copy

  1. package com.scott.receiver;
  2. import android.app.Service;
  3. import android.content.Intent;
  4. import android.os.IBinder;
  5. import android.util.Log;
  6. public class MsgPushService extends Service {
  7. private static final String TAG = "MsgPushService";
  8. @Override
  9. public void onCreate() {
  10. super.onCreate();
  11. Log.i(TAG, "onCreate called.");
  12. }
  13. @Override
  14. public int onStartCommand(Intent intent, int flags, int startId) {
  15. Log.i(TAG, "onStartCommand called.");
  16. return super.onStartCommand(intent, flags, startId);
  17. }
  18. @Override
  19. public IBinder onBind(Intent arg0) {
  20. return null;
  21. }
  22. }

然后我们需要在AndroidManifest.xml中配置相关信息:

[html] view
plain
 copy

  1. <!-- 开机广播接受者 -->
  2. <receiver android:name=".BootCompleteReceiver">
  3. <intent-filter>
  4. <!-- 注册开机广播地址-->
  5. <action android:name="android.intent.action.BOOT_COMPLETED"/>
  6. <category android:name="android.intent.category.DEFAULT" />
  7. </intent-filter>
  8. </receiver>
  9. <!-- 消息推送服务 -->
  10. <service android:name=".MsgPushService"/>

我们看到BootCompleteReceiver注册了“android.intent.action.BOOT_COMPLETED”这个开机广播地址,从安全角度考虑,系统要求必须声明接收开机启动广播的权限,于是我们再声明使用下面的权限:

[html] view
plain
 copy

  1. <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

经过上面的几个步骤之后,我们就完成了开机启动的功能,将应用运行在模拟器上,然后重启模拟器,控制台打印如下:

如果我们查看已运行的服务就会发现,MsgPushService已经运行起来了。

2.网络状态变化

在某些场合,比如用户浏览网络信息时,网络突然断开,我们要及时地提醒用户网络已断开。要实现这个功能,我们可以接收网络状态改变这样一条广播,当由连接状态变为断开状态时,系统就会发送一条广播,我们接收到之后,再通过网络的状态做出相应的操作。下面就来实现一下这个功能:

[java] view
plain
 copy

  1. package com.scott.receiver;
  2. import android.content.BroadcastReceiver;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.net.ConnectivityManager;
  6. import android.net.NetworkInfo;
  7. import android.util.Log;
  8. import android.widget.Toast;
  9. public class NetworkStateReceiver extends BroadcastReceiver {
  10. private static final String TAG = "NetworkStateReceiver";
  11. @Override
  12. public void onReceive(Context context, Intent intent) {
  13. Log.i(TAG, "network state changed.");
  14. if (!isNetworkAvailable(context)) {
  15. Toast.makeText(context, "network disconnected!", 0).show();
  16. }
  17. }
  18. /**
  19. * 网络是否可用
  20. *
  21. * @param context
  22. * @return
  23. */
  24. public static boolean isNetworkAvailable(Context context) {
  25. ConnectivityManager mgr = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
  26. NetworkInfo[] info = mgr.getAllNetworkInfo();
  27. if (info != null) {
  28. for (int i = 0; i < info.length; i++) {
  29. if (info[i].getState() == NetworkInfo.State.CONNECTED) {
  30. return true;
  31. }
  32. }
  33. }
  34. return false;
  35. }
  36. }

再注册一下这个接收者的信息:

[html] view
plain
 copy

  1. <receiver android:name=".NetworkStateReceiver">
  2. <intent-filter>
  3. <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
  4. <category android:name="android.intent.category.DEFAULT" />
  5. </intent-filter>
  6. </receiver>

因为在isNetworkAvailable方法中我们使用到了网络状态相关的API,所以需要声明相关的权限才行,下面就是对应的权限声明:

[html] view
plain
 copy

  1. <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

我们可以测试一下,比如关闭WiFi,看看有什么效果。

3.电量变化

如果我们阅读软件,可能是全屏阅读,这个时候用户就看不到剩余的电量,我们就可以为他们提供电量的信息。要想做到这一点,我们需要接收一条电量变化的广播,然后获取百分比信息,这听上去挺简单的,我们就来实现以下:

[java] view
plain
 copy

  1. package com.scott.receiver;
  2. import android.content.BroadcastReceiver;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. import android.os.BatteryManager;
  6. import android.util.Log;
  7. public class BatteryChangedReceiver extends BroadcastReceiver {
  8. private static final String TAG = "BatteryChangedReceiver";
  9. @Override
  10. public void onReceive(Context context, Intent intent) {
  11. int currLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);  //当前电量
  12. int total = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);      //总电量
  13. int percent = currLevel * 100 / total;
  14. Log.i(TAG, "battery: " + percent + "%");
  15. }
  16. }

然后再注册一下广播接地址信息就可以了:

[html] view
plain
 copy

  1. <receiver android:name=".BatteryChangedReceiver">
  2. <intent-filter>
  3. <action android:name="android.intent.action.BATTERY_CHANGED"/>
  4. <category android:name="android.intent.category.DEFAULT" />
  5. </intent-filter>
  6. </receiver>

当然,有些时候我们是要立即获取电量的,而不是等电量变化的广播,比如当阅读软件打开时立即显示出电池电量。我们可以按以下方式获取:

[java] view
plain
 copy

  1. Intent batteryIntent = getApplicationContext().registerReceiver(null,
  2. new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
  3. int currLevel = batteryIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
  4. int total = batteryIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 1);
  5. int percent = currLevel * 100 / total;
  6. Log.i("battery", "battery: " + percent + "%");

BroadCastReceiver 的 API

abortBroadcast ():

这个方法可以截获由 sendOrderedBroadcast () 发送来的 广播,让其它广播接收者无法收到这个广播。

clearAbortBroadcast ()

这个方法是针对上面的 abortBroadcast() 方法的,用于取消截获广播。这样它的下一级广播接收者就能够收到该广播了。

getAbortBroadcast ()

这个方法作用是:判断是否调用了 abortBroadcast (),如果先调用 abortBroadcast (),接着再调用getAbortBroadcast (),将返回 true; 如果在调用 abortBroadcast() 、 clearAbortBroadcast ()

getAbortBroadcast (),将返回 false;

public final boolean getDebugUnregister ()

Since: API Level 1

Return the last value given to setDebugUnregister(boolean) .

getResultCode ()

如果用下面四个方法发送得广播,返回码为: -1 ;

// sendBroadcast(intent);

// sendBroadcast(intent, receiverPermission);

// sendOrderedBroadcast(intent, receiverPermission);

// sendStickyBroadcast(intent);

如果用下面两个方法发送得广播,返回码为:根据你设置 initialCode 的数字是多少就是多少;

// sendStickyOrderedBroadcast(intent, resultReceiver, scheduler,

// initialCode, initialData, initialExtras)

// sendOrderedBroadcast(intent, receiverPermission, resultReceiver,

// scheduler, initialCode, initialData, initialExtras)

getResultData ()

得到发送广播时设置的 initialData 的数据;

getResultExtras(boolean makeMap)

If true then a new empty Map will be made for you if the current Map is null; if false you should be prepared to receive a null Map.

得到由

sendStickyOrderedBroadcast(intent, resultReceiver, scheduler,

// initialCode, initialData, initialExtras) ;

// sendOrderedBroadcast(intent, receiverPermission, resultReceiver,

// scheduler, initialCode, initialData, initialExtras)

中 initialExtras 传入的参数。

实验:我用上面两个方法发了 initialExtras (这个一个 Bundle )传入的参数时,只要不为空,那么 makeMap是否为 true 和 false 都能够得到数据。

isInitialStickyBroadcast ()

Returns true if the receiver is currently processing the initial value of a sticky broadcast -- that is, the value that was last broadcast and is currently held in the sticky cache, so this is not directly the result of a broadcast right now.

如果广播接收者是目前处理的一个宿主的广播的初始值,将返回 true , - 也就是说,这个值是最后的广播出的值,目前正在举行的宿主缓存,所以这并不是直接导致了现在的广播。

实验:在第三个应用中调用这个方法,无论你用哪种方式发送广播,这个方法得到的总是 false ;在发送广播 的resultReceiver 广播接收者里面调用,得到的也是 false ;

isOrderedBroadcast ()

sendStickyOrderedBroadcast(intent, resultReceiver, scheduler,

initialCode, initialData, initialExtras)

上面这个方法发送时,得到的是 true;

判断是否是有序广播;

onReceive (Context context, Intent intent)

public IBinder peekService (Context myContext, Intent service)

Provide a binder to an already-running service. This method is synchronous and will not start the target service if it is not present, so it is safe to call from onReceive.

Parameters:

myContext The Context that had been passed to onReceive(Context, Intent)

service The Intent indicating the service you wish to use. See Context.startService(Intent) for more information.

setDebugUnregister (boolean debug)

Control inclusion of debugging help for mismatched calls to {@ Context#registerReceiver(BroadcastReceiver, IntentFilter) Context.registerReceiver()}. If called with true, before given to registerReceiver(), then the callstack of the following Context.unregisterReceiver()call
is retained, to be printed if a later incorrect unregister call is made. Note that doing this requires retaining information about the BroadcastReceiver for

时间: 2024-10-13 00:39:26

android基础总结篇之五:BroadcastReceiver应用详解的相关文章

Android View 事件分发机制源码详解(View篇)

前言 在Android View 事件分发机制源码详解(ViewGroup篇)一文中,主要对ViewGroup#dispatchTouchEvent的源码做了相应的解析,其中说到在ViewGroup把事件传递给子View的时候,会调用子View的dispatchTouchEvent,这时分两种情况,如果子View也是一个ViewGroup那么再执行同样的流程继续把事件分发下去,即调用ViewGroup#dispatchTouchEvent:如果子View只是单纯的一个View,那么调用的是Vie

java提高篇(九)-----详解匿名内部类

摘自http://blog.csdn.net/chenssy/article/details/13170015 java提高篇(九)-----详解匿名内部类 在Java提高篇-----详解内部类中对匿名内部类做了一个简单的介绍,但是内部类还存在很多其他细节问题,所以就衍生出这篇博客.在这篇博客中你可以了解到匿名内部类的使用.匿名内部类要注意的事项.如何初始化匿名内部类.匿名内部类使用的形参为何要为final. 一.使用匿名内部类内部类 匿名内部类由于没有名字,所以它的创建方式有点儿奇怪.创建格式

Qt on Android: Qt Quick 之 Hello World 图文详解

在上一篇文章,<Qt on Android:QML 语言基础>中,我们介绍了 QML 语言的语法,在最后我们遗留了一些问题没有展开,这篇呢,我们就正式开始撰写 Qt Quick 程序,而那些问题,随着本系列文章的展开也会一一被干掉. 在开始介绍 Qt Quick 应用的基本元素之前,我们先来创建一个 HelloQtQuickApp 项目,就是经典的 Hello World 了. 笔者的教程最终会面向 Qt Quick 与 C++ 混合编程,所以我们 HelloQtQuickApp 从零开始.

ANDROID自定义视图——onMeasure流程,MeasureSpec详解

简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量--onMeasure():决定View的大小 2.布局--onLayout():决定View在ViewGroup中的位置 3.绘制--onDraw():如何绘制这个View. 而第3步的onDraw系统已经封装的很好了,基本不用我们来操心,只需要专注到1,2两个步骤就中好了. 而这篇文章就来谈谈第一步,也是十分关键得一步:"测量(Measure)" Measure(): Measure的中文意思就是测量.所以它的

JAVA学习篇--javaweb之Filter详解

在DRP项目中,多次提到了Filter,它解决了字符集的统一设置以及统一控制简单WebCache,从中我们可以体会到,它给我们带来的好处不仅仅是减少代码量这么简单,它的出现避免了我们每个页面重复的编写相同的代码,减少了我们的工作量,而且给维护带来了极大的便利,那么它是如何实现统一管理的呢?既然它能统一管理某些重复的操作,那么它和AOP有什么关系呢? Filter简介 ServletAPI中提供了一个Filter接口,开发web应用时,如果编写的Java类实现了这个接口,则把这个java类称之为过

J2EE学习篇之--JQuery技术详解

前面我们讲解了的J2EE的技术都是服务端的技术,下面我们来看一下前端的一些开发技术,这一篇我们来看一下jQuery技术 简介: jQuery由美国人John Resig创建,至今已吸引了来自世界各地的众多 javascript高手加入其team. jQuery是继prototype之后又一个优秀的Javascript框架.其宗旨是--WRITE LESS,DO MORE,写更少的代码,做更多的事情. 它是轻量级的js库(压缩后只有21k) ,这是其它的js库所不及的,它兼容CSS3,还兼容各种浏

J2EE学习篇之--Struts2技术详解

前面说到了Struts1的相关知识,下面来说一下Struts2的相关知识,我们知道现在Struts2使用的比Struts1多,Struts2已经替代Struts1成为主流的框架了... 摘要 Struts2是在WebWork2基础发展而来的.和struts1一样, Struts2也属于MVC框架.不过有一点大家需要注意的是:尽管Struts2和Struts1在名字上的差别不是很大,但Struts2和struts1在代码编写风格上几乎是不一样的.那么既然有了struts1,为何还要推出struts

Android中自定义View、ViewGroup理论基础详解

Android自身提供了许多widgets,但是有时候这些widgets并不能满足我们的需求,这时我们就需要自定义View,本文会详细说明自定义View的各种理论基础,只有理解了这些知识,我们才能更好地实现各种功能的控件. 我觉得自定义View中最重要的部分就是绘图和交互,自定义的绘图使得你的View与众不同,交互使用户可以与你的View进行交互,而绘图的前提是View的量算与布局,交互的基础是触摸事件,所以量算.布局.绘图.触摸事件这些是自定义View的核心. 除此之外,一个设计友好的自定义V

LevelDb日知录之五:MemTable详解

[LevelDb日知录之五:MemTable详解] LevelDb日知录前述小节大致讲述了磁盘文件相关的重要静态结构,本小节讲述内存中的数据结构Memtable,Memtable在整个体系中的重要地位也不言而喻.总体而言,所有KV数据都是存储在Memtable,Immutable Memtable和SSTable中的,Immutable Memtable从结构上讲和Memtable是完全一样的,区别仅仅在于其是只读的,不允许写入操作,而Memtable则是允许写入和读取的.当Memtable写入