Handler机制
Handler机制主要是用作线程间通信,尤其是主线程和子线程之间的通信。
Handler机制里面涉及到四个对象:Handler,message、MessageQueue、Loo per
- Handler:消息的处理者,负责将Message添加到消息队列以及对消息队列中的Message进行处理。
- 主线程创建一个Handler对象,重写handleMessage()方法
- 在子线程中创建一个Message对象,保存要传递的消息。通过Handler的sendMessage()方法发出消息
- Handler.sendMessage: 把消息加入到主线程的MessageQueue中,主线程中的Looper从MessageQueue中取出消息,调用Message.target.handleMessage方法
- Handler.post: 基于Handler.sendMessage,把消息加入到主线程的MessageQueue中,主线程中的Looper从MessageQueue中取出消息,调用Message.callback.run方法
- 这条message被添加到MessageQueue中等待处理。MessageQueue: 消息队列,用来存放通过Handler发布的消息,按照先进先出执行。
- Looper (消息队列管家):Looper发现有新消息到来时,就会处理这个消息。会调用Looper.loop()方法来
- 一个线程最多只有一个Looper对象。当没有Looper对象时,去创建一个Looper
- **在Looper的构造方法里面,会创建消息队列MessageQueue,**并让它供Looper持有,因为一个线程最多只有一个Looper对象,所以一个线程最多也只有一个消息队列。然后再把当前线程赋值给mThread。
创建Handler还是需要调用Looper.prepare的,我们平常在主线程不需要手动调用,是因为系统在启动App时,就帮我们调用了。并且还需要调用Looper.loop方法
所以使用Handler通信之前需要有以下三步:
-
调用Looper.prepare()
- 所以Looper.prepare()的作用主要有以下三点
- 创建Looper对象 & 存放在ThreadLocal变量中
- 创建MessageQueue对象,并让Looper对象持有(在Looper的构造方法里面,会创建消息队列MessageQueue,并让它供Looper持有)
- 让Looper对象持有当前线程
-
创建Handler对象
-
调用Looper.loop()
- 从当前线程的MessageQueue从不断取出Message,并调用其相关的回调方法。
-
判断了当前线程是否有Looper,然后得到当前线程的MessageQueue -
(死循环)不断调用MessageQueue的next方法取出MessageQueue中的Message,注意,当MessageQueue中没有消息时,next方法会阻塞,导致当前线程挂起 -
拿到Message以后,会调用它的target的dispatchMessage方法,这个target其实就是发送消息时用到的Handler。并调用其相关的回调方法(拿到Message之后,调用相关的回调方法)
sendMessage的全部过程,其实就是把Message加入到MessageQueue的合适位置
然后,会判断when,它是表示延迟的时间,我们这里没有延时,所以为0,满足if条件。把消息插入到消息队列的头部。如果when不为0,则需要把消息加入到消息队列的合适位置。
- Handler.sendMessage: 把消息加入到主线程的MessageQueue中,主线程中的Looper从MessageQueue中取出消息,调用Message.target.handleMessage方法
- Handler.post: 基于Handler.sendMessage,把消息加入到主线程的MessageQueue中,主线程中的Looper从MessageQueue中取出消息,调用Message.callback.run方法
- Activity.runOnUiThread: 基于Handler.post
- View.post: 基于Handler.post
所以,以上子线程更新主线程UI的所有方式,都是依赖于Handler机制。
消息的延时处理
https://www.jianshu.com/p/edf4f5ee0057/
MessageQueue是按照Message触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。排在越前面的越早触发,那我们现在应该了解到了,这个所谓的延时呢,不是延时发送消息,而是延时去处理消息,我们在发消息都是马上插入到消息队列当中。
可以看到,在这个方法内,如果头部的这个Message是有延迟而且延迟时间没到的(now < msg.when),会计算一下时间(保存为变量nextPollTimeoutMillis),然后在循环开始的时候判断如果这个Message有延迟,就调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞。nativePollOnce()的作用类似与object.wait(),只不过是使用了Native的方法对这个线程精确时间的唤醒。
1、postDelay()一个10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞; 2、紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()方法唤醒线程; 3、MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper; 4、Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;直到阻塞时间到或者下一次有Message进队;
(1) Handler引起内存泄漏
在Java中,非静态内部类和匿名类都会持有当前类的外部引用。
而在这里Handler是非静态内部类,所以次MHandler持有当前Activity的隐式引用,如果Handler没有被释放,其持有的外部引用也就是Activity也不可能被释放。
发送的消息中包含有 Handler 实例的引用,只有消息被处理后,handler 的引用才会释放,如果 Activity 已经销毁,但是Looper 中还有消息没有处理完,handler 就不能释放。如果handler 不是 Activity 的静态内部类,那么 handler 就会持有 Activity 的引用,导致该 Activity 不能正常回收,这样就造成了内存泄露。
(2)Handler内存泄漏解决方法
- 使用静态内部类并继承Handler(或者也可以单独存放成一个类文件)
- 因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。当你需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理
- 使用弱引用解决静态内部类访问外部类
- 创建一个静态的Handler内部类,然后对Handler持有的外部对象使用弱引用, 这样在回收时JVM可以销毁Handler持有的对象。但就算如此,在退出MainActivity后,Looper线程的消息队列中还是可能会有待处理的消息,因此建议在Activity销毁时,移除消息队列中的消息。
Android的Handler机制(1)
|