消息的注册到业务的实现
从接触呼叫业务的第一天起,我就一直琢磨代码里成群结队的registerXXXX方法跟notifyXXXX方法究竟是个怎么关系。
一个消息(Message)从注册到被处理(handleMessage)都经过了哪些步骤?为什么要通过这种设计来完成线程间通信?是什么决定哪些消息在哪些类中被处理?消息上报之后,framework层都完成了哪些工作?
诸如此类,一言难尽。
在过去的总结中,我倾向于通过业务层不同呼叫状态的实例含义来理解整个通话的过程,而不是专注于dial,hangup,rejectCall,acceptCall这类的具体通话流程。换句话说,我觉得,如果能掌握不断改变的通话状态与android系统所做的处理之间的联系,也就能明白呼叫业务、甚至是android通信流程的真正脉络。
而这个Register-Notify模型,恐怕就是理解它的钥匙之一。
当我们正在谈论注册(register)的时候,它们在干什么
所谓的registerXXX,简单的说就是在某个特定的泛型列表中添加一个新的泛型节点。
两个有趣的地方。
第一, 为什么要通过不同的泛型列表来搜集不同的消息?
第二, 添加的这个新的泛型节点都包括哪些内容?
首先来看第二个问题,一个新的泛型节点通常包含了以下三样东西:
Handler h, int what, Object obj
在一个注册方法被调用时,h代表了能处理这个消息,同时也继承了Hanlder类的线程名称,what代表了该消息标签名称,而obj代表了以基本对象为格式所封装的消息内容。
再来看一个新的Registrant泛型节点用这些参数都构成了什么。
public Registrant(Handler h, int what, Object obj) { refH = new WeakReference(h); this.what = what; userObj = obj; }
套用上一节发快递来理解Handler的思路,这个注册新节点,其实就是个对消息打包过程,我在一则消息中标注了它的收件人信息(refH),签名(what),以及消息本身(userObj)。
这时候再来看第一个问题,如果把同样的收件人(refH),同样签名(what)的消息放到同一个队列当中,维护这个队列实际上就维护了所有同类注册的消息。相对的,从这个队列中取出的每个节点也都有同样的属性,可以用一种方式进行处理。
那么,为什么我会在意这个问题?
因为android系统为了“更直观”地向用户表达变量的含义,通常会给代表同种类型事件的消息以相同名称的泛型列表来维护。
譬如说,就“通话状态发生变化”这个事件而言,android系统用相同名称的泛型列表mPreciseCallStateRegistrants来维护了不同的签名(what)的消息触发,代码结构上从下到上是这样的:
在CallManager.java中
phone.registerForPreciseCallStateChanged(mHandler,EVENT_PRECISE_CALL_STATE_CHANGED, null);
在PhoneUtils.java中
cm.registerForPreciseCallStateChanged(mConnectionHandler,PHONE_STATE_CHANGED, cm);
两者调用的形参,被引用的对象都不一样,只有方法名称是相同的。但就是这个相同的名称,在最开始把我弄的稀里糊涂,以为同一个泛型列表可以收录同种事件,但是内容不一样的消息,这是错误的。希望大家能够区分清楚这是两条不同的列表,只是代表的事件类型相同罢了。
当我们正在谈论通知(notify)的时候,它们在干什么
注册完一个消息,从某种意义上说,只是准备好了包裹而没有投递它。真正的投递过程是在notfiy过程完成的。
注意,我说的投递过程,而不是处理过程。
在一开始学习Register-Notify模型的时候,我一直想当然的以为那些处于handleMessage代码段中的notifyXXXX就是这个消息的处理。
但愿你不是这么想的,因为这是个直观但是错误的认知。
我们来看notifyXXXX实现了什么。
一段合格的notfiy代码,必然带着AsyncResult类型的形参一块被调用。追根溯源,其实调用的是RegistrantList.java里的这个方法:
private synchronized void internalNotifyRegistrants (Object result,Throwable exception) { for (int i = 0, s = registrants.size();i < s ; i++) { Registrant r = (Registrant) registrants.get(i); r.internalNotifyRegistrant(result,exception); } }
诸位见到了,Ar格式的形参数据,其实包含了一个结果消息result,以及封装在内的异常信息exception。
再来看Registrant.java类的这个方法:
/*package*/ void internalNotifyRegistrant (Object result,Throwable exception) { Handler h = getHandler(); if (h == null) { clear(); } else { Message msg = Message.obtain(); msg.what = what; msg.obj = new AsyncResult(userObj,result, exception); h.sendMessage(msg); } }
这下清楚了。它从我的收件人(refH)封装中取出对应的收件人信息(h=getHandler()),然后从消息池中挖出一个消息,把之前写在包裹上的标签(what)给标注在消息上,然后根据消息的内容(userObj),结果信息(result)以及异常信息(exception)重新打包为消息的内容。
接着把它发出去。就像上一节我们看到的sendMessage方法一样,把这个包裹扔到待处理的线程消息队列中去。
然后……然后就像上一节描述的那样样,系统的回调机制会分发这条消息,最终在指定的Handler对象的handleMessage方法中完成这个消息的处理。
这里可能有人要问,既然同样是消息内容,都可以为基本对象格式,那么result与UserObj内容有什么区别。
我是这么理解的。直观的看UserObj内容在消息注册时由register方法给定,而result内容则是消息在投递时由notify方法给定,两者的内容来源不同,设计者根据情况需要进行不同的消息设计。
当然,在实际运用中,由于消息触发的嵌套机制,A消息收到的UserObj内容可能就是B消息的result内容,这也是有可能的。
不算结束的总结
零零碎碎说了这么多,想起之前总结的内容,关于Register-Notify模型一节的内容,确实还有一些不恰当的地方,这里做一个修正,希望能够与诸位不断的深入理解android Handler机制。
其实,Register-Notify的模型这么画也许更合适:
当然,这只是个诸多消息回调中比较典型的一种罢了。
最后再补一句。
在android framework层并非所有的消息都是通过Handler回调机制来完成的。譬如跟RIL层接口的ril.java文件,尽管也用到了obtain和send方法来实现消息的产生与投递,但事实上它的收发机制是socket套接字的方式与RILD进行数据交互,所以对它的消息收发仍需要仔细研究代码才可以。