Android消息處理模型彙總

1. 概述

Android開發中,消息的處理是一項非常重要的事情,好的消息處理模型的建立對於系統穩定性和可維護性很重要,下面就日常開發中用到的消息處理模型做下彙總。

2.消息模型

2.1責任鏈消息處理模型

這種消息處理模型運用了責任鏈模式,使多個對象都有機會處理請求,從而避免了請求的發送者和接收者的耦合關係。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有對象處理它爲止。
責任鏈模式UML圖如下所示
在這裏插入圖片描述
簡單責任鏈模式UML圖
在簡單責任鏈模式下,消息可以理解爲一個簡單的字符串,將消息通過Client下發到責任鏈的一端,然後消息在這條鏈上流動起來,當某個Handler關心這條消息,那麼它就去處理它,消息處理到此終止,當它不關心這條消息時,將消息傳遞到下一個Handler,直到被處理爲止,當所有的Handler都不關心這條消息時,那麼我們需要做默認處理或者直接不care。
在這裏插入圖片描述
複雜責任鏈模式UML圖
複雜責任鏈模型設計中,與簡單模型不同的是,消息變得複雜了,不再是簡單的字符串消息,變成了AbstractRequest對象。雖然消息實體變的複雜了,但是處理流程還是一樣的,將這個請求消息在鏈上流動起來,由關心它的Handler類去處理。

在gs 應用開發中,我們最常處理的兩類消息,一類是dbus消息,一類是廣播消息。當待處理的消息較多時,我們通常的做法是通過條件判定來界定消息類型,然後針對不同消息類型做處理,當過多的消息放到同一代碼塊中處理時,不免造成代碼塊過長,業務複雜,難以維護,這個時候責任鏈的設計就派上用場了,通過AbstractHandler類將業務拆分,將不同類型的消息放到不同類型的AbstractHandler類中處理。下面以dbus消息處理爲例說明詳細的代碼設計流程:

  1. AbsHandler類及其子類
public abstract class AbsDbusHandler {
    protected final Context mContext;
    protected Handler mHandler = new Handler();
    protected AbsDbusHandler nextHandler;
    public AbsDbusHandler(Context context) {
        mContext = context;
    }
    public abstract boolean handle(String sigName, int argLength, List<DbusObj> argList);
    protected String getTag() {
        return getClass().getSimpleName();
    }
}

MetaSwitch Signal的處理

public class MetaSwitchSignalsHandler extends AbsDbusHandler {
    public MetaSwitchSignalsHandler(Context context) {
        super(context);
    }
    @Override
    public boolean handle(String sigName, int argLength, List<DbusObj> argList) {
        if (sigName.equals(DbusSignal.SIGNAL_METASWITCH_REQ_LOGIN)||
        sigName.equals(DbusSignal.SIGNAL_METASWITCH_LOGIN_ERROR)) {
            //do some thing
            return true;
        } else {
            if (nextHandler != null) {
                return nextHandler.handle(sigName, argLength, argList);
            } else {
                return false;
            }
        }
    }
}

Audio Signal消息的處理

public class AudioSignalsHandler extends AbsDbusHandler {
    public AudioSignalsHandler(Context context) {
        super(context);
    }
    @Override
    public boolean handle(String sigName, int argLength, List<DbusObj> argList) {
        if (sigName.equals(DbusSignal.SIGNAL_AUDIO_DEV)) {
            //do someting
            return true;
        } else {
            if (nextHandler != null) {
                return nextHandler.handle(sigName, argLength, argList);
            } else {
                return false;
            }
        }
    }
  1. HandlerChainFactory組裝責任鏈
    通過簡單工廠類將各個Handler串起來,組成一條鏈,特別注意不要將第一個Handler和最後一個Handler關聯,否則會形成環狀鏈,如果消息沒有可以處理的Handler類,那麼消息將一直在鏈上傳遞,所以最好有一個默認消息處理的Handler放到鏈的末端。
public class HandlerChainFactory {
    private static AbsDbusHandler dbusHandler;
    public static AbsDbusHandler productDbusChain(Context context) {
        if(dbusHandler == null){
            AbsDbusHandler metaSwitchSignalsHandler = new MetaSwitchSignalsHandler(context);
            AbsDbusHandler audioSignalsHandler = new AudioSignalsHandler(context);
            AbsDbusHandler upgradeSignalsHandler = new UpgradeSignalsHandler(context);
            metaSwitchSignalsHandler.nextHandler = audioSignalsHandler;
            audioSignalsHandler.nextHandler = upgradeSignalsHandler;
            dbusHandler = metaSwitchSignalsHandler;
        }
        return dbusHandler;
    }
 }
  1. 責任鏈的調用
protected AbsDbusHandler getDbusHandler(){
        return HandlerChainFactory.productDbusChain(mContext);
    }
private DbusCallback dbusCallback = new DbusCallback() {
        @Override
        public void onCallback(int sigId, String sigName, int argNum, List<DbusObj> argList) {
            if (!getDbusHandler().handle(sigName, argNum, argList)) {
            // do something, 未處理的dbus消息在這裏處理
            }

至此,一個簡單的責任鏈設計就完成,在開發過程中,大家可以參考實現類似場景的消息模型設計。
在Android框架中還有許多類似的責任鏈模式設計的例子,比如,在Android事件分發處理的流程中就使用到責任鏈的設計,
通過dispatchTouchEvent, onTouchEvent, onInterruptTouchEvent三個覆寫方法完成事件的分發處理。再不如,OkHttp3框架的interceptor的設計也使用了責任鏈模式設計。

OkHttp3架構設計圖
在這裏插入圖片描述
Android事件分發處理流程圖
在這裏插入圖片描述

2.2 對象池消息處理模型

池的概念大家並不陌生,對象池,連接池,線程池等等,在程序設計中引入池的目的是實現對象的複用,用的時候從池中獲取,用完再放回池子,對於消息的處理可以稱爲消息池,Android中消息處理採用的Handler+Looper+Message+MessageQueue的方式,具體的工作方式這裏不在贅述了,我們重點關注它的內部的消息模型的設計,讀過源碼應該比較清楚,它內部消息管理採用的
是消息池+鏈表的方式,消息池的目的是實現消息的複用,鏈表引入是爲了保證消息的順序。

消息池使用的場景如下:
1.需要頻繁大量創建對象
2.對象沒有特定身份
3.需要緩衝池的場景
在設計模式中,與對象池概念相通的設計模式叫做享元模式,存放對象必然需要一個容器,這些容器可以是一個數組,鏈表,或者map, 在對象池的使用中,我們必然需要不停的從對象池中獲取對象,用完後放回容器,那麼多線程場景下需要考慮線程安全問題。

實際開發中,當我們需要使用到對象池時並不需要從頭開發,只需要對Android Message機制做下簡單封裝就可達到目的。
例如,Android原生Telephony框架中,在處理rild AT命令時使用的就是這種對象池的設計,概括起來就是需要處理消息的發送和接收兩種場景,發送又可以分爲有迴應發送以及無迴應發送,具體的實現可以參考如下路徑:
frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java
內部類RILRequest類使用了對象池的設計,容器使用了鏈表的實現,代碼如下:

class RILRequest {
    static final String LOG_TAG = "RilRequest";

    //***** Class Variables
    static Random sRandom = new Random();
    static AtomicInteger sNextSerial = new AtomicInteger(0);
    private static Object sPoolSync = new Object();
    private static RILRequest sPool = null;
    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 4;
    private Context mContext;

    //***** Instance Variables
    int mSerial;
    int mRequest;
    Message mResult;
    Parcel mParcel;
    RILRequest mNext;

    /**
     * Retrieves a new RILRequest instance from the pool.
     *
     * @param request RIL_REQUEST_*
     * @param result sent when operation completes
     * @return a RILRequest instance from the pool.
     */
    static RILRequest obtain(int request, Message result) {
        RILRequest rr = null;

        synchronized(sPoolSync) {
            if (sPool != null) {
                rr = sPool;
                sPool = rr.mNext;
                rr.mNext = null;
                sPoolSize--;
            }
        }

        if (rr == null) {
            rr = new RILRequest();
        }

        rr.mSerial = sNextSerial.getAndIncrement();

        rr.mRequest = request;
        rr.mResult = result;
        rr.mParcel = Parcel.obtain();

        if (result != null && result.getTarget() == null) {
            throw new NullPointerException("Message target must not be null");
        }

        // first elements in any RIL Parcel
        rr.mParcel.writeInt(request);
        rr.mParcel.writeInt(rr.mSerial);

        return rr;
    }

    /**
     * Returns a RILRequest instance to the pool.
     *
     * Note: This should only be called once per use.
     */
    void release() {
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                mNext = sPool;
                sPool = this;
                sPoolSize++;
                mResult = null;
            }
        }
    }

    private RILRequest() {
    }

    static void
    resetSerial() {
        // use a random so that on recovery we probably don't mix old requests
        // with new.
        sNextSerial.set(sRandom.nextInt());
    }

    String
    serialString() {
        //Cheesy way to do %04d
        StringBuilder sb = new StringBuilder(8);
        String sn;

        long adjustedSerial = (((long)mSerial) - Integer.MIN_VALUE)%10000;

        sn = Long.toString(adjustedSerial);

        //sb.append("J[");
        sb.append('[');
        for (int i = 0, s = sn.length() ; i < 4 - s; i++) {
            sb.append('0');
        }

        sb.append(sn);
        sb.append(']');
        return sb.toString();
    }

    void
    onError(int error, Object ret) {
        CommandException ex;

        ex = CommandException.fromRilErrno(error);

        if (RIL.RILJ_LOGD) Rlog.d(LOG_TAG, serialString() + "< "
            + RIL.requestToString(mRequest)
            + " error: " + ex + " ret=" + RIL.retToString(mRequest, ret));

        if (mResult != null) {
            AsyncResult.forMessage(mResult, ret, ex);
            mResult.sendToTarget();
        }

        if (mParcel != null) {
            mParcel.recycle();
            mParcel = null;
        }
    }
}

下面以獲取IMS註冊狀態爲例子說明發送消息流程

在這裏插入圖片描述
1請求對象獲取

public void getImsRegistrationState(Message result) {
        //從對象池中獲取一個請求對象,具體過程可以參考RILRequest的obtain函數
        RILRequest rr = RILRequest.obtain(RIL_REQUEST_IMS_REGISTRATION_STATE, result);
        send(rr);
    }

2.請求對象回收
該處理流程主要在RILSender.handleMessage中處理

 //回調異常結果
 rr.onError(RADIO_NOT_AVAILABLE, null);
 //回收請求對象
 rr.release();
 decrementWakeLock();

實際在Android源碼中,google已經給我們提供了pool模型,具體源碼在

android/frameworks/support/v4/java/android/support/v4/util/Pools.java

內部給我提供了SimplePool和SynchronizedPool兩類對象池。

2.3 發佈訂閱式消息處理模型

發佈訂閱式的消息處理模型可以爲組件以及模塊間的通信提供解耦,使用觀察者模式設計,包括註冊事件監聽,發送事件,取消事件監聽等幾個步驟, 你可以選擇使用EventBus,但是EventBus選擇使用Class作爲事件的Tag,所以每監聽一個類型
事件就需要爲該事件創建一個新的類,我們也可以自己封裝個事件分發框架,通過定義事件topic來區分不同的Event, 通過
對象池設計來複用事件對象。

  1. 註冊事件監聽
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<I_CEventListener>();
                    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);
                }
            }
        }
    }

監聽器數據類型如下

/**
* 監聽器列表,支持一對多存儲, Object對象可以是List類型
*/
private static final HashMap<String, Object> LISTENER_MAP = new HashMap<String, Object>();

訂閱關心的Topic,並在相應Listener中處理

//訂閱的topic列表
private final String[] subscribeTopic = new String[]{Constants.EventTopic.SIP_DETECT_RESULT,
            Constants.EventTopic.AV_DETECT_RESULT, Constants.EventTopic.SIP_TRANS_PROTO,
            Constants.EventTopic.DETECT_STATE_CHANGED, Constants.EventTopic.AV_DETECT_GRADE};
//註冊topic主題
CEventCenter.registerEventListener(this, subscribeTopic);

//根據topic類型處理不同的事件
@Override
    public void onCEvent(final String topic, int msgCode, int resultCode, Object obj) {
        Logger.d(TAG,"onCEvent, topic = "+topic+", object = "+obj.toString());
        //TODO something, 根據topic類型執行不同的訂閱操作
   }
  1. 事件分發
   /**
     * 同步分發事件
     * @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);
        }
    }
    
    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);
        }
    }

根據EventTopic類型分發事件

int grade = 0;
grade = ((ProbeTaskResult) event).getGrade();
dispatchEvent(Constants.EventTopic.AV_DETECT_GRADE, grade);

SipResult sipResult = ((ProbeTaskResult) event).getSipResult();
if (sipResult != null) {
    dispatchEvent(Constants.EventTopic.SIP_DETECT_RESULT, sipResult);
}

List<TasksResultItem> avTaskResults = ((ProbeTaskResult) event).getTasksResult();
if (avTaskResults != null) {
    dispatchEvent(Constants.EventTopic.AV_DETECT_RESULT, avTaskResults);
}

public void dispatchEvent(final String topic, final Object event) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                CEventCenter.dispatchEvent(topic, 0, 0, event);

            }
        });
    }
  1. 註銷事件監聽
/**
     * 註銷監聽器
     *
     * @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);
                }
            }
        }
    }

通常在OnDestory()方法中取消事件監聽

@Override
    public void onDestroyView() {
        super.onDestroyView();
        //取消事件監聽
        CEventCenter.unregisterEventListener(this, subscribeTopic);
        
    }

具體代碼可以參考H60目錄下
vendor/grandstream/apps/SoftProbe/app/src/main/java/com/grandstream/cmcc/softprobe/eventcenter

2.4 總結

實際開發中還有許多其他的消息處理模型可以選擇,希望大家在認真思考實際場景,根據場景選擇適當的消息模型進行處理,這對於系統的穩定性至關重要。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章