内容简介:由于上一篇文章本文的CEventCenter基于老规矩,先来看看效果:
由于上一篇文章 《开源一个自用的Android IM库,基于Netty+TCP+Protobuf实现。》 得到了不错的反响,激发了写作的兴趣,趁着时间空闲,决定继续写一些文章,以下这篇,是一个自定义的Android事件分发中心库,实现类似 系统广播 、 EventBus 、 RxBus 的事件发布-订阅功能,后续有时间,会继续完善之前的 NettyChat 库,包括加入 WebSocket协议实现 、 UDP协议实现 以及大家需要的UI页面的封装(包含会话列表页、消息列表页等),敬请期待。
本文的CEventCenter基于 对象池 及 接口回调 实现,主要解决在Activity/Fragment/Service之间的消息事件传递问题,由于作者水平有限,文中有不对之处欢迎批评与指正。
老规矩,先来看看效果:
可以看到,在B Activity发布事件后,A Activity中的TextView文本改变了。 不想看文章的同学可以直接移步到Github fork源码: github地址接下来,让我们进入正题。
为什么不用BroadcastReceiver?
首先,BroadcastReceiver是重量级的、消耗资源较多的方式。其次,我们都知道,onReceive()方法是在主线程运行的,执行时间不能超过 10 秒,否则会导致ANR。那么,大家可能会有疑惑,直接在onReceive()中开启一个子线程处理耗时任务不就可以了吗?这种方式,不能说不行,只能说并不可靠。Receiver只在onReceive方法执行时是激活状态,只要onReceive一返回,Receiver就不再是激活状态了。由于activity可能会被用户退出,Broadcast Receiver的生命周期本身就很短,可能出现的情况是:
在子线程还没有结束的情况下,Activity已经被用户退出了,或者BroadcastReceiver已经结束了。在Activity已经退出、BroadcastReceiver已经结束的情况下,此时它们所在的进程就变成了空进程(没有任何活动组件的进程),系统需要内存时可能会优先终止该进程。如果宿主进程被终止,那么该进程内的所有子线程也会被中止,这样就可能导致子线程无法执行完成。
以上摘自: 为什么不能在BroadcastReceiver中开启子线程
为什么不用RxBus、EventBus?
其实我也有在用,记得在17年初的时候,我们当时在做一个直播项目,其中的消息事件传递,就是用的RxBus,当时是这样的:观众给主播送礼,是通过im给服务端发送消息,服务端收到送礼消息后,处理送礼的逻辑,然后给客户端返回送礼的状态消息,客户端收到消息后,通过RxBus把消息传递到Activity(其实这里不管是通过im还是http接口请求,都存在相同的问题)。在压测的时候,是每个50ms送一个礼物,很大的概率会出现一个bug,就是下图这个:
出现这个bug,是因为我们当时用的RxBus版本,内部是使用的RxJava1.0,而RxJava1.0是有一个设计缺陷的,也就是不支持背压,简单地说,抛出MissingBackpressureException往往就是因为,被观察者发送事件的速度太快,而观察者处理太慢,而且你还没有做相应措施,所以报异常。
当时心态炸了啊,因为项目比较庞大,撇弃RxBus的话,那工作量将非常巨大,而且当时项目着急上线,无奈之下,只能把可能出现这个bug的所有地方,替换成自己实现的CEventCenter,后续再逐步逐步替换...当然了,目前的RxJava已经修复了背压的问题,而CEventCenter在那之后也一直在使用,目前来说暂时没发现有什么问题,所以也就保持在用了,大家如果采用此库,在使用过程中如果发现问题,烦请联系我,我个人也是不提倡重复造轮子的,如果目前有比较好用的库,那就没必要重新开发一个,当然,如果时间允许,自己写一个其实也不错,至少在这过程中,一定会有所收获。当然了,EventBus的线程模型设计和粘性事件的支持是非常好的。
什么是对象池?为什么要使用对象池?
上面有提到,CEventCenter是基于对象池及接口回调实现的,那么,什么是对象池?其实大家应该都使用过OkHttp,了解过源码的应该都知道,OkHttp源码里面,就有一种叫做连接池的东西,而对象池,跟连接池类似。
在 java 中,对象的生命周期包括对象创建、对象使用,对象消失三个时间段,其中对象的使用是对象真正需要存活的时间,不好修改,该用的时候还得使用啊。对象的创建和消失就得好好控制下了。对象的创建是比较费时间的,也许感觉不到,好比一个赋值操作int i=1,也是需要耗时的,在比如构造一个对象,一个数组就更加消耗时间。再说对象的消除,在 java 里面使用GC来进行对象回收,其实也是需要对对象监控每一个运行状态,包括引用,赋值等。在Full GC的时候,会暂停其他操作,独占CPU。所以,我们需要控制对象的创建数量,也不要轻易的让对象消失,让他的复用更加充分。
以上摘自:java 对象池技术
废话不多说,直接开始吧。
首先,定义一个对象池中使用的对象接口:
PooledObject.java
package com.freddy.event; /** * <p>@ProjectName: CEventCenter</p> * <p>@ClassName: PooledObject.java</p> * <p>@PackageName: com.freddy.event</p> * <b> * <p>@Description: 对象池中的对象要求实现PooledObject接口</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/04/25 16:59</p> * <p>@email: chenshichao@outlook.com</p> */ public interface PooledObject { /** * 恢复到默认状态 */ void reset(); } 复制代码
然后,定义一个事件模型,也就是需要传递的消息事件对象:
CEvent.java
package com.freddy.event; /** * <p>@ProjectName: CEventCenter</p> * <p>@ClassName: CEvent.java</p> * <p>@PackageName: com.freddy.event</p> * <b> * <p>@Description: 事件模型</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/04/25 17:24</p> * <p>@email: chenshichao@outlook.com</p> */ public class CEvent implements PooledObject { private String topic; // 主题 private int msgCode; // 消息类型 private int resultCode; // 预留参数 private Object obj; // 回调返回数据 public CEvent() { } public CEvent(String topic, int msgCode, int resultCode, Object obj) { this.topic = topic; this.msgCode = msgCode; this.resultCode = resultCode; this.obj = obj; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public int getMsgCode() { return msgCode; } public void setMsgCode(int msgCode) { this.msgCode = msgCode; } public int getResultCode() { return resultCode; } public void setResultCode(int resultCode) { this.resultCode = resultCode; } public Object getObj() { return obj; } public void setObj(Object obj) { this.obj = obj; } /** * 恢复到默认状态 */ @Override public void reset() { this.topic = null; this.msgCode = 0; this.resultCode = 0; this.obj = null; } } 复制代码
接下来,自定义一个对象池:
ObjectPool.java
package com.freddy.event; /** * <p>@ProjectName: CEventCenter</p> * <p>@ClassName: ObjectPool.java</p> * <p>@PackageName: com.freddy.event</p> * <b> * <p>@Description: 自定义的对象池</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/04/25 17:30</p> * <p>@email: chenshichao@outlook.com</p> */ public abstract class ObjectPool<T extends PooledObject> { private T[] mContainer;// 对象容器 private final Object LOCK = new Object();// 对象锁 private int length;// 每次返回对象都放到数据末端,length表示前面可用对象数 public ObjectPool(int capacity) { mContainer = createObjPool(capacity); } /** * 创建对象池 * * @param capacity 最大限度容量 * @return 对象池 */ protected abstract T[] createObjPool(int capacity); /** * 创建一个新的对象 * * @return */ protected abstract T createNewObj(); /** * 从对象池中捞出一个对象,如果池已满,会重新创建一个对象 * * @return */ public final T get() { // 先从池中找到空闲的对象,如果没有,则重新创建一个对象 T obj = findFreeObject(); if (null == obj) { obj = createNewObj(); } else { // 清除对象状态 obj.reset(); } return obj; } /** * 从池中找到空闲的对象 * * @return */ private T findFreeObject() { T obj = null; synchronized (LOCK) { if (length > 0) { --length; obj = mContainer[length]; // 赋值完成后,释放资源 mContainer[length] = null; } } return obj; } /** * 把对象放回池里面 * * @param obj 需要放回对象池的对象 */ public final void returnObj(T obj) { synchronized (LOCK) { int size = mContainer.length; if (length < size) { mContainer[length] = obj; length++; } } } } 复制代码
然后,自定义一个事件对象池,继承自定义对象池:
CEventPool.java
package com.freddy.event; /** * <p>@ProjectName: CEventCenter</p> * <p>@ClassName: CEventObjPool.java</p> * <p>@PackageName: com.freddy.event</p> * <b> * <p>@Description: 事件对象池</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/04/25 17:45</p> * <p>@email: chenshichao@outlook.com</p> */ public class CEventObjPool extends ObjectPool<CEvent> { public CEventObjPool(int capacity) { super(capacity); } @Override protected CEvent[] createObjPool(int capacity) { return new CEvent[capacity]; } @Override protected CEvent createNewObj() { return new CEvent(); } } 复制代码
还有事件监听器:
I_CEventListener.java
package com.freddy.event; /** * <p>@ProjectName: CEventCenter</p> * <p>@ClassName: I_CEventListener.java</p> * <p>@PackageName: com.freddy.event</p> * <b> * <p>@Description: 事件监听器</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/04/25 17:52</p> * <p>@email: chenshichao@outlook.com</p> */ public interface I_CEventListener { /** * 事件回调函数 * <b>注意:</b><br /> * 如果 obj 使用了对象池,<br /> * 那么事件完成后,obj即自动回收到对象池,请不要再其它线程继续使用,否则可能会导致数据不正常 * @param topic * @param msgCode * @param resultCode * @param obj */ void onCEvent(String topic, int msgCode, int resultCode, Object obj); } 复制代码
最后,就是我们的主角了,事件分发中心:
CEventCenter.java
package com.freddy.event; import android.text.TextUtils; import android.util.Log; import java.util.HashMap; import java.util.LinkedList; import java.util.List; /** * <p>@ProjectName: CEventCenter</p> * <p>@ClassName: CEventCenter.java</p> * <p>@PackageName: com.freddy.event</p> * <b> * <p>@Description: 类描述</p> * </b> * <p>@author: FreddyChen</p> * <p>@date: 2019/04/25 17:48</p> * <p>@email: chenshichao@outlook.com</p> */ public class CEventCenter { private static final String TAG = CEventCenter.class.getSimpleName(); /** * 监听器列表,支持一对多存储 */ private static final HashMap<String, Object> LISTENER_MAP = new HashMap<>(); /** * 监听器列表锁 */ private static final Object LISTENER_LOCK = new Object(); /** * 事件对象池 */ private static final CEventObjPool POOL = new CEventObjPool(5); /** * 注册/注销监听器 * * @param toBind true注册 false注销 * @param listener 监听器 * @param topic 单个主题 */ public static void onBindEvent(boolean toBind, I_CEventListener listener, String topic) { onBindEvent(toBind, listener, new String[]{topic}); } /** * 注册/注销监听器 * * @param toBind true注册 false注销 * @param listener 监听器 * @param topics 多个主题 */ public static void onBindEvent(boolean toBind, I_CEventListener listener, String[] topics) { if (toBind) { registerEventListener(listener, topics); } else { unregisterEventListener(listener, topics); } } /** * 注册监听器 * * @param listener 监听器 * @param topic 单个主题 */ public static void registerEventListener(I_CEventListener listener, String topic) { registerEventListener(listener, new String[]{topic}); } /** * 注册监听器 * * @param listener 监听器 * @param topics 多个主题 */ public static void registerEventListener(I_CEventListener listener, String[] topics) { if (null == listener || null == topics) { return; } synchronized (LISTENER_LOCK) { for (String topic : topics) { if (TextUtils.isEmpty(topic)) { continue; } Object obj = LISTENER_MAP.get(topic); if (null == obj) { // 还没有监听器,直接放到Map集合 LISTENER_MAP.put(topic, listener); } else if (obj instanceof I_CEventListener) { // 有一个监听器 I_CEventListener oldListener = (I_CEventListener) obj; if (listener == oldListener) { // 去重 continue; } LinkedList<I_CEventListener> list = new LinkedList<>(); list.add(oldListener); list.add(listener); LISTENER_MAP.put(topic, list); } else if (obj instanceof List) { // 有多个监听器 LinkedList<I_CEventListener> listeners = (LinkedList<I_CEventListener>) obj; if (listeners.indexOf(listener) >= 0) { // 去重 continue; } listeners.add(listener); } } } } /** * 注销监听器 * * @param listener 监听器 * @param topic 单个主题 */ public static void unregisterEventListener(I_CEventListener listener, String topic) { unregisterEventListener(listener, new String[]{topic}); } /** * 注销监听器 * * @param listener 监听器 * @param topics 多个主题 */ public static void unregisterEventListener(I_CEventListener listener, String[] topics) { if (null == listener || null == topics) { return; } synchronized (LISTENER_LOCK) { for (String topic : topics) { if (TextUtils.isEmpty(topic)) { continue; } Object obj = LISTENER_MAP.get(topic); if (null == obj) { continue; } else if (obj instanceof I_CEventListener) { // 有一个监听器 if (obj == listener) { LISTENER_MAP.remove(topic); } } else if (obj instanceof List) { // 有多个监听器 LinkedList<I_CEventListener> listeners = (LinkedList<I_CEventListener>) obj; listeners.remove(listener); } } } } /** * 同步分发事件 * * @param topic 主题 * @param msgCode 消息类型 * @param resultCode 预留参数 * @param obj 回调返回数据 */ public static void dispatchEvent(String topic, int msgCode, int resultCode, Object obj) { if (!TextUtils.isEmpty(topic)) { CEvent event = POOL.get(); event.setTopic(topic); event.setMsgCode(msgCode); event.setResultCode(resultCode); event.setObj(obj); dispatchEvent(event); } } /** * 同步分发事件 * * @param event */ public static void dispatchEvent(CEvent event) { // 没有监听器,直接跳出代码,无需执行以下代码 if (LISTENER_MAP.size() == 0) { return; } if (null != event && !TextUtils.isEmpty(event.getTopic())) { String topic = event.getTopic(); // 通知事件监听器处理事件 I_CEventListener listener = null; LinkedList<I_CEventListener> listeners = null; synchronized (LISTENER_LOCK) { Log.d(TAG, "dispatchEvent | topic = " + topic + "\tmsgCode = " + event.getMsgCode() + "\tresultCode = " + event.getResultCode() + "\tobj = " + event.getObj()); Object obj = LISTENER_MAP.get(topic); if (obj == null) { return; } if (obj instanceof I_CEventListener) { listener = (I_CEventListener) obj; } else if (obj instanceof LinkedList) { listeners = (LinkedList<I_CEventListener>) ((LinkedList) obj).clone(); } } // 分发事件 if (null != listener) { listener.onCEvent(topic, event.getMsgCode(), event.getResultCode(), event.getObj()); } else if (null != listeners && listeners.size() > 0) { for (I_CEventListener l : listeners) { l.onCEvent(topic, event.getMsgCode(), event.getResultCode(), event.getObj()); } } // 把对象放回池里面 POOL.returnObj(event); } } } 复制代码
代码比较简单,就是 注册监听器 -> 分发事件 -> 事件回调响应 -> 注销监听器 四个步骤,支持一对一、一对多发布主题事件,事件分发完毕后,把对象放回对象池里面,便于对象复用。
使用方法,拿Activity举例吧:
- 在A Activity的onCreate()方法中,调用 CEventCenter.registerEventListener(I_CEventListener listener, String topic/String[] topics) 方法注册监听器,A Activity需要 实现I_CEventListener接口 , 重写onCEvent(String topic, int msgCode, int resultCode, Object obj)方法 ,同时不要忘记在onDestroy()方法中 调用CEventCenter.unregisterEventListener(I_CEventListener listener, String topic/String[] topics) 方法注销监听器,如下图: 接着,在B Activity中,调用 CEventCenter.dispatchEvent(CEvent event/ String topic, int msgCode, int resultCode, Object obj) 方法发布事件即可,如下图: 这时会发现,A Activity的 onCEvent(String topic, int msgCode, int resultCode, Object obj) 会回调,在该方法里执行相应的逻辑处理就可以了。
我们来看看运行效果:
可以看到,在B Activity发布事件后,A Activity中的TextView文本改变了。另外,多个事件监听器注册/注销用法都一样,只是把String替换成String[]即可,就不介绍啦。
写在最后
这篇文章比较简单,由于之前的文章贴了大量的图片,导致可能加载过慢,而且在手机上看代码截图不是那么方便,所以这篇文章大部分换成了直接贴源码的方式,方便大家阅读。这个库实现的功能比较简单,所以源码也能全部贴上来了,如果此库对你有用,希望在github上给我一个star哈。。。另外,欢迎fork,期望大家与我一起完善。后续会陆续的形式开源一些平时工作中积累的、Android实用的库,希望大家会喜欢。。。
另外,创建了一个Android即时通讯技术交流QQ群: 1015178804 ,有需要的同学可以加进来,不懂的问题,我会尽量解答,一起学习,一起成长。
The end.
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 自用梭子
- 开源一个自用的Android IM库,基于Netty+TCP+Protobuf实现。
- 程序员亲自用代码给敲出飘落的雪花!网友:大神收我吧!
- 微信自用高性能通用key-value组件MMKV已开源!
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
MD5 加密
MD5 加密工具
HSV CMYK 转换工具
HSV CMYK互换工具