基本原理
广播的使用包括注册和发送两步。注册广播又分为两种方式
- 在代码中进行动态注册
- 在XML文件中进行静态注册。
不管哪种方式都涉及两个类 BroadcastReceiver 与 IntentFilter。前者负责处理广播,后者负责匹配广播。
因为广播易造成内存泄漏,一般在活动的
onResume
和onPause
方法中成对的进行注册和销毁。
2.发送广播本质上是一个后台操作,发送广播的类型包括
sendBroadcast
发送无序广播,异步执行,效率高,但存在隐患sendOrderedBroadcast
发送有序广播,在某个接收器执行的同时会阻塞其他的接收器
虽然执行广播的进程是一个优先级较高的前台进程,但
BroadcastReceiver
对象的生命周期只在onReceive
方法的执行过程中,一旦执行完,对象将销毁。这一特性决定了如果在广播接收器内部执行异步操作,将无法返回。
局部广播
LocalBroadcastManager
局部广播不需要跨进程,并非使用Binder
机制。
粘性广播
粘性广播在21中被deprecated
了,使用粘性广播首先需要权限
1 | android.permission.BROADCAST_STICKYXML |
使用方法sendStickyBroadcast
方法来发送粘性广播
1 | Intent intent = new Intent("receiver"); |
它的行为和正常广播基本一致,在广播接收器取消注册后不会受理广播。唯一有区别的一点是广播接收器取消注册后,如果发送粘性广播,则Intent将被会缓存到系统中。这样再次注册广播接收器时,能从该方法返回值中获取先前的Intent即其中的数据。
1 | Intent mIntentSticky = registerReceiver(receiver,filter); |
因此发送粘性广播即便不能被成功接受,也可以保存数据,可见粘性广播的好处是使得广播能够在注册周期之外感知数据变化,但损失的是安全性,这些数据可以被任意获取与修改。
最佳实践
使用广播必须要手动注册接收机,可以优化的地方有两点
1.注册和解除配对出现,一般放在 onStart 和 onStop 方法中,其目的是防止内存泄漏。
2.视广播如何处理决定是否在注册时禁用(enable),以避免无谓的耗电。
首先禁止广播
1 | <receiver |
在必要时开启,并及时释放
1 | PackageManager packageManager = getPackageManager(); |
广播注册原理
在 Android 的广播机制中, ActivityManagerService 扮演着广播中心的角色,负责系统中所有广播的注册和发布操作,因此,Android应用程序注册广播接收器的过程就是把广播接收器注册到ActivityManagerService的过程。
广播注册实际由ContextImpl
的如下方法执行(已作精简)
1 | private Intent registerReceiverInternal(BroadcastReceiver receiver, ..., Context context) { |
该方法首先构建 IIntentReceiver 对象接口 ,这是一个单向的 Binder 对象,专门负责执行接收广播,定义如下
1 | oneway interface IIntentReceiver { |
而后使用ActivityManagerService
类在服务端进行真实的广播注册
在研究AMS如何注册广播之前,先做些预备工作
1.ActivityManagerService
维持着一个广播过滤器集合
1 | HashMap<IBinder, ReceiverList> mRegisteredReceivers |
其中key
为 IIntentReceivers 对象,而Value
为 ReceiverList ,代表一个注册了若干个广播的广播接收机,定义如下
1 | class ReceiverList extends ArrayList<BroadcastFilter>{ |
可见 ReceiverList 是一个集合类,元素为广播过滤器 BroadcastFilter 。该类是 IntentFilter 的子类。
2.IntentResolver类负责操作广播过滤器,其中有一个重要的方法是判断两个 IntentFilter 对象是否相等。其实现原理是是依次比较Action
,Category
以及Data
是否相等,其中Data
的比较又分为多个部分。
AMS 中注册广播的方法如下
1 | public Intent registerReceiver(IIntentReceiver receiver, IntentFilter filter, String permission, int userId) { |
注册广播的大部分代码是在处理粘性广播,对此只大略叙述
1.首先收集所有的粘性广播
2.而后收集能够经过过滤器的粘性广播集合
3.如果确实发送的是粘性广播,则返回代表最近的一条粘性广播的Intent
。
广播注册的过程是同步的,实际步骤是
1.先从广播过滤器缓存集合中查询是否存在传入的广播接收器,如果没有,则创建 ReceiverList ,并将其存入缓存
2.根据参数 IntentFilter 创建过滤器 BroadcastFilter ,并添加到系统解析器 mReceiverResolver中。
总结:广播有两个要素
BroadcastReceiver
与IntentFilter
。注册广播时前者生成Binder
对象,定义了如何处理广播;后者生成一个新的过滤器。系统内存维持着一个字典集合,不考虑粘性广播,则广播的注册过程是将二者写入这个字典集合中去。
WakefulBroadcastReceiver
唤醒锁
在认识WakefulBroadcastReceiver
广播之前先要了解唤醒锁。
安卓使用PowerManager
服务来控制设备电源状态,设备的接口定义在IPowerManager
接口中,可执行的方法如下
1 | void goToSleep(long time) |
其中最重要的方法是创建唤醒锁
1 | WakeLock newWakeLock(int levelAndFlags, String tag) |
WakeLock
类即代表唤醒锁,是PowerManager
的内部类,保持该锁会使得设备保持开启状态,无法进入休眠,必须等待锁的释放。
在上述方法中参数levelAndFlags
表示锁的级别与类型,实际使用中应尽量不使用以及使用最低级别的锁。
FULL_WAKE_LOCK
PARTIAL_WAKE_LOCK
该类型的所会使CPU保持运行,无视屏幕是否熄灭,即使按下电源键设备也不能进入休眠。SCREEN_DIM_WAKE_LOCK
屏幕将一直保持较暗的亮度,但不会熄灭。按下电源键锁将释放。SCREEN_BRIGHT_WAKE_LOCK
同上
在创建唤醒锁之后使用如下方法启用
1 | acquire() |
释放唤醒锁
1 | release() |
使用唤醒锁可以保持屏幕长亮,但更轻量级的做法是对窗口对象使用属性android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON。
WifiLock
与WakeLock
类似的还有WifiLock
,该锁保持Wi-Fi
射频模块开启。正常模式下Wi-Fi
射频模块会自动关闭以节省电量,在下载大文件时可以使用该锁。
1 | WifiLock createWifiLock(int lockType, String tag) |
lockType 的可能取值为
WIFI_MODE_FULL_HIGH_PERF
表示高性能连接,低丢包率,适合传输语音WIFI_MODE_FULL
WIFI_MODE_SCAN_ONLY
WakefulBroadcastReceiver
WakefulBroadcastReceiver是一种利用唤醒锁的特殊广播,其目的是确保广播发射到启动服务的过程中,设备始终处于唤醒状态,不会因为进入休眠状态而中止启动服务。类内部保持了唤醒锁集合
1 | SparseArray<PowerManager.WakeLock> mActiveWakeLocks; |
该广播提供了一个工具方法startWakefulService
来启动服务,其实现如下
1 | public static ComponentName startWakefulService(Context context, Intent intent) { |
可见在启动服务的时候,将创建一个唤醒锁,并获得60s的唤醒时间,在此期间设备保持唤醒状态。
该广播还提供了completeWakefulIntent方法以便在服务中释放唤醒锁,其实现如下
1 | public static boolean completeWakefulIntent(Intent intent) { |
小部件(AppWidgetProvider)
小部件是 APP 的简易入口,宿主APP与小部件处于不同的进程中,宿主通过广播(AppWidgetProvider)来更新小部件,小部件通过 PedentIndent 与宿主交互。
继承 AppWidgetProvider 类创建一个广播,并注册到清单文件
1 | <receiver android:name="ExampleAppWidgetProvider" > |
有两点要注意
- 必须制定特殊的 action,系统由此判定是小部件
- 必须提供小部件的配置信息
1 | <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" |
小部件信息的必要配置有4个,但小部件布局能够支持的布局和控件内是有限的,且要注意小部件的边距问题。
到此为止 ,小部件就已经建立起来了,但 没有任何功能。
AppWidgetProvider 类实际就是普通广播,仅仅对5个广播相关的事件进行了转发处理
- ACTION_APPWIDGET_UPDATE: 小部件更新
- ACTION_APPWIDGET_DELETED:删除每一个小部件
- ACTION_APPWIDGET_ENABLED :发生在添加第一个小部件时
- ACTION_APPWIDGET_DISABLED:发生在移除最后一个小部件时
- ACTION_APPWIDGET_OPTIONS_CHANGED 小部件配置改变
更新小部件需要利用 AppWidgetManager 类,更具体的内容是操作 RemoteViews
1 |
|