Handler

163 阅读5分钟

1 Handler存在的意义

  1. 跟web开发的ajax有异曲同工之妙
  2. 使得Android开发难度大大降低
  3. 几乎看不到多线程死锁问题 Handler是android线程通信框架,完全解决掉Android中线程通信、线程切换的问题。Handler设计符合迪米特法则(最少知道原则),使用简单。

线程通信实现技术方案:内存共享。

2 Hadler四要素

与Hadler相关的类:

  • Message(消息): 通过Message.obtain()来从消息池中获得空消息对象,以节省资源,避免内存抖动。

  • MessageQueue(消息队列):保存Message,属于Looper对象。

  • Looper(消息循环):

    一个Thread对应一个Looper对象。创建Handler之前需调用Looper.prepare(),执行Looper的构造方法,初始化消息队列,并且指定当前线程。负责消息的入列和出列。

  • Handler(消息发送和处理):

3 Handler源码分析

Handler工作流程可以理解成一个传送带工作的流程: handler工作流程.png

Looper.loop开启传送,Thread提供动力。

3.1 Handler

  1. 消息入队列

    handler.send——>Handler.enequeueMessage——>MessageQueue.enquueuMessage

  2. 消息出队列 Looper.loop()——>MessageQueue.next——>handler.dispatchMessage()——>handler.handleMessage

3.2 MessageQueue

Message通过new Message或obtain获取,其实是获取到一块内存,message在不同线程间传递,就实现了内存共享,线程切换。

message.png

数据结构:MessageQueue是由单链表实现的优先级队列。

  1. 单向链表

    Message都有一个next节点。

    Message->new(Message)->new(Message)...

  2. 优先级

    Message有时间属性

    MessageQueue.enquueuMessage入队列排序算法:插入排序 messageEnqueue.png

  3. 队列

    先进先出

3.3 Looper源码

一个线程只有一个looper

  • Looper.prepare:

    调用构造函数,生成MessageQueue,获取当前线程,实现looper与thread的绑定,并把 Looper对象set到ThreadLocal。

  • Looper.loop

    Looper.loop是一个死循环,从messageQueue中获取Message,并使用handler进行分发。

    loop有两种可能会阻塞:

    1) message 不到时间 ,自动唤醒

    2) messageQueue为空,无限等待

    退出应用 Looper.quite可结束looper.loop()循环

4 Handler面试题

  1. 一个线程有几个Handler?

    多个,可以在线程中new多个Handler。

  2. 一个线程有几个Looper?如何保证?

    一个线程只有个Looper。

    Looper的构造函数是私有的。Looper.prepare中,首先获取ThreadLocal中存储的looper,如果不为空,抛异常,为空new looper,set到ThreadLocal中。其中get和set使用的是同一个ThreadLocal。

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

    内部类默持有外部类的对象 message handler.enqueueMessage中,把handler赋值给message.target,故message持有handler,handler持有activity。关闭activity时,如果message还没有处理,message占用的内存没有释放,意味着activity对象还被持有,故activity内存不能释放。

  4. 为何主线程可以new Handler?如果想要在子线程中new Handler 要做些什么准备? 子线程中new Handler需要调用Looper.perpare和Looper.loop。主线程已在ActivityThread的main函数中调用了这连个方法。

  5. 子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?

    调用handler.getLooper().quite(); 消息队列无消息时,looper.loop()的循环会一直阻塞在MessageQueue.next的nativePollOnce处,线程资源无法释放,调用quite后,会通过native唤醒线程,MessageQueue.next返回null,looper.loop循环退出,线程结束,释放线程资源。

    主线程调用quite会抛异常。

  6. 既然可以存在多个 Handler 往 MessageQueue 中添加数据(发消息时各个 Handler 可能处于不同线程),那它内部是如何确保线程安全的?

    MessageQueue存、取消息都加了synchronized锁,synchronized锁住的对象是MessageQueue实例,确保一个线程同一时刻只有一个可以操作队列的地方。

    synchronized:JVM内置锁,可以用于代码块、方法

  7. 我们使用 Message 时应该如何创建它?

    调用Message.obtain,从消息池中获得空消息对象,避免因多次new Message,分配、释放内存造成内存抖动,带来的内存碎片问题(享元模式:内存复用)。

  8. Looper死循环为什么不会导致应用卡死? ANR:5s内无法响应用户输入事件或BroadcastReceiver在10s内无法结束。

    用户输入事件最终会被封装成Message,用handler处理,如果5s内没有被处理,会通过handler发送ANR提醒,弹框。

    子线程执行完代码后,声明周期结束,线程退出。主线程肯定不能运行一段时间后自动结束,主线程的loop循环保证了主线程代码可以一直执行。

    主线程息队列无消息时,looper.loop()的循环会一直阻塞在MessageQueue.next的nativePollOnce处,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。

    这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。

5 ThreadHandler

HandlerThread继承与Thread,run方法中创建Looper对象,并调用了Looper.prepare、looper.loop(),本质上是Thread+Looper。方便Handler在子线程中使用,初始化Looper和获取Looper时,都加了同步锁,保证了线程安全。

    HandlerThread handlerThread = new HandlerThread("");
    handlerThread.run();
    new Handler(handlerThread.getLooper());
    handlerThread.quit();

6 IntentService

Service不是运行在独立的线程,所以不建议在Service中编写耗时的逻辑和操作,否则会引起ANR。Service中的耗时操作,需要单独创建线程执行。

IntentService

IntentService是系统磅我们创建了一个线程,并提供了一个回调方法onHandlerIntent,在此方法中可进行耗时操作。

IntentService在onCreate方法中,创建了HandlerThread,获取HandlerThread的looper,创建Handler,handler是在子线程中执行的。

onStart方法中,通过Handler发送消息,handleMessage会调用onHandlerIntent,重写onHandlerIntent处理业务。

  1. 可用于执行后台耗时的任务,任务执行后会自动停止。在IntentService内部的Handler.handleMessage中,调用onHandlerIntent会调用stopSelf

  2. 具有高优先级,适合高优先级的后台任务,且不容易被系统杀死。内部创建子线程时,优先级为0。

  3. 可以多次启动,每个耗时操作都会以工作队列的方式在IntentService的onHandleIntent回调方法中执行。