Android广播机制详解

概述

在 Android 里面有各种各样的广播,比如电池的使用状态,电话的接收和短信的接收都会产生一个广播,应用程序开发者也可以监听这些广播并做出程序逻辑的处理。下面是一张粗略的图来帮助大家理解广播的运行机制。
这里写图片描述

Android广播分为两个方面:广播发送者和广播接收者,通常情况下,BroadcastReceiver指的就是广播接收者(广播接收器)。广播作为Android组件间的通信方式,可以使用的场景如下:

  • 同一app内部的同一组件(Component)内的消息通信(单个或多个线程之间)
  • 同一app内部的不同组件之间的消息通信(单个进程)
  • 同一app具有多个进程的不同组件之间的消息通信
  • 不同app之间的组件之间消息通信
  • Android系统在特定情况下与App之间的消息通信

从实现原理上看,Android中的广播使用了观察者模式,基于消息的发布/订阅事件模型。因此,从实现的角度来看,Android中的广播将广播的发送者和接受者极大程度上解耦,使得系统能够方便集成,更易扩展。具体实现流程要点粗略概括如下:

  • 广播接收者BroadcastReceiver通过Binder机制向AMS(Activity Manager Service)进行注册
  • 广播发送者通过binder机制向AMS发送广播
  • AMS查找符合相应条件(IntentFilter/Permission等)的BroadcastReceiver,将广播发送到BroadcastReceiver相应的消息循环队列中
  • 消息循环队列拿到此广播后,回调BroadcastReceiver中的onReceive()方法

由此看来,广播发送者和广播接收者分别属于观察者模式中的消息发布和订阅两端,AMS属于中间的处理中心。广播发送者和广播接收者的执行是异步的,发出去的广播不会关心有无接收者接收,也不确定接收者到底是何时才能接收到。

在上文中列举的广播机制具体可以使用的场景中,现分析实际应用中的适用性:
第一种情形:同一app内部的同一组件内的消息通信(单个或多个线程之间),实际应用中肯定是不会用到广播机制的(虽然可以用),无论是使用扩展变量作用域、基于接口的回调还是Handler-post/Handler-Message等方式,都可以直接处理此类问题,若适用广播机制,显然有些“杀鸡牛刀”的感觉,会显太“重”;
第二种情形:同一app内部的不同组件之间的消息通信(单个进程),对于此类需求,在有些较复杂的情况下单纯的依靠基于接口的回调等方式不好处理,此时可以直接使用EventBus等,相对而言,EventBus由于是针对统一进程,用于处理此类需求非常适合,且轻松解耦。关于EventBus更详细介绍请看这篇文章Android EventBus事件总线剖析
第三、四、五情形:由于涉及不同进程间的消息通信,此时根据实际业务使用广播机制会显得非常适宜。

广播的注册

BroadcastReceiver可以分为两种注册类型:静态注册和动态注册。
(1)静态注册
直接在AndroidManifest.xml文件中进行注册。规则如下:

<receiver android:enabled=["true" | "false"]
          android:exported=["true" | "false"]
          android:icon="drawable resource"
          android:label="string resource"
          android:name="string"
          android:permission="string"
          android:process="string" >
          . . .
          <intent-filter>
              <action  android:name="string" />
          </intent-filter>
</receiver>

其中,需要注意的属性:
android:exported ——此BroadcastReceiver能否接收其他App发出的广播。这个属性默认值有点意思,其默认值是由receiver中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。(同样的,activity/service中的此属性默认值一样遵循此规则)。同时,需要注意的是,这个值的设定是以application或者application user id为界的,而非进程为界(一个应用中可能含有多个进程);
android:name —— 此BoadcastReceiver类名(不能是内部类,内部类只能动态注册);
android:permission ——如果设置,具有相应权限的广播发送方发送的广播才能被此broadcastReceiver所接收;
android:process ——BroadcastReceiver运行所处的进程。默认为App的进程。可以指定独立的进程(Android四大基本组件都可以通过此属性指定自己的独立进程)
intent-filter ——指定此广播接收器将用于接收特定Action的广播。

当此App首次启动时,系统会自动实例化此BroadcastReceiver,并注册到系统中。
注:之前常说静态注册的广播接收器即使App已经退出,只要有相应的广播发出,依然可以接收到。但此种描述自Android 3.1开始有可能不再成立,具体分析详见本文后面部分。

(2)动态注册
动态注册时,无须在AndroidManifest中注册<\receiver>组件。直接在代码中通过调用Context的registerReceiver函数,可以在程序中动态注册BroadcastReceiver。registerReceiver的定义形式如下:

registerReceiver(BroadcastReceiver receiver, IntentFilter filter)
registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)
public class MainActivity extends AppCompatActivity {
    public static final String BROADCAST_ACTION = "com.hx.bc";
    private BroadcastReceiver mBroadcastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //动态注册广播
        mBroadcastReceiver = new MyBroadcastReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(BROADCAST_ACTION);
        registerReceiver(mBroadcastReceiver, intentFilter);
    }

    public class MyBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            ...
        }
    }

    @Override
    protected void onDestroy() {
       super.onDestroy();
       unregisterReceiver(mBroadcastReceiver);
    }
}

当此Activity实例化时,会动态将MyBroadcastReceiver注册到系统中。当此Activity销毁时,动态注册的MyBroadcastReceiver将不再接收到相应的广播。
注:Android中所有与观察者模式有关的设计中,一旦涉及到register,必定在相应的时机需要unregister。因此,上例在onDestroy()回到中需要unregisterReceiver(mBroadcastReceiver)。

广播的接收

自定义广播接收器需要继承基类BroadcastReceivre,并实现抽象方法onReceive(context, intent)方法。广播接收器接收到相应广播后,会自动回调onReceive(..)方法。
默认情况下,广播接收器是运行在UI线程中的,因此,onReceive方法中不能执行太耗时的操作,否则将引起ANR。在BroadCast中尽量不要处理太多逻辑问题,建议复杂的逻辑交给Activity 或者 Service 去处理。一般情况下,根据实际业务需求,onReceive方法中都会涉及到与其他组件之间的交互,如发送Notification、启动service等。
下面代码片段是一个简单的自定义广播接收器:

public class MyBroadcastReceiver extends BroadcastReceiver {
      @Override
      public void onReceive(Context context, Intent intent) {
          showlog("MyBroadcastReceiver-onReceive");
          String strSMSBody = intent.getStringExtra("SMSBody");
          String strSMSAdress= intent.getStringExtra("SMSAdress");
          Intent myIntent = new Intent();    
          //通过Intent对象把信息的内容和发送人的号码发给新的Activity    
          myIntent.putExtra("SMSBody", strSMSBody);    
          myIntent.putExtra("SMSAdress", strSMSAdress);    
          //从Service或BroadcastReciver往Activity跳转时,要将Intent的Flag设置为FLAG_ACTIVITY_NEW_TASK才可以    
          myIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);      
          myIntent.setClass(context, BroadcastActivity.class);    
          context.startActivity(myIntent); 
      }
}

广播的发送及广播类型

通常说”发送广播“和”接收广播“,表面上看广播作为Android广播机制的实体,实际上这一实体本身是并不是以所谓的”广播“对象存在的,而是以”意图“(Intent)去表示。广播的定义过程,实际就是相应广播”意图“的定义过程,然后通过广播发送者将此”意图“发送出去。被相应的BroadcastReceiver接收后将会回调onReceive()函数。

根据广播的发送方式,可以将其分为以下几种类型:

  • Normal Broadcast:普通广播
  • System Broadcast: 系统广播
  • Ordered broadcast:有序广播
  • Sticky Broadcast:粘性广播(在 android 5.0/api 21中deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经deprecated)
  • Local Broadcast:App应用内广播

下面分别总结下各种类型的发送方式及其特点。
(1)Normal Broadcast:普通广播
此处将普通广播界定为:开发者自己定义的Intent,以context.sendBroadcast[AsUser](intent, …)形式。具体可以使用的方法有:

sendBroadcast(intent)
sendBroadcast(intent, receiverPermission)
sendBroadcastAsUser(intent, userHandler)
sendBroadcastAsUser(intent, userHandler, receiverPermission)

普通广播会被注册了的相应的感兴趣(intent-filter匹配)接收,且顺序是无序的。如果发送广播时有相应的权限要求,BroadCastReceiver如果想要接收此广播,也需要有相应的权限。普通广播是完全异步的,可以在同一时刻(逻辑上)被所有接收者接收到,消息传递的效率比较高,但缺点是:接收者不能将处理结果传递给下一个接收者,并且无法终止广播Intent的传播。

//注册广播
mBroadcastReceiver = new MyBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BROADCAST_ACTION_NORMAL);
registerReceiver(mBroadcastReceiver, intentFilter);

//取消注册广播
unregisterReceiver(mBroadcastReceiver);

//发送广播
button1.setOnClickListener(new View.OnClickListener(){
       @Override
       public void onClick(View v) {
            Intent it = new Intent();
            it.setAction(BROADCAST_ACTION_NORMAL);
            it.putExtra("name","normal broadcast");
            sendBroadcast(it);
       }
});

//接收广播
public class MyBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            showlog("MyBroadcastReceiver-onReceive");
            String name = intent.getStringExtra("name");
            text1.setText(name);
            showlog("name="+name);
        }
    }

点击按钮,发送广播,Log信息如下:
这里写图片描述

(2)System Broadcast:系统广播
Android系统中内置了多个系统广播,只要涉及到手机的基本操作,基本上都会发出相应的系统广播。如:开机启动,网络状态改变,拍照,屏幕关闭与开启,电量不足等等。每个系统广播都具有特定的intent-filter,其中主要包括具体的action,系统广播发出后,将被相应的BroadcastReceiver接收。系统广播在系统内部当特定事件发生时,由系统自动发出。

(3)Ordered broadcast:有序广播
有序广播中的“有序”是针对广播接收者而言的,指的是发送出去的广播被BroadcastReceiver按照先后顺序接收。有序广播的定义过程与普通广播无异,只是其主要发送方式变为:sendOrderedBroadcast(intent, receiverPermission, …)。

对于有序广播,其主要特点总结如下:
1>当前多个已经注册且有效的BroadcastReceiver接收有序广播时,是按照先后顺序接收的,先后顺序判定标准遵循:将当前系统中所有有效的动态注册和静态注册的BroadcastReceiver按照priority属性值(声明在intent-filter元素的android:priority属性中,数越大优先级别越高,取值范围:-1000到1000,也可以调用IntentFilter对象的setPriority()进行设置)从大到小排序,对于具有相同的priority的动态广播和静态广播,动态广播会排在前面。
2>先接收的BroadcastReceiver可以对此有序广播进行截断(BroadcastReceiver.abortBroadcast()),使后面的BroadcastReceiver不再接收到此广播。也可以将处理结果通过setResultExtras(Bundle)方法存放进结果对象,然后传给下一个接收者,通过代码:Bundle bundle =getResultExtras(true))可以获取上一个接收者存入在结果对象中的数据。一个经典的应用案例是电话黑名单,首先通过将黑名单号码保存在数据库里面,当来电时,我们接收到来电广播并将黑名单号码与数据库中的某个数据做匹配,如果匹配的话则做出相应的处理,比如挂掉电话或静音等。

        //广播注册
        mBroadcastReceiver1 = new MyBroadcastReceiver1();
        IntentFilter intentFilter1 = new IntentFilter();
        intentFilter1.addAction(BROADCAST_ACTION_ORDERED);
        intentFilter1.setPriority(800);
        registerReceiver(mBroadcastReceiver1, intentFilter1);

        mBroadcastReceiver2 = new MyBroadcastReceiver2();
        IntentFilter intentFilter2 = new IntentFilter();
        intentFilter2.addAction(BROADCAST_ACTION_ORDERED);
        intentFilter2.setPriority(500);
        registerReceiver(mBroadcastReceiver2, intentFilter2);

        mBroadcastReceiver3 = new MyBroadcastReceiver3();
        IntentFilter intentFilter3 = new IntentFilter();
        intentFilter3.addAction(BROADCAST_ACTION_ORDERED);
        intentFilter3.setPriority(300);
        registerReceiver(mBroadcastReceiver3, intentFilter3);

        //取消注册广播
        unregisterReceiver(mBroadcastReceiver1);
        unregisterReceiver(mBroadcastReceiver2);
        unregisterReceiver(mBroadcastReceiver3);

        //广播发送
        button2.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                Intent it = new Intent();
                it.setAction(BROADCAST_ACTION_ORDERED);
                it.putExtra("name","ordered broadcast");
                sendOrderedBroadcast(it, null);
            }
        });

     //广播接收
     public class MyBroadcastReceiver1 extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            showlog("MyBroadcastReceiver1-onReceive");
            String name = intent.getStringExtra("name");
            text2_1.setText(name);
            showlog("name="+name);

            Bundle bundle = new Bundle();
            bundle.putString("name", "ordered broadcast modified");
            setResultExtras(bundle);
        }
    }

    public class MyBroadcastReceiver2 extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            showlog("MyBroadcastReceiver2-onReceive");
            //获取原生广播的数据
//          String name = intent.getStringExtra("name");
            Bundle bundle =getResultExtras(true);
            String name = bundle.getString("name");
            text2_2.setText(name);
            showlog("name="+name);

            bundle.putString("name", "ordered broadcast modified again");
            setResultExtras(bundle);
     //     abortBroadcast();  //终止广播
        }
    }

    public class MyBroadcastReceiver3 extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            showlog("MyBroadcastReceiver3-onReceive");
            //获取原生广播的数据
//          String name = intent.getStringExtra("name");
            Bundle bundle =getResultExtras(true);
            String name = bundle.getString("name");
            text2_3.setText(name);
            showlog("name="+name);
        }
    }

点击按钮,发送广播,看Log输出信息:
这里写图片描述
可以看到Receiver1和Receiver2对接收到的数据分别进行了修改,继续传递给后续的广播接收器。
注:通过setResultExtras(bundle); 传递的数据是不会更改原生广播的数据的,只是在原来广播数据中额外添加新的数据。

(4)Sticky Broadcast:粘性广播
在 android 5.0/api 21中deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经deprecated。在此不再多做总结。

(5)Local Broadcast:App应用内广播(此处的App应用以App应用进程为界)
Android中的广播可以跨进程甚至跨App直接通信,且注册是exported对于有intent-filter的情况下默认值是true,由此将可能出现安全隐患如下:

  • 其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并处理;
  • 其他App可以注册与当前App一致的intent-filter用于接收广播,获取广播具体信息。

无论哪种情形,这些安全隐患都确实是存在的。由此,最常见的增加安全性的方案是:

  • 对于同一App内部发送和接收广播,将exported属性人为设置成false,使得非本App内部发出的此广播不被接收;
  • 在广播发送和接收时,都增加上相应的permission,用于权限验证;
  • 发送广播时,指定特定广播接收器所在的包名,具体是通过intent.setPackage(packageName)指定,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。{ String packageName = intent.getPackage(); }

App应用内广播可以理解成一种局部广播的形式,广播的发送者和接收者都同属于一个App。实际的业务需求中,App应用内广播确实可能需要用到。同时,之所以使用应用内广播,而不是使用全局广播的形式,更多的考虑到的是Android广播机制中的安全性问题。
相比于全局广播,App应用内广播优势体现在:安全性更高、更加高效。
为此,Android v4兼容包中给出了封装好的LocalBroadcastManager类,用于统一处理App应用内的广播问题,使用方式上与通常的全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将主调context变成了LocalBroadcastManager的单一实例。
代码片段如下:

/**注册应用内广播接收器*/
mBroadcastReceiver4 = new MyBroadcastReceiver4();
IntentFilter intentFilter4 = new IntentFilter();
intentFilter4.addAction(BROADCAST_ACTION_LOCAL);
localBroadcastManager = LocalBroadcastManager.getInstance(this);
localBroadcastManager.registerReceiver(mBroadcastReceiver4, intentFilter4);

//取消注册应用内广播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver4);

//发送应用内广播
button3.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v) {
        Intent it = new Intent();
        it.setAction(BROADCAST_ACTION_LOCAL);
        it.putExtra("name","local broadcast");
        localBroadcastManager.sendBroadcast(it);
    }
});

//接收应用内广播
public class MyBroadcastReceiver4 extends BroadcastReceiver {
     @Override
     public void onReceive(Context context, Intent intent) {
         showlog("MyBroadcastReceiver4-onReceive");
         String name = intent.getStringExtra("name");
         text3.setText(name);
         showlog("name="+name);
     }
 }

点击按钮,发送广播,Log输出信息:
这里写图片描述

onReceive(context, intent)中的context具体类型

(1)对于静态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是ReceiverRestrictedContext;
注:这个context是一个ReceiverRestrictedContext实例,它有两个主要函数被禁掉:registerReceiver()和bindService()。这两个函数在BroadcastReceiver.onReceive()不允许调用。每次Receiver处理一个广播,传递进来的context都是一个新的实例。
(2)对于全局广播的动态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是Activity Context;
(3)对于通过LocalBroadcastManager动态注册的ContextReceiver,回调onReceive(context, intent)中的context具体指的是Application Context。
注:对于LocalBroadcastManager方式发送的应用内广播,只能通过LocalBroadcastManager动态注册的ContextReceiver才有可能接收到(静态注册或其他方式动态注册的ContextReceiver是接收不到的)。

API重要变迁

还记得本篇开始时说的“静态注册的广播接收器即使app已经退出,只要有相应的广播发出,依然可以接收到,但此种描述自Android 3.1开始有可能不再成立”吗?这里我们探究一下原因:
Android 3.1开始系统在Intent与广播相关的flag增加了参数:

  • FLAG_INCLUDE_STOPPED_PACKAGES:包含已经停止的包(停止:即包所在的进程已经退出)
  • FLAG_EXCLUDE_STOPPED_PACKAGES:不包含已经停止的包

自Android3.1开始,系统本身则增加了对所有app当前是否处于运行状态的跟踪。在发送广播时,不管是什么广播类型,系统默认直接增加了值为FLAG_EXCLUDE_STOPPED_PACKAGES的flag,导致即使是静态注册的广播接收器,对于其所在进程已经退出的app,同样无法接收到广播。
对于系统广播,由于是系统内部直接发出,无法更改此intent flag值,因此,3.1开始对于静态注册的接收系统广播的BroadcastReceiver,如果App进程已经退出,将不能接收到广播。
但是对于自定义的广播,可以通过复写此flag为FLAG_INCLUDE_STOPPED_PACKAGES,使得静态注册的BroadcastReceiver,即使所在App进程已经退出,也能能接收到广播,并会启动应用进程,但此时的BroadcastReceiver是重新新建的。

Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
intent.putExtra("name", "test broadcast");
sendBroadcast(intent);

注:
1. 对于动态注册类型的BroadcastReceiver,由于注册和取消注册是在其他组件(如Activity)中进行,因此,不受此改变影响。
2. 在3.1以前,相信不少app可能通过静态注册方式监听各种系统广播,以此进行一些业务上的处理(如即时app已经退出,仍然能接收到,可以启动service等..),3.1后,静态注册接受广播方式的改变,将直接导致此类方案不再可行。于是,通过将Service与App本身设置成不同的进程已经成为实现此类需求的可行替代方案。

Demo下载地址

  • 10
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值