编程知识 cdmana.com

Android的Handler机制(2)

Handler机制

img

Handler机制主要是用作线程间通信,尤其是主线程和子线程之间的通信。

Handler机制里面涉及到四个对象:Handler,message、MessageQueue、Loo per

  • Handler消息的处理者,负责将Message添加到消息队列以及对消息队列中的Message进行处理。
    • 主线程创建一个Handler对象,重写handleMessage()方法
  • 在子线程中创建一个Message对象,保存要传递的消息。通过Handler的sendMessage()方法发出消息
    1. Handler.sendMessage: 把消息加入到主线程的MessageQueue中,主线程中的Looper从MessageQueue中取出消息,调用Message.target.handleMessage方法
    2. 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通信之前需要有以下三步:

  1. 调用Looper.prepare()

    • 所以Looper.prepare()的作用主要有以下三点
      1. 创建Looper对象 & 存放在ThreadLocal变量中
      2. 创建MessageQueue对象,并让Looper对象持有(在Looper的构造方法里面,会创建消息队列MessageQueue,并让它供Looper持有)
      3. 让Looper对象持有当前线程
  2. 创建Handler对象

    • 在Handler的构造方法里面:得到当前线程调用sThreadLocal.set保存的Looper对象,让Handler持有它。接下来就会判断得到的Looper对象是否为空,如果为空,就会抛出异常(得到当前线程的Looper对象,并判断是否为空

    • 让创建的Handler对象持有Looper、MessageQueue、Callback的引用

    • 当创建Handler对象时,则通过 构造方法 自动关联当前线程的Looper对象 & 对应的消息队列对象(MessageQueue),从而 自动绑定了 实现创建Handler对象操作的线程

  3. 调用Looper.loop()

    • 从当前线程的MessageQueue从不断取出Message,并调用其相关的回调方法。
    1. 判断了当前线程是否有Looper,然后得到当前线程的MessageQueue

    2. (死循环)不断调用MessageQueue的next方法取出MessageQueue中的Message,注意,当MessageQueue中没有消息时,next方法会阻塞,导致当前线程挂起

    3. 拿到Message以后,会调用它的target的dispatchMessage方法,这个target其实就是发送消息时用到的Handler。并调用其相关的回调方法(拿到Message之后,调用相关的回调方法

示意图

image-20210511092838461

sendMessage的全部过程,其实就是把Message加入到MessageQueue的合适位置

然后,会判断when,它是表示延迟的时间,我们这里没有延时,所以为0,满足if条件。把消息插入到消息队列的头部。如果when不为0,则需要把消息加入到消息队列的合适位置。

  1. Handler.sendMessage: 把消息加入到主线程的MessageQueue中,主线程中的Looper从MessageQueue中取出消息,调用Message.target.handleMessage方法
  2. Handler.post: 基于Handler.sendMessage,把消息加入到主线程的MessageQueue中,主线程中的Looper从MessageQueue中取出消息,调用Message.callback.run方法
  3. Activity.runOnUiThread: 基于Handler.post
  4. 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)

版权声明
本文为[甜酒SweetWine]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_45424937/article/details/123641443

Scroll to Top