Android源码学习-----HandlerThread

Posted 予有荣焉

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android源码学习-----HandlerThread相关的知识,希望对你有一定的参考价值。

HandlerThread

1.run()方法

HandlerThread 从继承关系上看, 它继承Thread类, 由此可以得知这个类其实是一个线程类,既然是一个线程类, 那么肯定是要重写Thread中的run()方法, 所以可以浏览下run()方法

从红色箭头的三个方法中, 看到有三个方法, Looper.prepare(), Looper.myLooper(), Looper.loop(),  这三个方法其实是, 在子线程创建Handler时所要调用的三个方法, 在HandlerThreadle类中, 和其它类的区别在于创建了一个Looper, 让它能够在该线程中创建Handler对象

 

1.1 run()方法中的Synchronized和notifyAll

通常情况下, 只要出现了synchronized关键字, 往往意味着此处会造成线程安全问题, 并且这里调用了Thread中的notifyAll()方法来唤醒其它线程, 此处应该能够推断出, 既然调用了notifyAll方法, 那么很有可能其它地方使用了wait()方法, 具体调用位置请看 getLooper方法

 

2.getLooper()方法

该方法中有一行注释, 大概意思是, 如果当这个线程已经被创建了, 但是looper还没有创建完成, 此时需要让线程进入wait()状态,  等待looper对象创建完成, 当looper对象在run()方法中通过Looper.myLooper()创建完成后, 会紧接着调用notifyAll()方法来唤醒wait()状态下的线程

 

3.    quit()方法和quitSafely()方法

3.1 用途

这两个方法的用途其实都是终止looper的, 因为每次创建一个HandlerThread对象, 调用run()方法, 就会创建一个Looper对象, 而Looper是一个死循环, 这样会消耗大量的资源, 所以正确的操作方式为, 当界面销毁的时候, 调用quit()方法或者quitSafely()方法, 将Looper停止掉

 

3.2 源码

两个方法看起来很类似, 区别在于一个调用了looper对象中的quit()方法, 另一个调用了quitSafely()方法, 跟进源码

图中的mQueue其实是一个MessageQueue对象, 这两个方法最终调用的就是MessageQueue中的quit方法,只不过传入的参数分别是true 和 false

 查看MessageQueue中的quit方法

传入的boolean值最终在这里判断, 如果为true ,也就是调用的是quitSafely()方法, 那么执行的是removeAllFutureMessagesLocked()方法, 如果为false, 那么调用的就是quit()方法, 最终执行的就是removeAllMessagesLocked()方法.

继续跟进removeAllFutureMessageLocked()方法, 里面也会调用方法 removeAllMessageLocked(),

这两个方法的有一个共同点就是, 当方法被调用时, Looper不会再接收新的Message消息,

区别在于不带future的方法, 会将消息队列中的所有类型的消息清空, 无论是延迟发送的还是立即执行的, 而带future的方法, 只是清除掉了延迟消息, 对于非延迟消息, 则会将其交给handler去执行.

图片上面是源码的处理,

但是, 为什么n.when > now 就表示后就可以跳出循环,意味着没有立即执行的消息了?

这是因为MessageQueue消息队列处理方式的关系, 简单的可以理解为, MessageQueue消息队列是按照消息的处理时间的从早到晚排序的

 

4.    MessageQueue插入消息的方式简介

Handler将一个消息发送出去后, 这个消息会被插入到消息队列, 但并不是无脑的将这个消息插入到最末尾, 先看下Message中的部分成员属性

When:是这个消息的执行时间, 也就是判断这个消息是否是一个延迟消息, 以及延迟多久

Data: 是这个消息中携带的数据, 并且可以看出Message是利用Bundle来处理消息的

Next: 是一个消息对象, 上面有一行注释, 大概意识是有的时候需要用next来存储一些链关系, 这其实是类似链表的结构, 链表结构中有头指针 头节点等概念, 这里面假设mMessage永远指向队列中的第一个Message

 

当一个新的消息发送过来时, 分情况分析,

第一种情况, 满足放在首位的判断条件, 那么就让mMessages来指向这个新插入的消息即可, 那么放在首位的条件是什么? 请看下面的源码

熟悉handler源码的应该知道, Handler发送消息, 无论调用哪个方法, 最终都是由sendMessageAtTime()这个方法来执行, 而Handler的sendMessageAtTime()方法中调用了这个enqueueMessage()方法, 在enqueueMessage()这个方法中,  首条消息对象mMessage赋值给了 一个Message对象 p, 然后对这个p进行了一些列判断,

P == null: 这个判断表示消息队列中mMessage对象是否为null, 如果为null, 那么证明此时整个消息队列都是空的, 所以满足这个条件, 新的消息可以放到第一个位置

When == 0: when就是 handler发送消息时传入过来的消息触发时间, 如果为0, 表示这个消息是个非延迟消息, 需要立刻执行, 此时新的消息也要放到第一条,

When < p.when: 也就是handler新发送的这个消息的触发时间, 小于第一条消息的触发时间, 那么也要放到第一个位置

只要满足上面三种情况, 那么就让mMessage指向新插入的消息即可,

 

源码

 

 第二种情况

也就是新发送来的消息msg的触发时间, 要晚于mMessage的时间, 那么就会将msg的when和消息队列后面的每个时间when做下对比, 直到符合从早到晚的顺序为止

 

可以看出源码中有一个死循环, 会一直判断消息队列的后面是否还有消息, 以及触发时间, 当满足了任意一个条件后, 就会停止循环判断, 然后将新消息msg插入到队列中

源码中可以看出声明了一个新的消息对象 prev, 这个对象永远指向的是msg的前一个消息

 

 下面是过程图, 图中0x1010等假设为消息的地址, 仅供理解参考用, 不为真实地址, 所以无论何时, prev永远记录的是p的前一个地址

当新的消息msg找到何时的位置时, 也就是prev.when <  msg.when  < p.when时,  就会将msg插入到prev之后,  p之前这个位置

 

到此为止, 新的消息msg就插入到了MessageQueue队列中了

如有错误, 敬请指正, 感激不尽!

以上是关于Android源码学习-----HandlerThread的主要内容,如果未能解决你的问题,请参考以下文章

Android Handler消息机制03-Message源码学习

Android Handler消息机制03-Message源码学习

Android Handler消息机制01-Message源码学习

Android-View的绘制源码学习总结

Android Handler消息机制02-Looper源码学习

Android开发学习之路-Handler消息派发机制源码分析