当前位置: 首页 > news >正文

开源一个自用的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;

/**
 * <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举例吧:

  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.

相关文章:

  • 九九乘法表
  • CentOS 7 LNMP部署—php
  • 利用原生javascript完成倒计时
  • pycharm 设置py文件的默认模版头部信息
  • 关于stackoverflow网页加载很慢的解决办法
  • 由比特币说起 - 区块链的前世今生及未来趋势
  • 典型用户和场景描述(团队项目)
  • 腾讯课堂:腾讯大神带你进阶性能测试
  • springboot整合mybatis开发
  • TypeScript+Webpack+React组件库开发采坑实记
  • gitlab的使用(待书写)
  • ssh免密码登陆设置时bad ownership or modes for file 报错的解决办法
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算
  • 模块讲解
  • 真·APIO2018滚粗记
  • $translatePartialLoader加载失败及解决方式
  • ComponentOne 2017 V2版本正式发布
  • iOS动画编程-View动画[ 1 ] 基础View动画
  • JSONP原理
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • Puppeteer:浏览器控制器
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • webgl (原生)基础入门指南【一】
  • 前端攻城师
  • 前嗅ForeSpider采集配置界面介绍
  • 入手阿里云新服务器的部署NODE
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 通过几道题目学习二叉搜索树
  • 小程序开发中的那些坑
  • 主流的CSS水平和垂直居中技术大全
  • - 转 Ext2.0 form使用实例
  • MiKTeX could not find the script engine ‘perl.exe‘ which is required to execute ‘latexmk‘.
  • raise 与 raise ... from 的区别
  • ​一、什么是射频识别?二、射频识别系统组成及工作原理三、射频识别系统分类四、RFID与物联网​
  • (ISPRS,2023)深度语义-视觉对齐用于zero-shot遥感图像场景分类
  • (LNMP) How To Install Linux, nginx, MySQL, PHP
  • (pojstep1.1.1)poj 1298(直叙式模拟)
  • (Redis使用系列) SpirngBoot中关于Redis的值的各种方式的存储与取出 三
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (原創) X61用戶,小心你的上蓋!! (NB) (ThinkPad) (X61)
  • (原創) 如何安裝Linux版本的Quartus II? (SOC) (Quartus II) (Linux) (RedHat) (VirtualBox)
  • .bashrc在哪里,alias妙用
  • .net core 6 集成 elasticsearch 并 使用分词器
  • .NET NPOI导出Excel详解
  • .NET Standard、.NET Framework 、.NET Core三者的关系与区别?
  • .net 前台table如何加一列下拉框_如何用Word编辑参考文献
  • .NET 中使用 Mutex 进行跨越进程边界的同步
  • .net/c# memcached 获取所有缓存键(keys)
  • .NET面试题解析(11)-SQL语言基础及数据库基本原理
  • .NET实现之(自动更新)
  • /etc/sudoers (root权限管理)
  • [20170728]oracle保留字.txt
  • [BZOJ1089][SCOI2003]严格n元树(递推+高精度)
  • [C++数据结构](22)哈希表与unordered_set,unordered_map实现
  • [caffe(二)]Python加载训练caffe模型并进行测试1