1. 概述
Android開發中,消息的處理是一項非常重要的事情,好的消息處理模型的建立對於系統穩定性和可維護性很重要,下面就日常開發中用到的消息處理模型做下彙總。
2.消息模型
2.1責任鏈消息處理模型
這種消息處理模型運用了責任鏈模式,使多個對象都有機會處理請求,從而避免了請求的發送者和接收者的耦合關係。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有對象處理它爲止。
責任鏈模式UML圖如下所示
簡單責任鏈模式UML圖
在簡單責任鏈模式下,消息可以理解爲一個簡單的字符串,將消息通過Client下發到責任鏈的一端,然後消息在這條鏈上流動起來,當某個Handler關心這條消息,那麼它就去處理它,消息處理到此終止,當它不關心這條消息時,將消息傳遞到下一個Handler,直到被處理爲止,當所有的Handler都不關心這條消息時,那麼我們需要做默認處理或者直接不care。
複雜責任鏈模式UML圖
複雜責任鏈模型設計中,與簡單模型不同的是,消息變得複雜了,不再是簡單的字符串消息,變成了AbstractRequest對象。雖然消息實體變的複雜了,但是處理流程還是一樣的,將這個請求消息在鏈上流動起來,由關心它的Handler類去處理。
在gs 應用開發中,我們最常處理的兩類消息,一類是dbus消息,一類是廣播消息。當待處理的消息較多時,我們通常的做法是通過條件判定來界定消息類型,然後針對不同消息類型做處理,當過多的消息放到同一代碼塊中處理時,不免造成代碼塊過長,業務複雜,難以維護,這個時候責任鏈的設計就派上用場了,通過AbstractHandler類將業務拆分,將不同類型的消息放到不同類型的AbstractHandler類中處理。下面以dbus消息處理爲例說明詳細的代碼設計流程:
- 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;
}
}
}
- 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;
}
}
- 責任鏈的調用
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, 通過
對象池設計來複用事件對象。
- 註冊事件監聽
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類型執行不同的訂閱操作
}
- 事件分發
/**
* 同步分發事件
* @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);
}
});
}
- 註銷事件監聽
/**
* 註銷監聽器
*
* @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 總結
實際開發中還有許多其他的消息處理模型可以選擇,希望大家在認真思考實際場景,根據場景選擇適當的消息模型進行處理,這對於系統的穩定性至關重要。