開源一個自用的Android事件分發中心庫,實現相似系統廣播功能。

歡迎轉載,轉載請註明出處:juejin.im/post/5cbe81…java

寫在前面

因爲上一篇文章《開源一個自用的Android IM庫,基於Netty+TCP+Protobuf實現。》獲得了不錯的反響,激發了寫做的興趣,趁着時間空閒,決定繼續寫一些文章,如下這篇,是一個自定義的Android事件分發中心庫,實現相似系統廣播EventBusRxBus的事件發佈-訂閱功能,後續有時間,會繼續完善以前的NettyChat庫,包括加入WebSocket協議實現UDP協議實現以及你們須要的UI頁面的封裝(包含會話列表頁、消息列表頁等),敬請期待。android

本文的CEventCenter基於對象池接口回調實現,主要解決在Activity/Fragment/Service之間的消息事件傳遞問題,因爲做者水平有限,文中有不對之處歡迎批評與指正。git

老規矩,先來看看效果: github

最終運行效果
能夠看到,在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中開啓子線程bash


爲何不用RxBus?

其實我也有在用,記得在17年初的時候,咱們當時在作一個直播項目,其中的消息事件傳遞,就是用的RxBus,當時是這樣的:觀衆給主播送禮,是經過im給服務端發送消息,服務端收到送禮消息後,處理送禮的邏輯,而後給客戶端返回送禮的狀態消息,客戶端收到消息後,經過RxBus把消息傳遞到Activity(其實這裏不論是經過im仍是http接口請求,都存在相同的問題)。在壓測的時候,是每一個50ms送一個禮物,很大的機率會出現一個bug,就是下圖這個: ide

RxBus背壓bug
出現這個bug,是由於咱們當時用的RxBus版本,內部是使用的RxJava1.0,而RxJava1.0是有一個設計缺陷的,也就是不支持背壓,簡單地說,拋出MissingBackpressureException每每就是由於,被觀察者發送事件的速度太快,而觀察者處理太慢,並且你尚未作相應措施,因此報異常。
當時心態炸了啊,由於項目比較龐大,撇棄RxBus的話,那工做量將很是巨大,並且當時項目着急上線,無奈之下,只能把可能出現這個bug的全部地方,替換成本身實現的CEventCenter,後續再逐步逐步替換...固然了,目前的RxJava已經修復了背壓的問題,而CEventCenter在那以後也一直在使用,目前來講暫時沒發現有什麼問題,因此也就保持在用了,你們若是採用此庫,在使用過程當中若是發現問題,煩請聯繫我,我我的也是不提倡重複造輪子的,若是目前有比較好用的庫,那就不必從新開發一個,固然,若是時間容許,本身寫一個其實也不錯,至少在這過程當中,必定會有所收穫。

爲何不用EventBus?

目前EventBus最新的版本應該是3.x,這裏面就有一個比較坑爹的設計:事件只能經過事件的類名來區分。
這至少帶來了3大問題:函數

  1. 操做麻煩,每個事件,都要定義一個類;
  2. 增長方法數;
  3. 致使事件發送者和接收者都依賴耦合事件類。

以上摘自:EventBus的缺點及改進升級,原做者也提供了改進的思路,有興趣能夠進去看看,固然了,EventBus的線程模型設計和粘性事件的支持是很是好的。post


什麼是對象池?爲何要使用對象池?

上面有提到,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);
        }
    }
}
複製代碼

代碼比較簡單,就是註冊監聽器->分發事件->事件回調響應->註銷監聽器四個步驟,支持一對1、一對多發佈主題事件,事件分發完畢後,把對象放回對象池裏面,便於對象複用。
使用方法,拿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) 方法註銷監聽器,以下圖:
    A Activity註冊監聽器
    接着,在B Activity中,調用CEventCenter.dispatchEvent(CEvent event/ String topic, int msgCode, int resultCode, Object obj) 方法發佈事件便可,以下圖:
    B Activity發佈事件
    這時會發現,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.

相關文章
相關標籤/搜索