广播接受者是作为系统的监听者存在着的,它可以监听系统或系统中其他应用发生的事件来做出响应。如设备开机时,应用要检查数据的变化状况,此时就可以通过广播来把消息通知给用户。又如网络状态改变时,电量变化时都可以通过广播来通知用户。要做比喻的话,广播就像是我们的感官,能够有效且快速的从外界获取信息来反馈给自身。

一、广播的功能和特征

  1. 广播的生命周期很短,经过 调用对象—实现onReceive—结束 整个过程就结束了。从实现的复杂度和代码量来看,广播无疑是最迷你的Android 组件,实现往往只需几行代码。广播对象被构造出来后通常只执行BroadcastReceiver.onReceive方法,便结束了其生命周期。所以有的时候我们可以把它当做函数看也未必不可。
  2. 和所有组件一样,广播对象也是在应用进程的主线程中被构造,所以广播对象的执行必须是要同步且快速的。也不推荐在里面开子线程,因为往往线程还未结束,广播对象就已经执行完毕被系统销毁。如果需要完成一项比较耗时的工作 , 应该通过发送 Intent 给 Service, 由 Service 来完成。
  3. 每次广播到来时 , 会重新创建 BroadcastReceiver 对象 , 并且调用 onReceive() 方法 , 执行完以后 , 该对象即被销毁 . 当 onReceive() 方法在 10 秒内没有执行完毕, Android 会认为该程序无响应。

二、广播事件监听的两种方法

使用广播进行事件监听有两种方法,静态注册和动态注册,又或者称冷插拔和热插拔。静态注册就是将广播接收器的相关信息写在应用的配置文件中。当有广播事件发生时,组件管理服务就会从安装包管理服务中获取已安装应用的广播组件信息。动态注册则是通过Context.registerReceiver和Context.unregisterRecever,动态将广播接收器与所需要监听的事件绑定。

  1. 静态注册
    首先是在应用的配置文件中写入注册消息,同样是跟其他组件一样写在application标签之内

    <receiver android:name=".ColdReceiver"><!-- 你的Receiver名称 -->
    <intent-filter>
    <action android:name="android.intent.action.COLD_BROADCAST"/> <!-- 你广播要接受的intent名称 -->
    <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    </receiver>

    然后我们建一个ColdReceiver的类,继承BroadcastReceiver,里面代码如下

    public class ColdReceiver extends BroadcastReceiver {
    
        @Override
    public void onReceive(Context context, Intent intent) {
    //跳转到service中
    intent = new Intent("android.intent.action.BroadcastService");
    intent.addFlags(1);
    //开启service
    context.startService(intent);
    //日志打印
    Log.d("TEST","静态注册");
    } }

    上面的Service启动看到了吗,这里我用了上次说过的快捷跳转。其中service的配置和内容如下

    <service android:name=".BroadcastService"><!-- 你自定义的service文件   (在<application></application>里面加)-->
    <intent-filter>
    <action android:name="android.intent.action.BroadcastService" /><!-- 用intent启动时的快捷名(也可以用常规的方式启动) -->
    <category android:name="android.intent.category.default" />
    </intent-filter>
    </service>
     public class BroadcastService extends Service{
    
         @Override
    public IBinder onBind(Intent intent) {
    // TODO Auto-generated method stub
    return null;
    } @Override
    public void onCreate() {
    //开启服务时会首先调用该方法
    super.onCreate();
    } @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
    //根据每次intent传过来的信息进行判断来显示不同信息
    switch(intent.getFlags()){
    case 1:{
    Toast.makeText(getApplicationContext(), "静态注册", Toast.LENGTH_SHORT).show();
    break;
    }
    case 2:{
    Toast.makeText(getApplicationContext(), "动态注册", Toast.LENGTH_SHORT).show();
    break;
    }
    case 3:{
    Toast.makeText(getApplicationContext(), "普通广播", Toast.LENGTH_SHORT).show();
    break;
    }
    case 4:{
    Toast.makeText(getApplicationContext(), "有序广播", Toast.LENGTH_SHORT).show();
    break;
    }
    }
    return START_STICKY;
    } @Override
    public void onDestroy() {
    // 停止service后会调用此方法
    Log.d("TEST", "destroy");
    super.onDestroy();
    } }

    那么静态广播的创建就完成了,简单吧,就两个步骤,一是配置广播,二是继承BroadcastReceiver,重写里面的onReceive函数。
    接下来我们在新建一个工程来检测广播是否可以响应消息。(检测应用的代码全部都在下方,并且不用再做更改了)

     public class MainActivity extends Activity implements OnClickListener{
    
         private Button b1,b2,b3,b4;
    private Intent intent;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    intent = new Intent();
    //获得界面的控件
    b1 = (Button) findViewById(R.id.button1);
    b1.setOnClickListener(this);
    b2 = (Button) findViewById(R.id.button2);
    b2.setOnClickListener(this);
    b3 = (Button) findViewById(R.id.button3);
    b3.setOnClickListener(this);
    b4 = (Button) findViewById(R.id.button4);
    b4.setOnClickListener(this);
    Log.d("TEST","===初始化完成===");
    } @Override
    public void onClick(View v) {
    // TODO Auto-generated method stub
    switch(v.getId()){
    case R.id.button1:{//发送到静态注册广播
    intent = new Intent("android.intent.action.COLD_BROADCAST");
    sendBroadcast(intent);
    //intent.putExtra("msg", "hello coldreceiver.");
    break;
    }
    case R.id.button2:{//发送到动态注册广播
    intent = new Intent("android.intent.action.HOT_BROADCAST");
    //intent.putExtra("msg", "hello hotreceiver.");
    sendBroadcast(intent);
    break;
    }
    case R.id.button3:{//普通广播
    intent = new Intent("android.intent.action.NORMAL_BROADCAST");
    sendBroadcast(intent);
    break;
    }
    case R.id.button4:{//有序广播
    intent = new Intent("android.intent.action.SORT_BROADCAST");
    sendOrderedBroadcast(intent, "scott.permission.SORT_BROADCAST_PERMISSION");
    break;
    }
    }
    } public void show(String str){
    Toast.makeText(this, str, Toast.LENGTH_LONG).show();
    } @Override
    protected void onDestroy() {
    // TODO Auto-generated method stub
    super.onDestroy();
    }
    }

    ok,将两个应用都安装到设备上,启动测试用的应用,点击第一个按钮,运行的效果如下

    同时,会出现"静态注册"的Toast(不方便截图)。可以看出静态注册广播能够跨应用来响应信息,这都要归功于安卓上的组件管理服务,它会读取每个应用的配置文件,然后获取里面的组件信息,每当有消息响应时,组件管理服务会从中查找有没有需要调用的组件,并判断是否进行执行

  2. 动态注册
    动态注册也可以分成两部分,一在代码中进行动态注册,二还是继承BroadcastReceiver,重写里面的onReceive函数。
    我们在广播应用中新建一个HotReceiver,继承BroadcastReceiver
    public class HotReceiver extends BroadcastReceiver {
    
        @Override
    public void onReceive(Context context, Intent intent) {
    //String msg = intent.getStringExtra("msg");
    intent = new Intent("android.intent.action.BroadcastService");
    intent.addFlags(2);
    context.startService(intent);
    Log.d("TEST","动态注册");
    } }

    在Activity中进行动态注册

    public class MainActivity extends Activity {
    
        private HotReceiver receiver;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main); //动态注册广播
    //新建一个广播对象
    receiver = new HotReceiver();
    //新建一个intent管理机制,(功能是对组件进行过滤,只获取需要的消息)
    IntentFilter filter = new IntentFilter();
    //添加白名单(只获取该动作的信息)
    filter.addAction("android.intent.action.HOT_BROADCAST");
    //与广播绑定,进行注册
    registerReceiver(receiver, filter);
    } @Override
    protected void onDestroy() {
    //取消注册,一定要记得,不然系统会报错误
    unregisterReceiver(receiver);
    stopService(new Intent("android.intent.action.BroadcastService"));
    super.onDestroy();
    } }

    ok,再使用测试应用来检查一下效果,注意步骤,安装好广播应用打开,不要让它退出,切换到测试用的广播,点击第二个按钮。

    测试成功。那么我们关掉广播应用在测试一下,会发现不会再出现动态注册的打印消息。这说明动态注册的广播是与Activity绑定的,当Activity销毁时,广播也会被销毁。
    在Android中,很多时候最好是使用动态注册的方式使用广播,比如时间变化事件,电量变更事件等,这些事件触发率太高,如果使用静态注册,会导致进程频繁的被构造和销毁从而影响整个系统的效率。

三、广播的两种类型

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

    public class NormalReceiver1 extends BroadcastReceiver {
    
        @Override
    public void onReceive(Context context, Intent intent) {
    intent = new Intent("android.intent.action.BroadcastService");
    intent.addFlags(3);
    context.startService(intent);
    Log.d("TEST","普通广播1");
    abortBroadcast();
    } }
    public class NormalReceiver2 extends BroadcastReceiver {
    
        @Override
    public void onReceive(Context context, Intent intent) {
    intent = new Intent("android.intent.action.BroadcastService");
    intent.addFlags(3);
    context.startService(intent);
    Log.d("TEST","普通广播2");
    abortBroadcast();
    } }
    public class NormalReceiver3 extends BroadcastReceiver {
    
        @Override
    public void onReceive(Context context, Intent intent) {
    intent = new Intent("android.intent.action.BroadcastService");
    intent.addFlags(3);
    context.startService(intent);
    Log.d("TEST","普通广播3");
    abortBroadcast();
    } }
    <receiver android:name=".NormalReceiver1">
    <intent-filter>
    <action android:name="android.intent.action.NORMAL_BROADCAST"/>
    <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    </receiver>
    <receiver android:name=".NormalReceiver2">
    <intent-filter>
    <action android:name="android.intent.action.NORMAL_BROADCAST"/>
    <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    </receiver>
    <receiver android:name=".NormalReceiver3">
    <intent-filter>
    <action android:name="android.intent.action.NORMAL_BROADCAST"/>
    <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    </receiver>

    Xml Code

    安装完毕后,我们点击检测应用的第三个按钮,可以看到

    abortBroadcast()作用是阻断广播向下一级传播,显然在这里不起作用,并会让系统报错。所以如果要让广播有一定的优先级进行传播就要使用到有序广播。

  2. 有序广播
    有序广播通过调用sendOrderedBroadcast函数进行发送。它每次只发送到优先级较高的接收者那里,然后由优先级高的接受者再传播到优先级低的接收者那里,优先级高的接收者有能力终止这个广播。在有序广播的传递过程中,每个执行中的触发器组件都可以通过BroadcastReceiver.setResult等函数附加额外的数据,而下一个广播则可以使用这些数据(BroadcastReceiver.getResultData)。这样可以构成一个消息数据处理链。
    为了保证某一事件一定会被处理,可以指明默认的广播接收器(Final Receiver)。
    一样的,新建三个广播样例
    public class SortReceiver1 extends BroadcastReceiver {
    
        @Override
    public void onReceive(Context context, Intent intent) {
    // TODO Auto-generated method stub
    //String msg = intent.getStringExtra("msg");
    intent = new Intent("android.intent.action.BroadcastService");
    intent.addFlags(4);
    context.startService(intent);
    Log.d("TEST","有序广播1");
    abortBroadcast();
    } }
    public class SortReceiver2 extends BroadcastReceiver {
    
        @Override
    public void onReceive(Context context, Intent intent) {
    // TODO Auto-generated method stub
    //String msg = intent.getStringExtra("msg");
    intent = new Intent("android.intent.action.BroadcastService");
    intent.addFlags(4);
    context.startService(intent);
    Log.d("TEST","有序广播2");
    } }
    public class SortReceiver3 extends BroadcastReceiver {
    
        @Override
    public void onReceive(Context context, Intent intent) {
    // TODO Auto-generated method stub
    //String msg = intent.getStringExtra("msg");
    intent = new Intent("android.intent.action.BroadcastService");
    intent.addFlags(4);
    context.startService(intent);
    Log.d("TEST","有序广播3");
    } }
    <receiver android:name=".SortReceiver1">
    <intent-filter android:priority="1000">
    <action android:name="android.intent.action.SORT_BROADCAST"/>
    <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    </receiver>
    <receiver android:name=".SortReceiver2">
    <intent-filter android:priority="999">
    <action android:name="android.intent.action.SORT_BROADCAST"/>
    <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    </receiver>
    <receiver android:name=".SortReceiver3">
    <intent-filter android:priority="998">
    <action android:name="android.intent.action.SORT_BROADCAST"/>
    <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    </receiver>

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

    sendOrderedBroadcast(intent, "scott.permission.SORT_BROADCAST_PERMISSION");

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

    所以我们在AndroidMainfest.xml中定义一个权限,并获得权限:(是要在广播的应用中声明)

    <permission android:protectionLevel="normal"
    android:name="scott.permission.SORT_BROADCAST_PERMISSION" />
    <uses-permission android:name="scott.permission.SORT_BROADCAST_PERMISSION" />

    (这里不是写在application内部,而是同application同级)

    运行后只会出现这么一个消息:

    因为在第一个广播出我们就终止了广播的继续传递,所以就只会出现这么一条打印消息。

四、形形色色的广播

在android中有很多系统自带的intent.action,通过监听这些事件我们可以完成很多功能。

  1. 开机:
    String BOOT_COMPLETED_ACTION 广播:在系统启动后。这个动作被广播一次(只有一次)。监听: “android.intent.action.BOOT_COMPLETED”
  2. 电话拨入:
    String ANSWER_ACTION 动作:处理拨入的电话。监听: “android.intent.action.ANSWER”
  3. 电量变化:
    String BATTERY_CHANGED_ACTION 广播:充电状态,或者电池的电量发生变化。监听: “android.intent.action.BATTERY_CHANGED”
  4. 日期改变:
    String DATE_CHANGED_ACTION 广播:日期被改变。 监听:“android.intent.action.DATE_CHANGED”
  5. 取消更新下载:
    String FOTA_CANCEL_ACTION 广播:取消所有被挂起的 (pending) 更新下载。 监听:“android.server.checkin.FOTA_CANCEL”
  6. 更新开始安装:
    String FOTA_READY_ACTION 广播:更新已经被下载 可以开始安装。监听 “android.server.checkin.FOTA_READY”
  7. 主屏幕:
    String HOME_CATEGORY 类别:主屏幕 (activity)。设备启动后显示的第一个 activity。 监听:"android.intent.category.HOME”
  8. 新应用:
    String PACKAGE_ADDED_ACTION 广播:设备上新安装了一个应用程序包。监听: “android.intent.action.PACKAGE_ADDED”
  9. 删除应用:
    String PACKAGE_REMOVED_ACTION 广播:设备上删除了一个应用程序包。监听: “android.intent.action.PACKAGE_REMOVED”
  10. 屏幕关闭:
    String SCREEN_OFF_ACTION 广播:屏幕被关闭。监听: “android.intent.action.SCREEN_OFF”
  11. 屏幕开启:
    String SCREEN_ON_ACTION 广播:屏幕已经被打开。 监听:“android.intent.action.SCREEN_ON”
  12. 时区改变:
    String TIMEZONE_CHANGED_ACTION 广播:时区已经改变。监听: “android.intent.action.TIMEZONE_CHANGED”
  13. 时间改变:
    String TIME_CHANGED_ACTION 广播:时间已经改变(重新设置)。 “android.intent.action.TIME_SET”
  14. 时间流逝:
    String TIME_TICK_ACTION 广播:当前时间已经变化(正常的时间流逝)。 “android.intent.action.TIME_TICK”
  15. 进入大容量存储模式:
    String UMS_CONNECTED_ACTION 广播:设备进入 USB 大容量存储模式。 “android.intent.action.UMS_CONNECTED”
  16. 退出大容量存储模式:
    String UMS_DISCONNECTED_ACTION 广播:设备从 USB 大容量存储模式退出。 “android.intent.action.UMS_DISCONNECTED”
  17. 壁纸改变:
    String WALLPAPER_CHANGED_ACTION 广播:系统的墙纸已经改变。 “android.intent.action.WALLPAPER_CHANGED”
  18. web搜索:
    String WEB_SEARCH_ACTION 动作:执行 web 搜索。 “android.intent.action.WEB_SEARCH”
  19. 网络变化:
    String CONNECTIVITY_CHANGE_ACTION 动作:网络变化。“android.intent.action.CONNECTIVITY_CHANGE_ACTION”
广播本身的使用很简单,它所包含的内容也很少,最多是要多记住几个系统常用的几个ACTION。但是结合广播来进行应用的开发却会给用户带来更好的体验,广播的使用会让用户觉得开发者能够为用户考虑到各个情况的发生,这点往往能够留住大多数的用户。所以多考虑各种情况的发生,不要吝啬广播的使用 ,争取为用户留下一个好的印象吧。
 
资源下载:Demo
 
 ========================================
作者:cpacm
出处:(http://www.cpacm.net/2015/03/22/Android开发日记(四)——Android四大组件之Broadcast-Receiver/

【Android开发日记】之入门篇(六)——Android四大组件之Broadcast Receiver的更多相关文章

  1. 【Android开发日记】第一个任务Android Service!Service靴+重力感应器+弹出窗口+保持执行

    前言: 近期在写一个小程序,需求是手机摇一摇就弹窗出来.第一次使用了Service,学习了两天,实现了Service弹窗,开机启动,Service启动和销毁,Service保持一直执行. 满足了自己的 ...

  2. 【Android开发日记】之入门篇(十二)——Android组件间的数据传输

    组件我们有了,那么我们缺少一个组件之间传递信息的渠道.利用Intent做载体,这是一个王道的做法.还有呢,可以利用文件系统来做数据共享.也可以使用Application设置全局数据,利用组件来进行控制 ...

  3. 【Android开发日记】之入门篇(十一)——Android的Intent机制

    继续我们的Android之路吧.今天我要介绍的是Android的Intent. 对于基于组件的应用开发而言,不仅需要构造和寻找符合需求的组件,更重要的是要将组件有机的连接起来,互联互通交换信息,才能够 ...

  4. 【Android开发日记】之入门篇(七)——Android数据存储(上)

    在讲解Android的数据源组件——ContentProvider之前我觉得很有必要先弄清楚Android的数据结构. 数据和程序是应用构成的两个核心要素,数据存储永远是应用开发中最重要的主题之一,也 ...

  5. 【Android开发日记】之入门篇(八)——Android数据存储(下)

    废话不多说了,紧接着来讲数据库的操作吧.Come On! 提到数据存储问题,数据库是不得不提的.数据库是用来存储关系型数据的不二利器.Android为开发者提供了强大的数据库支持,可以用来轻松地构造基 ...

  6. 【Android开发日记】之入门篇(九)——Android四大组件之ContentProvider

    数据源组件ContentProvider与其他组件不同,数据源组件并不包括特定的功能逻辑.它只是负责为应用提供数据访问的接口.Android内置的许多数据都是使用ContentProvider形式,供 ...

  7. 【Android开发日记】之入门篇(五)——Android四大组件之Service

    这几天忙着驾校考试,连电脑都碰不到了,今天总算告一段落了~~Service作为Android的服务组件,默默地在后台为整个程序服务,辅助应用与系统中的其他组件或系统服务进行沟通.它跟Activity的 ...

  8. 【Android开发日记】之入门篇(十四)——Button控件+自定义Button控件

        好久不见,又是一个新的学期开始了,为什么我感觉好惆怅啊!这一周也发生了不少事情,节假日放了三天的假(好久没有这么悠闲过了),实习公司那边被组长半强制性的要求去解决一个后台登陆的问题,结果就是把 ...

  9. 【Android开发日记】之入门篇(一)——开发环境的搭建

    写给自己的话:至此,大学的时光已经剩下一年的时光,下一年等毕业设计结束后就算是正式地踏入社会.自己学android也不过几个月的时间,为了更好管理文档,写点东西记录下自己曾经做过的点点滴滴是一个不错的 ...

随机推荐

  1. BZOJ2525 [Poi2011]Dynamite 【二分 + 贪心】

    题目链接 BZOJ2525 题解 就是要求所有有炸弹的点到点燃点距离最大值最小 显然二分答案距离\(D\) 然后按深度排序,贪心点燃当前没覆盖的深度最深的点往上第\(D\)层的点 每覆盖一个点要标记其 ...

  2. Java之高级IO,Properties

    IO流(高级) 释放资源的标准代码 主要考虑的是在什么时候释放资源比较合适.而且在jdk1.7之前和之后是不同的. package com.wzlove.demo; import java.io.Fi ...

  3. java类加载详解

    1,类的加载过程: JVM将类加载过程分为三个步骤:装载(load),链接(link)和初始化(initialize),其中链接又分为三个步骤: 验证(varification),准备(Prepara ...

  4. 解题:USACO13JAN Island Travels

    题面 好像没啥可说的,就当练码力了...... 先用BFS跑出岛屿,然后跑最短路求岛屿间的距离,最后状压DP得出答案 注意细节,码码码2333 #include<set> #include ...

  5. 1044 Shopping in Mars

    Shopping in Mars is quite a different experience. The Mars people pay by chained diamonds. Each diam ...

  6. shopt

    本文出自 “Mr_Computer” 博客,请务必保留此出处 Bash Shell有个extglob选项,开启之后Shell可以另外识别出5个模式匹配操作符,能使文件匹配更加方便. 开启方法很简单,使 ...

  7. HDU 6249

    Alice’s Stamps Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)To ...

  8. Shell记录-Shell脚本基础(一)

    Shell 注释: 你可以把注释,在你的脚本如下: #!/bin/bash # Author : Zara Ali # Copyright (c) Tutorialsyiibai.com # Scri ...

  9. 算法进阶之Leetcode刷题记录

    目录 引言 题目 1.两数之和 题目 解题笔记 7.反转整数 题目 解题笔记 9.回文数 题目 解题笔记 13.罗马数字转整数 题目 解题笔记 14.最长公共前缀 题目 解题笔记 20.有效的括号 题 ...

  10. iscroll demo

    下面是自己找网上资料写的一个小demo,基础的属性和方法都有用到,但是用法还不是很标准,github上的demo用法很标准,外面一个wrapper,里面还得有一个scroller,如果要做跑马灯效果还 ...