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

栏目: Android · 发布时间: 5年前

内容简介:由于上一篇文章本文的CEventCenter基于老规矩,先来看看效果:

由于上一篇文章 《开源一个自用的Android IM库,基于Netty+TCP+Protobuf实现。》 得到了不错的反响,激发了写作的兴趣,趁着时间空闲,决定继续写一些文章,以下这篇,是一个自定义的Android事件分发中心库,实现类似 系统广播EventBusRxBus 的事件发布-订阅功能,后续有时间,会继续完善之前的 NettyChat 库,包括加入 WebSocket协议实现UDP协议实现 以及大家需要的UI页面的封装(包含会话列表页、消息列表页等),敬请期待。

本文的CEventCenter基于 对象池接口回调 实现,主要解决在Activity/Fragment/Service之间的消息事件传递问题,由于作者水平有限,文中有不对之处欢迎批评与指正。

老规矩,先来看看效果:

开源一个自用的Android事件分发中心库,实现类似系统广播功能。
可以看到,在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,就是下图这个:

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

出现这个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举例吧:

  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) 方法注销监听器,如下图:
    开源一个自用的Android事件分发中心库,实现类似系统广播功能。
    接着,在B Activity中,调用 CEventCenter.dispatchEvent(CEvent event/ String topic, int msgCode, int resultCode, Object obj) 方法发布事件即可,如下图:
    开源一个自用的Android事件分发中心库,实现类似系统广播功能。
    这时会发现,A Activity的 onCEvent(String topic, int msgCode, int resultCode, Object obj) 会回调,在该方法里执行相应的逻辑处理就可以了。

我们来看看运行效果:

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

可以看到,在B Activity发布事件后,A Activity中的TextView文本改变了。另外,多个事件监听器注册/注销用法都一样,只是把String替换成String[]即可,就不介绍啦。

github地址

写在最后

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

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

The end.


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Dive Into Python 3

Dive Into Python 3

Mark Pilgrim / Apress / 2009-11-6 / USD 44.99

Mark Pilgrim's Dive Into Python 3 is a hands-on guide to Python 3 (the latest version of the Python language) and its differences from Python 2. As in the original book, Dive Into Python, each chapter......一起来看看 《Dive Into Python 3》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

MD5 加密
MD5 加密

MD5 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具