从源码一次彻底理解Android的消息机

情景重现

开头我们就看到了如上的一段简单的伪码。因为这里我试图去还原一种场景,一种可能我们不少人最初接触Android时可能都会遇到的错误场景。

这种场景的逻辑很简单:在程序运行中,某个控件的值会被动态的改变。这个值通过某种途径获取,但该途径是耗时的(例如访问网络,文件读写等)。

上面的伪码中的逻辑是:点击按钮将会改变文本框的值,且这个值是通过某种耗时的操作获取到,于是我们通过将线程休眠5秒来模拟这个耗时操作。

好的,现在我们通过编译正式开始运行类似的代码。那么,我们首先会收到熟悉的一个错误,即“ApplicationNoRspons(ANR)”。

接着,通过查阅相关的资料,我们明白了:原来我们像上面这样做时,耗时操作直接就是存在于主线程,即所谓的UI线程当中的。

那么这就代表着:这个时候的UI线程会因为执行我们的耗时操作而被堵塞,也自然就无法响应用户其它的UI操作。于是,就会引起ANR这个错误了。

现在我们了解了ANR出现的原因,所以我们自然就会通过一些手段来避开这种错误。我们决定将耗时的操作从UI线程拿走,放到一个新开的子线程中:

好的,现在我们模拟的耗时操作已经被我们放到了UI线程之外的线程。当我们信心十足的再次运行程序,确得到了如下的另一个异常信息:

从异常信息中,我们看到系统似乎是在告诉我们一个信息,那就是:只有创建一个视图层次结构的原始线程才能触摸到它的视图。

那么,我们似乎就能够理解这种异常出现的原因了:我们将耗时操作放在了我们自己创建的分线程中,显然它并非原始线程,自然就不能够去访问Viw。

这样设计的初衷实际上是不难猜想的,如果任何线程都能去访问UI,请联想一下并发编程中各种不可预知且操蛋的问题,可能我们的界面最终就热闹了。

但是,现在我们针对于这一异常的解决方案似乎也不难给出了。既然只有主线程能够访问Viw,那么我们只需要将更新UI的操作放到主线程就OK了。

那么,这里就顺带一提了。不知道有没有人和我曾经一样,想当然的写出过类似下面一样的代码:

是的,如上的代码十分的,非常的“想当然”。这当然是因为对Java多线程机制理解不够所造成的。更新UI的操作确实是放到了主线程,但是:

这并不代表着,UI更新一定会在分线程的耗时操作全部完成后才会执行,这自然是因为线程执行权是随机切换的。也就是说,很可能出现的情况是:

分线程中的耗时操作现在并没有执行完成,即我们还没有得到一个正确的结果,便切换到了主线程执行UI的更新,这个时候自然就会出现错误。

Handlr粉墨登场

这个时候,作为菜鸟的我们有点不知所措。于是,赶紧上网查查资料,看看有没有现成的解决方案吧。这时,通常“Handlr”就会进入我们的视线了:

我们发现关于Handlr的使用似乎十分容易不过,容易到当我们认为自己掌握了它的时候似乎都没有成就感:

首先,我们只需要建立一个Handlr对象。

接着,我们会在需要的地方,通过该Handlr对象发送指定的Mssag。

最后,该Handlr对象通过handlMssag方法处理接收到的Mssag。

但我们沉下心来想一想:Handlr为什么能够解决我们之前碰到的非原始线程不能更新UI的错误呢?它的实现原理如何?它能做的就只是更新UI吗?

掰扯了这么多,带着这些疑问,我们终于来到了我们这篇blog最终的目的,那就是搞清楚Android的消息机制(主要就是指Handlr的运行机制)。

从构造函数切入

就像医生如果要弄清楚人体构造,方式当然是通过解剖来进行研究。而我们要研究一个对象的实现原理,最好的方式就是通过分析它的源码。

个人的习惯是,当我们没有一个十分明确的切入点的时候,选择构造函数切入通常是比较合适的,那我们现在就打开Handlr的构造函数来看一下:

好的,分析一下我们目前所看的,我觉得我们至少可以很容易的分析并掌握两点:

Handlr自身提供了7种构造器,但实际上只有最后两种提供了具体实现。

我们发现各种构造器最终围绕了另外两个类,即Callback与Loopr。我们推测它们肯定不是做摆设的。

现在我们来分别看一下唯一两个提供了具体实现的构造器,我们发现:

除了”if(FIND_POTENTIAL_LEAKS)“这一段看上去和反射有关的if代码块之外,这两个构造器剩下的实现其实基本上是完全一致的,即:

唯一的不同在于mLoopr这一实例变量的赋值方式:

“mLoopr=Loopr.myLoopr();”这种方式究竟有何神奇,我们这里暂且不提。我们的注意力聚焦在以上看到的几个实例变量,打开源码看看:

mAsynchronous是一个布尔型的变量,并且我们看到默认情况它的构造值是fals,从命名我们就不难推测到,它多半与异步有关。除此之外:

其余类型分别是”MssagQuu,Loopr,Callback“。一定记住它们!!!正是它们配合Handlr及Mssag完成了整个消息传递的架构。

OK,首先我们来看Callback这个东西,从命名来看绝逼与回调有关系,打开源码,果不其然正是定义在Handlr内部的一个接口:

我们看到了其中唯一声明的一个方法接口,看上去似乎有点眼熟。是的,那么它与Handlr自身的handlMssag有何联系?我们暂且提不提。

现在,我们再接着看Loopr和MssagQuu两个类型。很遗憾的是,这里我们发现:这是另外单独定义的两个全新的类。也就是说:

目前我们似乎无法在逻辑上将其与Handlr联系起来。我们现在只知道从命名上来说,它们似乎分别代表着“循环”与“消息队列”的意思。

post()与sndMssag系列函数

那么,到了这一步似乎情况有点糟糕,因为似乎失去了下一步的切入点。没关系,这个时候我们回忆一下我们通常怎么样使用Handlr:

mHandlr.post();

mHandlr.sndMssag();

没错,我们基本上就是通过以上两种方式去使用Handlr。所以现在我们打开这两个方法相关的源码来看看:

由此我们发现的是:post函数最终调用的仍是snd系列的函数;而sndMssag底部也依然是通过sndMssagDlayd调用的。

并且!查看一系列的snd方法源码发现:它们最终都将通过sndMssagAtTim来完成整个调用。所以显然这将是我们下一个







































白癜风医院郑州哪家好
白癜风饮食



转载请注明:http://www.gslnbdf.com/azyx/1463.html