2.8Receiver深入原理
目录介绍
- 01.广播实现原理
- 02.使用流程
- 03.抽象方法onReceive()
- 04.广播的类型
- 05.内存泄漏
- 06.Receiver周期
- 07.如何耗时操作
01.广播实现原理
Android
中的广播使用了设计模式中的观察者模式:基于消息的发布/订阅事件模型。- 因此,Android将广播的发送者 和 接收者 解耦,使得系统方便集成,更易扩展
- 模型中有3个角色:
- 1.消息订阅者(广播接收者)
- 2.消息发布者(广播发布者)
- 3.消息中心(
AMS
,即Activity Manager Service
)
- 原理描述:
- 1.广播接收者 通过
Binder
机制在AMS
注册 - 2.广播发送者 通过
Binder
机制向AMS
发送广播 - 3.
AMS
根据 广播发送者 要求,在已注册列表中,寻找合适的广播接收者- 寻找依据:
IntentFilter / Permission
- 寻找依据:
- 4.
AMS
将广播发送到合适的广播接收者相应的消息循环队列中; - 5.广播接收者通过 消息循环 拿到此广播,并回调
onReceive()
- 1.广播接收者 通过
- 特别注意:
- 广播发送者 和 广播接收者的执行是异步的,发出去的广播不会关心有无接收者接收,也不确定接收者到底是何时才能接收到;
02.使用流程
- 具体使用流程如下:
03.抽象方法onReceive()
- 继承自BroadcastReceivre基类,必须复写抽象方法onReceive()方法
- 1.广播接收器接收到相应广播后,会自动回调onReceive()方法
- 2.一般情况下,onReceive方法会涉及与其他组件之间的交互,如发送Notification、启动service等
- 3.默认情况下,广播接收器运行在UI线程,因此,onReceive方法不能执行耗时操作,否则将导致ANR。
- 代码范例
public class CustomBroadcastReceiver extends BroadcastReceiver { //接收到广播后自动调用该方法 @Override public void onReceive(Context context, Intent intent) { //写入接收广播后的操作 } }
04.广播的类型
- 广播的类型主要分为5类:
- 普通广播(Normal Broadcast)
- 系统广播(System Broadcast)
- 有序广播(Ordered Broadcast)
- 粘性广播(Sticky Broadcast)
- App应用内广播(Local Broadcast)
- ①. 普通广播(Normal Broadcast)
- 即开发者自身定义intent的广播(最常用)。发送广播使用如下:
Intent intent = new Intent(); //对应BroadcastReceiver中intentFilter的action intent.setAction(BROADCAST_ACTION); //发送广播 sendBroadcast(intent);
- 若被注册了的广播接收者中注册时intentFilter的action与上述匹配,则会接收此广播(即进行回调onReceive())。如下mBroadcastReceiver则会接收上述广播
<receiver //此广播接收者类是mBroadcastReceiver android:name=".mBroadcastReceiver" > //用于接收网络状态改变时发出的广播 <intent-filter> <action android:name="BROADCAST_ACTION" /> </intent-filter> </receiver>
- 若发送广播有相应权限,那么广播接收者也需要相应权限
- ②. 系统广播(System Broadcast)
- Android中内置了多个系统广播:只要涉及到手机的基本操作(如开机、网络状态变化、拍照等等),都会发出相应的广播
- 每个广播都有特定的Intent - Filter(包括具体的action),Android系统广播action如下:
- 注:当使用系统广播时,只需要在注册广播接收者时定义相关的action即可,并不需要手动发送广播,当系统有相关操作时会自动进行系统广播
- ③. 有序广播(Ordered Broadcast)
- 定义:发送出去的广播被广播接收者按照先后顺序接收。有序是针对广播接收者而言的
- 广播接受者接收广播的顺序规则(同时面向静态和动态注册的广播接受者)
- 1.按照Priority属性值从大-小排序;
- 2.Priority属性相同者,动态注册的广播优先;
- 特点
- 1.接收广播按顺序接收
- 2.先接收的广播接收者可以对广播进行截断,即后接收的广播接收者不再接收到此广播;
- 3.先接收的广播接收者可以对广播进行修改,那么后接收的广播接收者将接收到被修改后的广播
- 具体使用
- 有序广播的使用过程与普通广播非常类似,差异仅在于广播的发送方式:
sendOrderedBroadcast(intent);
- ④. App应用内广播(Local Broadcast)
- 背景
- Android中的广播可以跨App直接通信(exported对于有intent-filter情况下默认值为true)
- 冲突可能出现的问题:
- 其他App针对性发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收广播并处理;
- 其他App注册与当前App一致的intent-filter用于接收广播,获取广播具体信息; 即会出现安全性 & 效率性的问题。
- 解决方案:使用App应用内广播(Local Broadcast)
- 1.App应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个App。
- 2.相比于全局广播(普通广播),App应用内广播优势体现在:安全性高 & 效率高
- 具体使用1 - 将全局广播设置成局部广播
- 1.注册广播时将exported属性设置为false,使得非本App内部发出的此广播不被接收;
- 2.在广播发送和接收时,增设相应权限permission,用于权限验证;
- 3.发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
- 通过 intent.setPackage(packageName) 指定报名
- 具体使用2 - 使用封装好的LocalBroadcastManager类
- 使用方式上与全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将参数的context变成了LocalBroadcastManager的单一实例
- 注:对于LocalBroadcastManager方式发送的应用内广播,只能通过LocalBroadcastManager动态注册,不能静态注册
//注册应用内广播接收器 //步骤1:实例化BroadcastReceiver子类 & IntentFilter mBroadcastReceiver mBroadcastReceiver = new mBroadcastReceiver(); IntentFilter intentFilter = new IntentFilter(); //步骤2:实例化LocalBroadcastManager的实例 localBroadcastManager = LocalBroadcastManager.getInstance(this); //步骤3:设置接收广播的类型 intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE); //步骤4:调用LocalBroadcastManager单一实例的registerReceiver()方法进行动态注册 localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter); //取消注册应用内广播接收器 localBroadcastManager.unregisterReceiver(mBroadcastReceiver); //发送应用内广播 Intent intent = new Intent(); intent.setAction(BROADCAST_ACTION); localBroadcastManager.sendBroadcast(intent);
- 背景
- ⑤. 粘性广播(Sticky Broadcast)
- 由于在Android5.0 & API 21中已经失效,所以不建议使用,在这里也不作过多的总结。
06.Receiver周期
- 进程生命周期
- 一个进程如果正在执行BroadcastReceiver的onReceive()方法,就会被当做一个前台进程,不易被系统杀死。
- 当 onReceive() 执行完毕,BroadcastReceiver就不再活跃,其所在进程会被系统当做一个空进程,随时有可能被系统杀死。
- Receiver 生命周期
- BroadcastReceiver的生命周期很短,从onReceiver()方法开始执行到结束,为其有效期,之后系统会销毁BraodcastReceiver对象,所以在onReceive方法中执行异步请求操作,很可能请求结果没有返回,BroadcastReceiver就被系统回收了。
07.如何耗时操作
- 如何正确地在BroadcastReceiver中执行耗时操作:
- (1)如果要在 BroadcastReceiver 中执行耗时操作,通过创建子线程的方式是不可靠的,因为 BroadcastReceiver 的生命周期很短,一旦结束,其所在进程属于空进程(没有任何活动组件的进程),极易在系统内存不足时优先被杀死,如此,正在工作的子线程也会被杀死。
- (2)在 BroadcastReceiver 中执行耗时操作,可开启一个 Service 将耗时操作交给 Service ,这样可以提高宿主进程优先级,保证耗时操作执行完成。
- 如何正确操作
- 在Android 开发中,一般耗时操作都会尽可能交给 Service 做,如上传图片,因为上传的过程可能应用被置于后台,Activity就会存在被系统回收的可能,如果担心 Service 被回收,还可通过startForeground(int, Notification)提升其优先级。
- 我们知道,Service是工作在主线程的,所以不能直接在里面执行耗时操作,一般需要开启子线程去做,而IntentService是Service的子类,正是可以用来处理异步请求的,同Service有两个好处:一是其内部开启了一个异步处理工作线程HandlerThread,不用我们自己去 new Thread了;二是不需要考虑什么时候关闭该 Service,当完成所有的任务后会自动关闭。
- 代码如下所示
/** * 策略二:使用 IntentService处理 * @param data */ private void sendMsgByIntentService(Context context, String data) { Intent intent = new Intent(context, MyIntentService.class); intent.setAction("com.yc.startIntentService"); intent.putExtra("data", data); context.startService(intent); } // 该方法是在 HandlerThread异步消息处理线程的消息队列中取出消息进行处理,当所有的msg处理完,messageQueue为空,IntentService 就会调用 stopSelf 方法停止 @Override protected void onHandleIntent(@Nullable Intent intent) { Log.d(TAG, "IntentService is handling intent msg"); if ("com.yc.startIntentService".equals(intent.getAction())) { String msg = intent.getStringExtra("data"); Log.d(TAG, Thread.currentThread().getName()); Log.d(TAG, "Msg received from main handler thread: broadcast send = " + msg); // 处理耗时请求 SystemClock.sleep(5000); Log.d(TAG, "Is Broadcast ANR ?"); // 子线程处理消息后,给主线程发消息,说"我执行完了" Message mainMsg = new Message(); mainMsg.what = 0x22; mainMsg.obj = "Msg from child handler thread: I have done !"; // 使用广播将处理耗时操作的数据发送给主线程 Intent intent0 = new Intent(BroadcastTestActivity.ACTION_FROM_INTENTSERVICE); intent0.putExtra("data_from_intent_service", "Msg from MyIntentService: I have done !"); sendBroadcast(intent0); // 如果消息队列为空,就会直接结束Service,接着执行onDestroyed方法 } }
其他介绍
01.关于博客汇总链接
02.关于我的博客
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yczbj/activities
- 简书:http://www.jianshu.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
- 开源中国:https://my.oschina.net/zbj1618/blog
- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 邮箱:yangchong211@163.com
- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
- segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles
- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e