一.Handler消息机制原理分析

本文详细介绍了Android中Handler、Looper和MessageQueue在线程间通信的角色和工作流程,分析了线程间通信的原理以及可能导致内存泄漏的情况。同时,讨论了Handler内存泄漏的原因和解决方案,包括使用静态Handler和在Activity销毁时清理消息。此外,文章还涵盖了在子线程中创建Handler的方法、HandlerThread的使用,以及IdleHandler的概念。最后,提到了消息的同步和异步特性,以及屏障消息的特殊性。

Handler面试题

  1. 一个线程有几个Handler?
    一个线程比如主线程,我们可以在主线程中创建多个Handler;所以一个线程中是可以包含任意多个Handler的.

  2. 线程间通信的原理是怎么样的?
    通常情况下我们会在子线程中,通过调用handler.sendMessage()方法发送消息;然后在主线程中Handler的回调方法handleMessage()中去处理消息;从而实现子线程与主线程通信.
    分析线程间通信的流程:

    1. 子线程:通过Handler发送消息Message到MessageQueue消息队列中
    • handler.sendMessage()
    • ->sendMessageDelayed
    • ->sendMessageAtTime()
    • ->enqueueMessage
    • ->(MessageQueue)queue.enqueueMessage
      • 将发送的消息添加到消息队列MessageQueue中,这里会去遍历之前的消息Message,并把心来的消息插进去.消息的实现方式是单链表,每个消息对象中会包含一个next变量存储下一个消息,消息之间是按照执行时间排序的,最早执行的消息排在最前面.
    1. 主线程:通过Looper从MessageQueue中取出消息并处理
    • ActivityThread.main()
      • 这个main()方法中会调用Looper.prepareMainLooper()方法,创建sMainLooper对象;然后调用调用Looper.loop()方法开启死循环,从消息队列中取消息分发给Hanlder处理.
    • Looper.loop()
      • 通过(Looper)me.mQueue获取到消息队列
    • ->(MessageQueue)queue.next()
      • 通过一个死循环不停地从消息队列中取消息
    • ->msg.target.dispatchMessage(msg)
      • 注意:msg.target是Message中的target变量,是Handler对象的变量,所以这里其实是调用的Handler中的dispatchMessage()方法
    • ->(Handler)dispatchMessage()
      • 调用Hanler中的分发消息方法dispatchMessage()
    • ->handleMessage(msg)
      • 最终回调我们Activity/Fragment中创建的Handler中的handleMessage方法,我们在这里处理消息.

    总结: (线程间通信的原理就是内存共享)
    由于线程间内存共享的机制,使得共享的Message消息对象可以通过子线程添加到MessageQueue消息队列中,接着主线程可以取出共享的MessageQueue队列中的Message对象进行处理.
    举例:
    Activity中创建的成员变量,我们在主线程和子线程中都可以去访问和修改这个成员变量,这就是线程间内存共享的案例.

  3. Handler内存泄漏的原因?为什么其他内部类没有这个问题?

    • 解决方案:
      • 首先我们可以通过将Handler声明称静态的static,因为静态内部类不会持有外部类的引用,从而页面可以正常销毁.
      • 在页面销毁的时候,调用removeCallbacksAndMessages(null)方法,这个方法会回收Hanlder持有的消息队列MessageQueue中的所有消息Message;该方法会遍历消息单链表,依次调用每一个消息的Message.recycleUnchecked()方法进行消息回收.从而避免延迟消息也页面销毁时未执行导致内存泄漏.
    • 问题分析:
      • MainActivity
        • 创建内部类Handler,并重写handleMessage()方法
          Handler mHandler = new Handler() {
                     
                     
              @Override
              public void handleMessage(@NonNull Message msg) {
                     
                     
                  super.handleMessage(msg);
              }
          };  
          
      • <-Handler
        • 非静态内部类Handler会持有外部类MainActivity的引用
      • <-Message
        • 发送的消息Message会持有Handler类.在发送消的过程中,Handler中的enqueueMessage()方法里会有如下一段代码:
          msg.target = this;
          Message就会持有this,这个this就是Handler类.
      • <-MessageQueue
        • 消息会添加到消息队列MessageQueue中,将消息添加到消息队列中的mMessages单链表中
      • <-Looper
        • Looper会调用prepareMainLooper()方法中的prepare()方法,将创建的Looper对象设置到ThreadLocal中去,代码如下:
          sThreadLocal.set(new Looper(quitAllowed));
          同时会执行如下代码,将ThreadLocal中存储的Looper对象赋值给一个静态变量sMainLooper,该变量的声明如下:
          private static Looper sMainLooper;
          这里sMainLooper是一个静态变量,他是GC Root对象,会持有所在类Looper,从而导致了内存泄漏.
          在使用Looper的loop()方法时,会调用MessageQueue的next()方法,从消息队列中取出消息进行进行处理,从而因为(Looper)sMainLooper是一个静态变量,是GC Root对象,无法被JVM回收,存在导致内存泄漏的风险.
        • Looper调用loop()方法,通过一个死循环从消息队列中取出消息分发给Hanlder去处理时,当消息被处理掉则不会导致内存泄漏.

    总结:
    由于页面在销毁的过程中,Looper类中的sMainLooper是静态变量,而静态变量是GC Root对象,无法被JVM回收,所以导致了Looper无法被回收,从而导致向上引用的MessageQueueMessageHandler和MainActivity都无法被回收,从而导致了内存泄漏.
    当我们将MainActivity中的Handler声明成static,内部类Handler就不会持有外部类MainActivity的应用,从而MainActivity销毁时可以被JVM正常回收;
    当我们在MainActivity销毁的时候调用removeCallbacksAndMessages(null)方法,可以回收所有的消息Message对象,从而Message就不会持有Handler的引用,从而MessageHandlerActivity都可以正常被回收.

  4. 为什么主线程可以new Handler?如果要在子线程中new Handler要做什么准备?

    • 主线程之所以能创建Handler:
      是因为Activity在启动的时候,会执行ActivityThread中的main()方法,这个方法中会执行这两行代码Looper.prepareMainLooper()和Loop.loop(),他们的作用分别是创建Looper对象,这个
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值