美文网首页
旋转寿司店的故事:从Handler,Looper,Message

旋转寿司店的故事:从Handler,Looper,Message

作者: redden33 | 来源:发表于2018-08-18 17:46 被阅读0次

    第一部分 线程间通信

          在操作系统里,有进程和线程两个概念。进程即一个独立的为了完成某个特殊功能的应用程序,而线程则是一个进程的一个副本,所以一个进程可以有多个线程。而android继承自Linux,在android中的进程就是APP,线程就是APP中开辟出来的各种线程。

            可能有的人会问,没有线程也可以啊,比如之前学的c语言里,不都是从头到尾的逻辑处理吗,完全没有用到线程这个概念。这是因为那个时候的程序逻辑和计算量都不复杂,在主线程里可以很快地解决。但是,一旦程序到了足够复杂的地步,有些耗时或者不确定性较高的操作是不可能挂在主线程里的,因为主线程作为和用户主要打交道的手段,必须及时响应用户的操作,如果在5s内未响应产生了ANR,对用户体验是很沉重的打击。

            解释了线程存在的合理性,那么多线程开发也是一种可以理解的概念,他可以提升用户体验和执行效率。这里就牵扯到了一个问题,在不同的线程间是如何通信的?如果一个线程不能传入和传出数据,那么它的作用就会大打折扣,他不能保证与其他进程的协同工作。就像团队开发缺少队员间的沟通一样。现实生活中队友的沟通通过语言传递,在Android线程通信中则是依靠Handler,Looper,MessageQueue,Message这四个类。

    第二部分 线程间通信的两个例子

               为了形象地解释这四者在线程通信中的所扮演的角色,我举一个例子,那就是旋转寿司(好吧其实我也没吃过),如果我们把线程间通信看做吃寿司这件事,不同的顾客(Handler)在不同的地方(线程)里捕获食材,交给店主(Looper),店主根据不同的食材制作好各具特点的寿司(Message),放置在旋转餐桌(MessageQueue)上,店主为顾客们递上专属于自己的寿司。

            回到Android的世界,不同的线程执行完相应的逻辑后会发送message至MessageQueue,Message运输着通信的内容,而Looper负责将不同的Message分发给不同的Handler进行处理。一个典型的在主线程中进行线程间通信的例子如下:

            可能有人就有疑惑了,为什么在这里Looper和MessageQueue都没有出场过呢?这是因为每个APP都有一个默认的主线程,在主线程里Android已经初始化了一个Looper,而Looper的构造方法里MessageQueue和创建Looper的当前线程是作为Looper的成员变量存在的,这保证了三者的紧密联系。

            继续,我们再看看在子线程里是如何实现线程通信的(源代码里刚好有就直接截下来了):

            可以看到区别在于多了一个Looper.prepare()和Looper.loop(),在主线程里这一步已经通过Looper.prepareMainLooper()默认执行了。并且还是不可退出模式,我们可以在SystemServer类中找到Looper.prepareMainLooper()的调用。

           综上所述,线程间通信可以概括为接受机制和发送机制。接受机制是由Looper.prepare(),Handler继承类的创建和Looper.loop()这三步组成的,而发送机制是Handler继承类的各种sendMessage()方法。

            那么,这两个机制是如何实现精准的消息分发机制呢,一个Message如何能保证它被对应的Handler接收,或者,万一根本没有Handler对应这个Message呢?带着这样的疑问,我们进入第三部分,研究Looper.prepare(),Looper.loop()和sendEmptyMessage()方法的源码。

    第三部分

             在下图的介绍中我们可以看到Looper.prepare()意味着准备循环消息,而Looper.loop()意味着开始循环处理消息。

    接下来我们看这两个方法里的具体逻辑是如何实现消息分发和处理的:

            sThreadLocal是Looper类的一个成员变量,它是一个泛型为Looper的ThreadLoacl对象,而ThreadLoacl是一种存储的数据结构。

            在Loop.parpare()中,通过sThreadLocal.get()方法得到当前线程的Looper,如果为空,则初始化一个新的Looper存入sThreadLocal。如果非空则会报错,这保证了每个进程只有一个Looper对象。现在回到第二部分,如果我们在主线程中使用Loop.prepare()会发生什么呢?

            这证明了在主线程中确实已经有了一个Looper对象,我们直接使用它即可,只有在子线程里我们才需使用Loop.prepare()为该子线程初始化并绑定一个Looper。接下去看Looper.loop()方法。

            首先,loop()方法通过myLooper()得到当前线程的Looper对象me,不过追溯一下我们看到用的还是sThreadLocal.get()方法。接下去我们可以看到一个死循环,在死循环中Looper开始分发message,死循环意味着如果我们不主动结束loop,它自己是不会停止的,这将会造成内存泄漏,这点我们之后再谈。另外在循环中对message的遍历是通过MessageQueue.next()方法,这意味着MessageQueue其实是一个单向链表。

            消息的分发处理过程非常复杂,这里我们只看最关键也是最易懂的一部分:

            在这里就要引入对message属性的简绍,除了常见的用来传递内容的what,arg1,arg2,object属性,message还有一个target属性,这是一个什么类型数据呢,从名字上看,这是一个跟message有目标关系的类,在本文提及的三个类中,有哪个类调用了message呢?答案就是Handler,还记得在主线程里最后用到的那个handler.sendEmptyMessage()吗?

            也就是说,Message和Handler之间其实是紧紧联系着的。那么一个message的target属性是什么时候被赋值的呢?我们看一看handler.sendEmptyMessage()的源代码。

    此时调用了mQueue对象,我觉得这是创建Handler线程的MessageQueue对象 部分

            这是一个六步的调用关系:sendEmptyMessage()--->时间延迟为0的sendEmptyMessageDelayedenqueueMessage()--->sendMessageDelayed()--->sendMessageAtTime()--->enqueueMessage()--->MessagQueue的enqueueMessage()。

            其实所有的类sendMessage方法都可以回溯到sendMessageAtTime()方法,另外 在sendEmptyMessageDelayed()中,通过Message.obtain()方法从消息池获取一个Message,这是出于节约内存的考虑,所以从已有Message中选择而非直接new一个出来。而与之对应的则是在Loop.loop()中,分发完消息后会msg.recycleUnchecked()回收消息。

            在经历了多层寻找后,我们在Handler的enqueueMessage()方法中发现了msg.target =this,为了方便理解这句代码,我们不妨把handler.sendEmptyMessage()看做handler.enqueueMessage(),那么handler.sendEmptyMessage(msg){msg.target =this}就不难理解了。在发送消息的时刻,Message和Handler就绑定在了一起。

            最后一步也很重要,它调用了mQueue对象,即将该mes发送到该MessageQueue。我在浏览了几个用到callback类的Handler构造方法中可以看到mQueue是指向也创建Handler线程的MessageQueue对象。但在Handler的匿名继承类中我不清楚mQueue的指向,如果有谁知道请不吝赐教。

            回到Loop.loop()方法中的msg.target.dispatchMessage(msg),现在也就是Handler.dispatchMessage(msg),源码如下:

            至此我们可以看出Looper.loop()循环调用了handleMessage方法处理消息,接下去总结整个消息处理的流程。

    第五部分 总结

             线程间通信可以概括为接收机制和发送机制,Handler,Looper,Message,MessageQueue四个类在其中协调工作。

            发送机制:首先创建Handler继承类,再调用Handler的类sendMessage()方法,将Message发送到创建该Handler的线程的MessageQueue中。并且这些这些发送方法都实现了msg.target =this,即把发送出去的Message和发送它的Handler通过target属性绑定在了一起,确保了Message和Handler的可靠联系。

            接受机制:1.Looper.prepare()准备好Looper和配套的MessageQueue。

                               2.Handler继承类的创建,重写handleMessage()方法等待调用

                               3.Looper.loop()开始循环,对MessageQueue队列中的Message进行遍历,对每个Message调用message.target.dispatchMessage()方法,其中message.target为发送该Message的Handler类,dispatchMessage()方法调用了第二个过程中被重写的handleMessage()方法,保证了消息的处理。

    第六部分 旋转寿司

            回到旋转寿司上,我们发现,每个顾客(Handler)其实想吃什么食材(Message)都是有天生喜好的(重写handleMessage)。每个顾客带着自己想吃的食材来到旋转寿司店,看似把食材交给店主处理(sendMessage),其实自己早已下了毒(target),只有吃过解药的自己才能享用。当如此多的客人看着一盘盘美妙的寿司经过餐桌(MessageQueue),谙熟客人们攀比之心的店主(Looper)也只好无奈得根据毒性一一为顾客们端上专属于他们的寿司。说到底,店主太厉害了,无论是在南极还是北极,只要客人们捕获了食材,他都可以立即接收到这些珍馐(线程间通信),但是他的杰出刀法(UI操作)却只能在这家寿司店(主线程)进行。此刻,客人们享用着店主的杰作,而店主呢,他却在看着窗外的远方,远处,别的旋转寿司店也是亮着灯光。原来,这世界上还有别的旋转寿司店,那里面的客人,吃的也很开心。

    相关文章

      网友评论

          本文标题:旋转寿司店的故事:从Handler,Looper,Message

          本文链接:https://www.haomeiwen.com/subject/eqkfiftx.html