一直以来,Handler于我而言都是难以形容的大麻烦。姑且不论线程通信一二三,就算是如何利用Handler来实现各项业务间消息传输,我也只能粗浅的说说如何跟踪消息而已。
跟它较劲了很久,零零碎碎总结了不少,总算是明白了些大概。
把昔日所做的笔记记录下来,权当作经验与教训与各位分享。理解有误的地方,还请多提建议。
我理解的Handler回调
先不说Handler,单问回调是什么,引用万能的wikipedia大神的话是这样的:
In computer programming, a callback is a piece of executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at someconvenient time. The invocation may be immediate as in a synchronous callback or it might happen at later time, asin an asynchronous callback.
我不知道诸位对它的解释是否满意,反正对我来说,说了跟没说一样。我理解回调的意义,知道回调的目的……但是,我不知道android Activity代码中一串串的onXXXX究竟是怎么来的!
这也许就是跨行从业的悲剧,一直学嵌入式硬件的我拼命阅读理解该死的底层代码,内核函数,仍然无法弄明白这个回调究竟是怎么起作用的,甚至怎么用都不明白。
直到有一天,我看到了这段话:
As your activity enters the paused state, thesystem calls theonPause() method on your Activity, which allows you to stopongoing actions that should not continue while paused (such as a video) orpersist any information that should be permanently saved in case the usercontinues to leave your app.
我终于有了那么点开窍,回调机制不就是披着软件马甲的硬件中断吗?
不知道在座各位是否了解嵌入式硬件中断,不过我知道做过单片机、dsp的同学肯定明白。所谓的中断,简单地讲就是当相应的中断源产生中断后,CPU会做现场保护,然后把PC指针跳进事先写好的中断服务程序中执行中断服务内容,执行完后CPU再恢复现场,接着该干嘛干嘛。
我这么说各位是否能看出来,这货活脱脱就是“回调”啊(事实上,回调机制更类似于软中断的概念,两者之间虽然有一定的联系,但绝对不是一码事。只是对于楼主而言,当初并不了解回调,而是通过中断流程才摸索出回调的原理,因此感触颇深而已,希望各位不要混淆了概念)。
某个事件源引起了系统响应,进而调用相应的回调函数来实现该事件源的处理内容,从而完成了“事件响应”的处理,两者都是“请求——等待——响应”的处理流程,自然在了解了中断机制之后,对回调的处理流程也就触类旁通了。
我可以不去纠结系统是如何产生和触发相应的事件源的,虽然在设计硬件时我需要区分中断是硬中断还是软中断,中断向量表是怎么写的,等等等等。但是这里我可以不管你,我只需要知道硬件层面有相应的事件源消息产生了。
我在意的是,什么样的事件源与什么样的回调函数挂钩。简单地说,我需要知道当前有事件源导致回调onCreate方法,有事件源导致回调onClick方法。
相应的,我的工作,简单的说就是编写对应事件源下的中断服务程序(回调函数)了。我知道按下Activity上的一个虚拟Button,系统应该进行某些动作(譬如弹出个菜单,拨出个电话等),那么我只需要详细的描述onClick这个回调就可以了(当然,还有一些其他的配置代码需要另行编写)。
这个理解也许还不是那么恰当,但对当时的我来说,确实清楚了android代码架构和过去过程式编码的一些区别——android代码恐怕把很多设计底层的内容都隐藏了,暴露给程序员的,更多的是一些工具方法,也许从某种意义上说,所谓的应用层编程倒像是在原有框架上一点点补足具体实现的内容。
Thread、Handler、Looper以及Message>
如果上面关于回调的内容讲的还算明白,请列位接着看这四个好基友的故事。
前文书说了,我一直维护framework侧代码,Activity什么的写的真心不多。那么在framework侧为什么会说到回调呢?
这是因为万恶的HandleMessage随处都在啊!
/** * When overridden the derived class needs to call * super.handleMessage(msg) so this method has a * a chance to process the message. * * @param msg */ @Override public void handleMessage(Message msg) { AsyncResult ar; … } |
老大说“……喏,记得把这几个消息回调实现了,然后看一下log成功没有”
我:“#¥%@……”
要说这四个好基友的关系,就需要理顺他们之间混乱的关系。
先把结论抛出来,我再慢慢的细说内容吧:
所谓的Handle-Message机制,与其说是某种复杂的消息处理机制,不如把它看成送快递的。打了收件人(target)标记的快递(message)被扔到一台不停旋转的传送带(Looper)上,系统每取出一个快递,就根据上面标签的内容把它发配到对应的收件人那去,收件人再根据标签上标注的签名(what)来分别处理不同的快递。
好了,接下来我讲讲我昔日的教训与心得,请诸位注意的同时也请说说自己的经验。
小误会引发的大问题
在理解Handle-Message机制的过程中,虽然整体的流程和概念我可以很快弄明白,但有个细节一直纠缠着我,如鲠在喉。
就是这个obtainMessage方法。
先来看一下,obtain的英文解释:
obtain[英][?b?tein] [美][?b?tein]
vt.获得,得到;流行;买到,达到(目的)
vi.通行,通用;流行;存在
可以见得,这个obtainMessage方法,应该指的是获取相应消息的意思,联系到它的定义:
public final Message obtainMessage(int what)
{ return Message.obtain(this, what); } |
我理所当然的认定这个方法指的是处理事件线程获取目标消息的意思……
因此,之后我的理解统统走了样,通过obtianMessage方法Handler线程得到了对应的消息,那么接下来就应该交给自己handleMessage来处理,可是这个SendMessage方法是什么!谁能告诉我为什么会有 sendMessageDelayed(obtainMessage(EVENT_CALL_RING_CONTINUE,token, 0), mCallRingDelay);这种情况。
把自己辛辛苦苦搜集到的消息再发出去算是怎么回事!?
我彻底混乱了。
所以,尽管看了不少的解析消息机制的文章,我还是非常痛苦的无法理解这个该死的Handle-Message到底是什么意思。
直到很久之后通读Message.java代码,我才发现所谓的obtian并不是从“消息源”中获取消息,而是从“消息池”中获取消息啊!所谓的obtianMessage过程,与其理解为获取消息,不如理解为“产生”消息更合适吧。
整个的流程应该是这么个顺序才对:
- 线程A从消息池中挖出一个消息,把自己的戳子,处理对象说明,附加内容等等都加上去,形成一个有实际意义的消息。
- 通过SendMessage方法,把这个消息加入目标线程的消息队列MessageQueue中去。
- 这个消息队列由目标线程的Looper维护,Looper会通过loop方法轮询队列中的消息,轮询到这个消息的时候,根据该消息中指向的target目标,调用对应Handler对象的dispatchMessage()方法把它分发给相应的处理方法。虽然通常来说,这个处理方法就是重写后的handleMessage()方法。
这个过程,宏观上看来,就是线程A抛出了一个消息,由自己或者是线程B在适当的时刻进行异步处理,这个处理内容就是handleMessage的回调函数。
把大象塞到冰箱里
原谅我用这个稍显夸张的副标题,不过它确实是我第一次理解Handler类时的真实感想。
为什么叫把大象塞到冰箱里?
原话是这么说的,请问把大象塞到冰箱里要几步?
这个Handler.java给我的感觉就是如此,使用Handler,可以不用纠结到把大象塞到冰箱里究竟应该怎么使劲,只需要知道“开门,塞进去,关门”就ok了。
为什么这么说?
你看我们平时都是怎么做的,上层注册一个消息,然后在对应线程中new一个Handler对象,在Handler对象里重写handleMessage方法处理消息的回调就ok了。细节什么的统统不用管啊……
而真实的情况是什么呢,我想有些就如我上节所写的,有些我还没理解。它应该是线程调度跟消息共享共同作用的结果。而底层封装的太过于漂亮,以至于上面运用它的人只需要开关冰箱门就搞定了消息的回调过程……真是惬意。
但是对于androider来说,光会开关冰箱门还是远远不够的,得深入了解才行。
关于Handler我仍有很多的疑问,譬如为啥sendMessage()方法中指定的msg.target为this,而不让形参直接传一个进来,这样不是更方便吗?(这个问题6楼给了个提示,不知这么理解是否正确:原理应该跟Handler实例的调用相关,试想sendMessage是一个实例方法而不是类方法,在调用时已经必然确定了处理用的Handler,所以target也无需重新定义了)
总之,handler是个非常值得学习的内容,搞定了这个,我才能继续说说跟呼叫业务消息相关的经验教训。
|