美文网首页
Handler相关

Handler相关

作者: Infinity_空 | 来源:发表于2022-01-11 15:31 被阅读0次
  1. Handler的作用
    在Android中,为了保证UI操作是线程安全的,所以只允许在主线程中去更新UI,而在一些场景中,为了提高工作效率,会在工作线程中更新UI的相关信息,此时就需要一种机制将工作线程的信息传递到主线程去更新UI。存在一个消息队列 MessageQueue,它是一个基于消息触发时间的优先级队列,还有一个基于此消息队列的事件循环 Looper,Looper 通过循环,不断的从 MessageQueue 中取出待处理的 Message,再交由对应的事件处理器 Handler/callback 来处理。

  2. Handler的相关概念

    1. Message:传递消息的载体
    2. MessageQueue:先进先出的存储消息的队列
    3. Handler:1. 用于主线程和子线程之前传递消息(Handler.post, Handler.sendMessage) 2.实际上处理消息的地方(Handler.handlerMessage)
    4. Looper:MessageQueue和Handler之间的媒介,循环从MessageQueue中取出消息,然后发送给对应的Handler
  3. Looper和Handler创建和准备过程

    1. 在主线程创建时,会调用Looper.prepareMainLooper(),创建主线程的Looper和对应的MessageQueue(如果是在子线程,需要手动调用Looper.prepare(),创建对应的Looper和MessageQueue),并执行Looper.loop()开始消息循环
    2. Handler初始化时,会自动绑定Looper和MessageQueue
    3. 在Looper.loop的过程中,通过for循环,不断调用MessageQueue.next,获取消息队列中的消息
      3.1 如果到了消息执行的时间,则通过Handler.dispatchMessage分发给对应的Handler,通过回调Handler.handlerMessage进行消息的处理
      3.2 如果没有到消息执行的时间,则通过Message.nativePollOnce进入线程阻塞,等待执行的时间
    4. 在使用Message时,可以通过new Message()或者Message.obtain()获得Message对象。Message.obtain()会去尝试复用Message对象,而new Message()则是直接创建一个Message对象
    5. 通过Handler.sendMessage或者Handler.sendMessageDelayed将Message发送到MessageQueue中。如果线程处于阻塞状态,则唤醒线程。
      5.1 如果此时队列为空则将消息放到队列头部
      5.2 如果队列有消息,则根据执行时间或者创建时间,插入到队列中。
  4. sendMessagepost的区别
    sendMessage的参数是Message,然后直接调用sendMessageDelayed(msg, 0)
    post的参数是Runnable,通过getPostMessageRunnable赋值给Message.callback,然后返回一个Message对象。最后还是调用sendMessageDelayed(getPostMessage(r), 0)
    Handler.dispatchMessage中通过判断callback是否为null,决定执行handleCallback还是handleMessage
    所以,实际上sendMessagepost本质上没有区别,只是在使用方法上有区别。

  5. sendMessageDelayed的原理(参考)
    Message内部维护一个when,用于记录Message的创建时间,当一个新的Message插入MessageQueue中,会根据when进行排序。

    1. 当使用sendMessageDelayed发送的Message,会在当前的系统时间上加设置的delayMillis设置为该Message的when
    2. MessageQueue在通过next()获取消息时,会对比当前的时间now和Message的when,如果now > when则取出消息交给Looper,如果now < when,则通过nativePollOnce阻塞线程,直到执行时间到了,或者被新的Message唤醒

    注:所以通过Handler延时处理的任务,并不一定会准时执行,因为有可能会因为前一条消息执行时间太长,而出现延迟。

  6. Handler内部有一个死循环,但是为什么不会ANR
    因为ANR指的是在一定的时间内,系统没有响应输入事件,或者是在组件规定的时间内,没有执行对应的生命周期,而出现的异常。而Handler的死循环并不需要响应事件,所以不存在ANR。

  7. Looper会一直消耗系统资源吗?(参考
    Looper不会一直消耗系统资源,当Looper的MessageQueue中没有消息时,或者定时消息没到执行时间时,当前持有Looper的线程通过nativePollOnce进入阻塞状态。直到有新消息到,或者时间到了,才会被唤醒。

  8. Handler的内存泄漏
    因为在Java中,非静态内部类 & 匿名内部类都默认持有 外部类的引用,所以,如果在Activity中创建一个Handler,所以该Handler会持有Activity的实例。
    当Handler中有未处理完的Message时,关闭Activity,此时就会因为Handler持有Activity,而Activity无法被GC,导致内存泄漏
    解决方案:通过将Handler设置为静态内部类,并且通过弱引用创建Handler实例

  9. Handler如何在多线程环境下保证线程安全?(参考
    Handler.sendMessage最后调用到了MessageQueue.enqueueMessage,在这里通过synchronized(this)给自己加了锁,保证了在多线程环境下消息的插入是线程安全的。同时在MessageQueue.next中,也加了锁,保证取出消息是线程安全的。

  10. 为什么MessageQueue队列没有设置上限?
    为了避免队列满了,导致死锁。因为出队和入队的时候持有的是同一把锁,如果入队时先持有锁,但是队列满了,无法入队,导致无法释放锁,而出队又拿不到锁,进行处理。

  11. 同步消息屏障(参考

    • 一般情况下,通过Handler发送的Message放到MessageQueue中,这些消息都是同步消息,都会在MessageQueue中排队等候处理。
    • 而在Handler中,还有另外一种异步消息。这种异步消息的应用场景主要是为了避免在消息队列中,消息太多了处理不完,而使用的一种“加急”处理紧急消息的机制。
    • 而要使用异步消息,就需要使用到同步消息屏障。这是一种特殊的Message,当它被插入队列之后,当队列头遇到它之后,在MessageQueue.next()方法中,会通过while循环,取出队列中的异步消息,进行处理。
      // msg.target == null,就是同步消息屏障
      if (msg != null && msg.target == null) {
        // Stalled by a barrier.  Find the next asynchronous message in the queue.
        do {
          prevMsg = msg;
          msg = msg.next;
        } while (msg != null && !msg.isAsynchronous());
      }
      
    • 使用场景:在Android屏幕绘制的过程中,为了能及时的处理绘制的消息(TraversalRunnable ),就会使用同步屏障,将MessageQueue中的消息暂时阻塞,优先处理绘制相关的异步消息,当异步消息处理完成之后,还会移除同步屏障,接着处理MessageQueue中的同步消息。
    • 同步消息屏障的原理
      了解过同步消息屏障的人,都知道一句话
      如果没有及时移除同步屏障,他会一直存在且不会执行同步消息。因此使用完成之后必须即时移除。
      因为在MessageQueue.next()处理消息的时候,会执行:
      if (prevMsg != null) {
          prevMsg.next = msg.next;
      } else {
          mMessages = msg.next;
      }
      msg.next = null;
      return msg;
      
      从这里可以看到,如果prevMsg != null,就会将prevMsg.next指向msg.next,即将msg从链表中取出来。因为我们知道当有同步屏障存在的时候,prevMsg肯定不为null,所以此时mMessage不会改变,还依旧是同步消息屏障,所以等到下一个next()循环,又会重新从队列里面找到异步消息,如果没有找到就会进入线程阻塞状态。只有将同步消息屏障移除之后,才会恢复。代码如下:
        public void removeSyncBarrier(int token) {
            synchronized (this) {
                Message prev = null;
                Message p = mMessages;
                // 在链表中找到同步屏障
                while (p != null && (p.target != null || p.arg1 != token)) {
                    prev = p;
                    p = p.next;
                }
      
                final boolean needWake;
                // 如果同步屏障在链表中,就直接移除
                if (prev != null) {
                    prev.next = p.next;
                    needWake = false;
                } else {
                // 这里同步屏障是链表头部,则直接将mMessages赋值为链表下一个节点
                    mMessages = p.next;
                    needWake = mMessages == null || mMessages.target != null;
                }
            }
        }
      

参考阅读:https://blog.csdn.net/carson_ho/article/details/51290360

  1. IdleHandler 是什么?怎么用?(参考
    IdleHandler是在Looper事件循环的过程中,当出现空闲的时候,可以去执行一些不耗时的任务。
    IdleHandler.queueIdle()的返回值表示,如果返回true则表示该任务会一直重复,false表示该任务只执行一次。
    使用场景:GC操作

  2. 当 mIdleHandlers一直不为空时,为什么不会进入死循环?
    1. mIdleHandlers执行依赖的变量是pendingIdleHandlerCount,当pendingIdleHandlerCount < 0时,才会获取mIdleHandlers中的数量。
    1.1. 如果pendingIdleHandlerCount <= 0,就会continue重新循环,并且在此次的for循环中,pendingIdleHandlerCount不会再< 0,所以mIdleHandlers没有机会再执行,只能等待下一次'next()'的调用
    1.2. 如果pendingIdleHandlerCount > 0,则会执行for (int i = 0; i < pendingIdleHandlerCount; i++) {},意味着此次mIdleHanders中的任务也只会被遍历一次。之后pendingIdleHandlerCount = 0; nextPollTimeoutMillis = 0;都置为0了,会唤醒线程,判断Message的执行时间是否到了。就算此次Message执行时间还未到,那么参考1.1,mIdleHandlers的任务还是不会再次执行。

  3. ThreadLocal有什么作用?(参考
    ThreadLocal类可实现线程本地存储的功能,把共享数据的可见范围限制在同一个线程之内,无须同步就能保证线程之间不出现数据争用的问题,这里可理解为ThreadLocal帮助Handler找到本线程的Looper。
    每个线程的Thread对象中都有一个ThreadLocalMap对象,它存储了一组以ThreadLocal.threadLocalHashCode为key、以本地线程变量为value的键值对,而ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,也就包含了一个独一无二的threadLocalHashCode值,通过这个值就可以在线程键值值对中找回对应的本地线程变量。

  4. 如何判断当前线程是主线程:return Looper.getMainLooper() == Looper.myLooper();因为myLooper返回的是用ThreadLocal存储的Looper,ThreadLocal对每个线程都单独保留一份

  5. ThreadLocal:以当前线程对象(thread)为key,value为要保存的对象。这样即可保持线程之间的独立性。

相关文章

  • Handler相关

    Handler handler通过发送和处理Message和Runnable对像来关联相应线程的MessageQu...

  • Handler相关

    原理: Handler 、 Looper 、Message 这三个是Android异步消息处理线程相关的概念。异步...

  • Handler 相关

    当Android应用程序启动时,会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件进行事件分发...

  • Handler相关

    1.一个线程中可以有几个Handler2.一个线程中有几个Looper?如何保证?3.Handler内存泄露的原因...

  • android的消息机制

    handler学习思路 handler是是什么,做什么用,相关知识了解?handler主线程代码示例handler...

  • Handler的那些事

    Handler的那些事 相关的类:Handler、Looper、Message、MessageQueue、Asyn...

  • Handler探究

    Handler相关概念 什么Handler Handler允许您发送和处理与线程的MessageQueue关联的M...

  • 【干货】2021最新Android高级开发面试宝典1296页PD

    一、Handler相关知识 1、Handler Looper Message关系是什么?2、Messagequeu...

  • Android Handler 机制

    相关类 Handler Message Looper MessageQueue

  • Android面试集

    Handler相关 涉及到的主要的类:Handler、Looper、MessageQueue、Message Ha...

网友评论

      本文标题:Handler相关

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