开源一个自用的Android事件分发中心库,实现类似系统广播功能。

欢迎转载,转载请注明出处:juejin.im/post/5cbe81…

写在前面

由于上一篇文章《开源一个自用的Android IM库,基于Netty+TCP+Protobuf实现。》得到了不错的反响,激发了写作的兴趣,趁着时间空闲,决定继续写一些文章,以下这篇,是一个自定义的Android事件分发中心库,实现类似系统广播EventBusRxBus的事件发布-订阅功能,后续有时间,会继续完善之前的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?

其实我也有在用,记得在17年初的时候,我们当时在做一个直播项目,其中的消息事件传递,就是用的RxBus,当时是这样的:观众给主播送礼,是通过im给服务端发送消息,服务端收到送礼消息后,处理送礼的逻辑,然后给客户端返回送礼的状态消息,客户端收到消息后,通过RxBus把消息传递到Activity(其实这里不管是通过im还是http接口请求,都存在相同的问题)。在压测的时候,是每个50ms送一个礼物,很大的概率会出现一个bug,就是下图这个:

出现这个bug,是因为我们当时用的RxBus版本,内部是使用的RxJava1.0,而RxJava1.0是有一个设计缺陷的,也就是不支持背压,简单地说,抛出MissingBackpressureException往往就是因为,被观察者发送事件的速度太快,而观察者处理太慢,而且你还没有做相应措施,所以报异常。
当时心态炸了啊,因为项目比较庞大,撇弃RxBus的话,那工作量将非常巨大,而且当时项目着急上线,无奈之下,只能把可能出现这个bug的所有地方,替换成自己实现的CEventCenter,后续再逐步逐步替换...当然了,目前的RxJava已经修复了背压的问题,而CEventCenter在那之后也一直在使用,目前来说暂时没发现有什么问题,所以也就保持在用了,大家如果采用此库,在使用过程中如果发现问题,烦请联系我,我个人也是不提倡重复造轮子的,如果目前有比较好用的库,那就没必要重新开发一个,当然,如果时间允许,自己写一个其实也不错,至少在这过程中,一定会有所收获。

为什么不用EventBus?

目前EventBus最新的版本应该是3.x,这里面就有一个比较坑爹的设计:事件只能通过事件的类名来区分。
这至少带来了3大问题:

  1. 操作麻烦,每一个事件,都要定义一个类;
  2. 增加方法数;
  3. 导致事件发送者和接收者都依赖耦合事件类。

以上摘自:EventBus的缺点及改进升级,原作者也提供了改进的思路,有兴趣可以进去看看,当然了,EventBus的线程模型设计和粘性事件的支持是非常好的。


什么是对象池?为什么要使用对象池?

上面有提到,CEventCenter是基于对象池及接口回调实现的,那么,什么是对象池?其实大家应该都使用过OkHttp,了解过源码的应该都知道,OkHttp源码里面,就有一种叫做连接池的东西,而对象池,跟连接池类似。
在java中,对象的生命周期包括对象创建、对象使用,对象消失三个时间段,其中对象的使用是对象真正需要存活的时间,不好修改,该用的时候还得使用啊。对象的创建和消失就得好好控制下了。对象的创建是比较费时间的,也许感觉不到,好比一个赋值操作int i=1,也是需要耗时的,在比如构造一个对象,一个数组就更加消耗时间。再说对象的消除,在 java 里面使用GC来进行对象回收,其实也是需要对对象监控每一个运行状态,包括引用,赋值等。在Full GC的时候,会暂停其他操作,独占CPU。所以,我们需要控制对象的创建数量,也不要轻易的让对象消失,让他的复用更加充分。
以上摘自:java 对象池技术

废话不多说,直接开始吧。

首先,定义一个对象池中使用的对象接口:
PooledObject.java

package com.freddy.event;

/**
 * 

@ProjectName: CEventCenter

*

@ClassName: PooledObject.java

*

@PackageName: com.freddy.event

* *

@Description: 对象池中的对象要求实现PooledObject接口

*
*

@author: FreddyChen

*

@date: 2019/04/25 16:59

*

@email: [email protected]

*/ public interface PooledObject { /** * 恢复到默认状态 */ void reset(); } 复制代码

然后,定义一个事件模型,也就是需要传递的消息事件对象:
CEvent.java

package com.freddy.event;

/**
 * 

@ProjectName: CEventCenter

*

@ClassName: CEvent.java

*

@PackageName: com.freddy.event

* *

@Description: 事件模型

*
*

@author: FreddyChen

*

@date: 2019/04/25 17:24

*

@email: [email protected]

*/ 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;

/**
 * 

@ProjectName: CEventCenter

*

@ClassName: ObjectPool.java

*

@PackageName: com.freddy.event

* *

@Description: 自定义的对象池

*
*

@author: FreddyChen

*

@date: 2019/04/25 17:30

*

@email: [email protected]

*/ public abstract class ObjectPool { 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;

/**
 * 

@ProjectName: CEventCenter

*

@ClassName: CEventObjPool.java

*

@PackageName: com.freddy.event

* *

@Description: 事件对象池

*
*

@author: FreddyChen

*

@date: 2019/04/25 17:45

*

@email: [email protected]

*/ public class CEventObjPool extends ObjectPool { 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;

/**
 * 

@ProjectName: CEventCenter

*

@ClassName: I_CEventListener.java

*

@PackageName: com.freddy.event

* *

@Description: 事件监听器

*
*

@author: FreddyChen

*

@date: 2019/04/25 17:52

*

@email: [email protected]

*/ public interface I_CEventListener { /** * 事件回调函数 * 注意:
* 如果 obj 使用了对象池,
* 那么事件完成后,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;

/**
 * 

@ProjectName: CEventCenter

*

@ClassName: CEventCenter.java

*

@PackageName: com.freddy.event

* *

@Description: 类描述

*
*

@author: FreddyChen

*

@date: 2019/04/25 17:48

*

@email: [email protected]

*/ public class CEventCenter { private static final String TAG = CEventCenter.class.getSimpleName(); /** * 监听器列表,支持一对多存储 */ private static final HashMap 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 list = new LinkedList<>(); list.add(oldListener); list.add(listener); LISTENER_MAP.put(topic, list); } else if (obj instanceof List) { // 有多个监听器 LinkedList listeners = (LinkedList) 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 listeners = (LinkedList) 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 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) ((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举例吧:

  1. 在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地址

写在最后

这篇文章比较简单,由于之前的文章贴了大量的图片,导致可能加载过慢,而且在手机上看代码截图不是那么方便,所以这篇文章大部分换成了直接贴源码的方式,方便大家阅读。这个库实现的功能比较简单,所以源码也能全部贴上来了,如果此库对你有用,希望在github上给我一个star哈。。。另外,欢迎fork,期望大家与我一起完善。后续会陆续的形式开源一些平时工作中积累的、Android实用的库,希望大家会喜欢。。。

另外,创建了一个Android即时通讯技术交流QQ群:1015178804,有需要的同学可以加进来,不懂的问题,我会尽量解答,一起学习,一起成长。

The end.

你可能感兴趣的:(开源一个自用的Android事件分发中心库,实现类似系统广播功能。)