Handler分析及面试题总结个人笔记

handler 消息机制

包含framework/base/core/java/andorid/os/

  • Handler.java
  • Looper.java
  • Message.java
  • MessageQueue.java

消息机制主要包含

Message:消息->分为硬件产生的消息和软件产生的消息

MessageQueue : 消息队列 主要功能是向消息池投递消息(MessageQueue.enqueueMessage)和从消息池中取走消息MessageQueue.next

handler :消息辅助类主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage)

Looper: 不断循环执行按分发机制将消息分发给目标处理者

当Message的回调方法不为空时,则回调方法msg.callback.run(),其中callBack数据类型为Runnable,否则进入步骤2;
当Handler的mCallback成员变量不为空时,则回调方法mCallback.handleMessage(msg),否则进入步骤3;
调用Handler自身的回调方法handleMessage(),该方法默认为空,Handler子类通过覆写该方法来完成具体的逻辑。
对于很多情况下,消息分发后的处理方法是第3种情况,即Handler.handleMessage(),一般地往往通过覆写该方法从而实现自己的业务逻辑

MessageQueue.enqueueMessage()

Handler.sendEmptyMessage()等系列方法最终调用MessageQueue.enqueueMessage(msg, uptimeMillis),
将消息添加到消息队列中,其中uptimeMillis为系统当前的运行时间,不包括休眠时间。

获取消息 是调用 Handler.obtainMessage()方法,最终是调用 Messsage.obtain(this)方法
removeMessages() 调用的也是 MessageQueue.removeMessages(this,…) 的方法

MessageQueue是消息机制的Java层和C++层的连接纽带,大部分核心方法都交给native层来处理,
其中MessageQueue类中涉及的native方法如下:
nativeInit
nativeDestroy
nativePollOnce
nativeWake
nativeIsPolling
nativeSetFileDescriptorEvents

创建过程

​ 1.Looper的prepare或者prepareMainLooper静态方法被调用,将一个Looper对象保存在ThreadLocal里面。
​ 2.Looper对象的初始化方法里,首先会新建一个MessageQueue对象。
​ 3.MessageQueue对象的初始化方法通过JNI初始化C++层的NativeMessageQueue对象。
​ 4.NativeMessageQueue对象在创建过程中,会初始化一个C++层的Looper对象。
​ 5.C++层的Looper对象在创建的过程中,会在内部创建一个管道(pipe),并将这个管道的读写fd都保存在mWakeReadPipeFd和mWakeWritePipeFd中。
​ 然后新建一个epoll实例,并将两个fd注册进去。
​ 6.利用epoll的机制,可以做到当管道没有消息时,线程睡眠在读端的fd上,当其他线程往管道写数据时,本线程便会被唤醒以进行消息处理

消息循环

​ 1.首先通过调用Looper的loop方法开始消息监听。loop方法里会调用MessageQueue的next方法。next方法会堵塞线程直到有消息到来为止。
​ 2.next方法通过调用nativePollOnce方法来监听事件。next方法内部逻辑如下所示(简化):
​ 进入死循环,以参数timout=0调用nativePollOnce方法。
​ 如果消息队列中有消息,nativePollOnce方法会将消息保存在mMessage成员中。nativePollOnce方法返回后立刻检查mMessage成员是否为空。
​ 如果mMessage不为空,那么检查它指定的运行时间。如果比当前时间要前,那么马上返回这个mMessage,否则设置timeout为两者之差,进入下一次循环。
​ 如果mMessage为空,那么设置timeout为-1,即下次循环nativePollOnce永久堵塞。
​ 3.nativePollOnce方法内部利用epoll机制在之前建立的管道上等待数据写入。接收到数据后马上读取并返回结果。

消息发送

1.Handler对象在创建时会保存当前线程的looper和MessageQueue,如果传入Callback的话也会保存起来。
2.用户调用handler对象的sendMessage方法,传入msg对象。handler通过调用MessageQueue的enqueueMessage方法将消息压入MessageQueue。
3.enqueueMessage方法会将传入的消息对象根据触发时间(when)插入到message queue中。然后判断是否要唤醒等待中的队列。
如果插在队列中间。说明该消息不需要马上处理,不需要由这个消息来唤醒队列。
如果插在队列头部(或者when=0),则表明要马上处理这个消息。如果当前队列正在堵塞,则需要唤醒它进行处理。
4.如果需要唤醒队列,则通过nativeWake方法,往前面提到的管道中写入一个"W"字符,令nativePollOnce方法返回。

消息处理

​ Looper对象的loop方法里面的queue.next方法如果返回了message,那么handler的dispatchMessage会被调用。

如果新建Handler的时候传入了callback实例,那么callback的handleMessage方法会被调用。
如果是通过post方法向handler传入runnable对象的,那么runnable对象的run方法会被调用。
其他情况下,handler方法的handleMessage会被调用。

关于Handler 消息机制的问题

​ handler 的作用 是 切换线程
​ 1.handler 是怎么获取到当前looper的?
​ looper 和Thread 的是对应的 一个looper 一个Thread ,当looper调用 prepare 方法的时候会将 looper储存在ThreadLocal中
​ Threadlocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据
​ 通过调用 myLooper的方法从ThreadLocal中 调用 get 方法获取
​ 2.当MessageQueue 没有消息的时候,在干什么
​ 当MessageQueue.next() 从队列中取消息实际上是在 nativePollOnce() 这个native 方法中实现的,当没有消息的时候 ,该方法会阻塞线程

  1. 一个线程中最多有多少个Handler,Looper,MessageQueue?

    可以有多个Handler 因为handler 是自己new出来的

    looper 和messageQueue 只能有一个 因为创建的looper 是保存在ThreadLocal 中的

    ThreadLocal 提供了针对于单独的线程的局部变量,并能够使用 set()、get() 方法对这些变量进行设置和获取,并且能够保证这些变量与其他线程相隔离。

    MessageQueue 是在looper 的构造中创建的所以也只有一个

  2. ​ Looper死循环为什么不会导致应用卡死,会耗费大量资源吗?

    因为是处于睡眠状态

    looper .loop() 方法是一个死循环 不断的从消息队列中取数据queue.next(),实际上是通过 nativePollOnce() native

    方法 调用C++的方法来实现的, 当没有数据的时候,会调用 epoll_wait()阻塞方法.此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

    Android是基于消息处理机制的,用户的行为都在这个Looper循环中,我们在休眠时点击屏幕,便唤醒主线程继续进行工作

  3. ​ 子线程的如何更新UI,比如Dialog,Toast等?系统为什么不建议子线程中更新UI?

    线程中更新UI的重点是创建它的ViewRootImpl和checkThread所在的线程是否一致

  4. ​ 主线程如何访问网络?

    将严苛模式网络检测关掉 网络请求之前

    StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitNetwork().build();
    StrictMode.setThreadPolicy(policy);
    
  5. ​ 如何处理Handler使用不当造成的内存泄漏?

    有延时消息,在界面关闭后及时移除Message/Runnable,调用handler.removeCallbacksAndMessages(null)

    内部类导致的内存泄漏改为静态内部类,并对上下文或者Activity/Fragment使用弱引用。

  6. ​ Handler的消息优先级,有什么应用场景?

    通过dispatchMessage 来分发消息,里面有两个if判断

    1. 判断message.callback() 是否为空,不为空则执行这个回调,

    2. 如果handler 的CallBack不为空,则执行这个回调

    3. 是handler的handlerMessage的方法

  7. ​ 主线程的Looper何时退出?能否手动退出?

    不能手动退出,主线程的looper 是在app退出的时候退出的

    App退出时 ActivityThread中的mH(Handler)收到消息后退出,不可手动退出

    因为主线程不允许退出,一旦退出就意味着程序挂了,退出也不应该用这种方式退出。

  8. ​ 如何判断当前线程是安卓主线程?

    Looper.getMainLooper()
    Looper.getMainLooper().getThread()
    

    使用Looper 判断

  9. ​ 正确创建Message实例的方式?

    1. 通过 Message 的静态方法 Message.obtain() 获取;
    2. 通过 Handler 的公有方法 handler.obtainMessage()

    所有的消息会被回收,放入sPool中,使用享元设计模式。** 享元模式主要用于减少对象创建的数量,以减少内存占用和提高性能**

  10. handler 的同步屏障机制

Handler中加入了同步屏障这种机制,来实现[异步消息优先]执行的功能。

当messgeQueue.next() 去消息的时候 会判断对头是不是同步屏障消息,如果是的话遍历队列里面所有的异步消息,取出来执行操作,否则就会阻塞,主线程就处于空闲状态,在之道同步屏障 消息配移除,才开始执行同步任务

  1. 如果在子线程中使用handler需要做什么?

    looper.prepare() looper.loop() looper.quit()

  2. Handler 中使用了什么设计模式

    Message 使用了享元模式 ----减少对象的创建,对象可以反复使用

    MessageQueue 生产者消费者

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值