[Android]广播机制概述 一

广播简单描述

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

Note:

  1. 同一app内部的同一组件内的消息通信(单个或多个线程之间);
  2. 同一app内部的不同组件之间的消息通信(单个进程);
  3. 同一app具有多个进程的不同组件之间的消息通信;
  4. 不同app之间的组件之间消息通信;

  5. Android系统在特定情况下与App之间的消息通信。
    具体实现流程要点粗略概括如下:
    5.1 广播接收者BroadcastReceiver通过Binder机制向AMS(Activity Manager Service)进行注册;
    5.2 广播发送者通过binder机制向AMS发送广播;
    5.3 AMS查找符合相应条件(IntentFilter/Permission等)的BroadcastReceiver,将广播发送到BroadcastReceiver(一般情况下是Activity)相应的消息循环队列中;
    5.4 消息循环执行拿到此广播,回调BroadcastReceiver中的onReceive()方法。
    从实现原理看上,Android中的广播使用了观察者模式,基于消息的发布/订阅事件模型。因此,从实现的角度来看,Android中的广播将广播的发送者和接受者极大程度上解耦,使得系统能够方便集成,更易扩展。

对于不同的广播类型,以及不同的BroadcastReceiver注册方式,具体实现上会有不同。但总体流程大致如上。
由此看来,广播发送者和广播接收者分别属于观察者模式中的消息发布和订阅两端,AMS属于中间的处理中心。广播发送者和广播接收者的执行是异步的,发出去的广播不会关心有无接收者接收,也不确定接收者到底是何时才能接收到。显然,整体流程与EventBus非常类似。

在上文说列举的广播机制具体可以使用的场景中,现分析实际应用中的适用性:

第一种情况,同一app内部的同一组件内的消息通信(单个或多个线程之间),实际应用中肯定是不会用到广播机制的(虽然可以用),无论是使用扩展变量作用域、基于接口的回调还是Handler-post/Handler-Message等方式,都可以直接处理此类问题,若适用广播机制,显然有些“杀鸡牛刀”的感觉,会显太“重”;

第二种情况, 同一app内部的不同组件之间的消息通信(单个进程),对于此类需求,在有些教复杂的情况下单纯的依靠基于接口的回调等方式不好处理,此时可以直接使用EventBus等,相对而言,EventBus由于是针对统一进程,用于处理此类需求非常适合,且轻松解耦。可以参见文件《Android各组件/控件间通信利器之EventBus》。

第三、四、五情形:由于涉及不同进程间的消息通信,此时根据实际业务使用广播机制会显得非常适宜。下面主要针对Android广播中的具体知识点进行总结。

2. BroadcastReceiver

2.1 自定义BroadcastReceiver

不做介绍

2.2 静态注册

<receiver android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:name="string"
android:permission="string"
android:process="string" >
. . .
</receiver>
  • exported: 表示是否可以应用外被访问, 此处为APP内,而非相同进程
    默认值为false,若是有intentFilter,则为true
  • process: 可指定单独的进程名,默认为app
  • name: 对应相应的receiver类名
  • permission: 表示若要接收某个intent 必须得配置与之相应的权限

静态注册的Receiver,若是APP进程未启动时,是否会被调用到 ?
Refs: [AMS广播发送原理]

2.3 动态注册

IntentFilter filter = new IntentFilter();
filter.addAction(...);
...
//
registerReceivers(broadcastReceiver, filter)
unRegisterReceivers(broadcastReceiver)
//

register/unregister必须成对出现

3. 广播类型

广播类型是根据发送时sendxxxBroadcast()方法来决定的,这个方法会决定AMS将intent传给receiver的方式

Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
intent.putExtra("key", "value");
sendBroadcast(intent);
sendBroadcast(intent, [permission, bundle])

sendOrderedBroadcast
sendStickyBroadcast

3.1 Normal Broadcast

普通广播的发送类似以上代码, 发出后根据IntentFilter来匹配相应的receivers
特点:1. 无序的,为何普通广播是无序的呢? 参考:BroadcastQueue 动态广播的分发

3.2 System Broadcast

必须由系统进程来发送,在AMS分发时会根据callUid来校验
类似的系统广播有:开关机, 网络变化,信息强度变化, 时间更新, WIFI信息变化,亮度及电量变化等等

3.3 Ordered Broadcast

有序广播,是指广播发出后,Ams/BroadcastQueue 在分发广播时是按一定的顺序来分发下去

sendOrderedBroadcast(intent...) // 发送方式与接收方式都有所区别
  1. 接收顺序

    NOTE:
    Order广播是以一定的顺序来接收的: Prority > registered > static register

  2. 可编辑或取消

 //并且可对intent中的内容进行修改
isOrderedBroadcast()
abortBroadcast()

3.4 Sticky Broadcast (已经过时,不深入讨论)

  • 粘性广播(在 android 5.0/api 21中deprecated,不再推荐使用,相应的还有粘性有序广播,同样已经deprecated)
  • 发送广播后一直在后台缓存中,再次发送时,则会使用缓存中的Broadcast
  • 下一次注册时,注册后立即会接收到cache中的广播,除非不再使用removeStickyBroadcast
sendStickyBroadcast(intent)
removeStickyBroadcast(intent)

3.5 Local Broadcast

指在应用内接收的广播(以APP进程为限),区别于Normal Broadcast(全局广播)
默认的广播发出来后,会以APP跨进程通信的方式,可能会由其它进程的receiver来处理

Refs: Ams广播原理,在收集广播时,会判断接收者为specify uid或是all_user

对于有intent-filter的情况下默认值是true,由此将可能出现安全隐患如下:

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

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

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

App应用内广播可以理解成一种局部广播的形式,广播的发送者和接收者都同属于一个App。实际的业务需求中,App应用内广播确实可能需要用到。同时,之所以使用应用内广播时,而不是使用全局广播的形式,更多的考虑到的是Android广播机制中的安全性问题。

相比于全局广播,App应用内广播优势体现在:
1. 安全性更高;
2. 更加高效。

为此,Android v4兼容包中给出了封装好的LocalBroadcastManager类,用于统一处理App应用内的广播问题,使用方式上与通常的全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将主调context变成了LocalBroadcastManager的单一实例。

代码片段如下: 提升即效率[^2]

//registerReceiver(mBroadcastReceiver, intentFilter);
  //注册应用内广播接收器
  localBroadcastManager = LocalBroadcastManager.getInstance(this);
  localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);

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

 Intent intent = new Intent();
 intent.setAction(BROADCAST_ACTION);
 intent.putExtra("name", "qqyumidi");
 //sendBroadcast(intent);
//发送应用内广播
localBroadcastManager.sendBroadcast(intent);

猜你喜欢

转载自blog.csdn.net/s394500839/article/details/76686830