Android学习笔记:Handler

一、Handler简介

Handler是Android消息机制的上层接口,这使得在开发过程中只需要和Handler交互即可。Handler的使用过程很简单,通过它可以轻松的将一个任务转换到Handler所在的线程中去执行。Handler的作用是将一个任务切换到某个指定的线程中去执行。Android提供这个功能的原因是Android规定访问UI只能在主线程中进行,如果在子线程中访问UI,那么程序就会抛出异常。因此,系统之所以提供Handler,主要原因就是为了解决在子线程中无法访问UI的矛盾。但从本质上来说,Handler并不是专门用于更新UI的,它只是常被开发者用来更新UI。

Handler主要用于异步消息的处理:当发出一个消息后,首先进入一个消息队列,发送消息的函数即刻返回,而另外一个部分在消息队列中逐一将消息取出,然后对消息进行处理,也就是 发送消息和接收消息不是同步的处理。这种机制通常用来处理相对耗时比较长的操作。

总的来说,(1)Handler是Android SDK中用来处理异步消息的核心类;(2)子线程可以通过Handler来通知主线程进行UI的更新;


二、Handler运行机制的核心类

Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。

异步消息处理流程
(1) 首先需要在主线程当中创建一个Handler对象,并重写handleMessage()方法。
(2) 然后,当子线程中需要进行UI操作时,就会创建一个Message对象,并通过Handler.sendMessage()方法将这条消息发送出去。
(3) 之后,这条消息会被添加到MessageQueue的队列中等待被处理,而Looper则会一直尝试从MessageQueue中取出待处理消息。
(4) 最后,Looper回调dispatchMessage()方法,将取出的待处理消息分发回Handler的handleMessage()方法中。
由于Handler是在主线程创建的,所以此时handleMessage()方法中的代码也会在主线程中运行,于是可以进行UI操作
一条Message经过这样一个流程后,也就是从子线程进入到了主线程,从不能更新UI变成了可以更新UI,这就是异步消息处理的核心思想。
( Handler消息处理流程:首先需要在UI线程中创建一个Handler对象,然后在子线程中调用Handler的sendMessage()方法,接着这个消息会存放在UI线程的MessageQueue中,通过Looper对象取出MessageQueue中的消息,最后分发回Handler的handleMessage()方法中。
异步消息处理流程图
(图片来源于《Android第一行代码》P346)

如果想在自己的线程中创建Handler,必须调用Looper的prepare()与loop()两个方法。通过Looper.prepare()即可为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环。

1、Handler——主要用于发送和处理消息

工作原理

Handler创建时会采用当前线程的Looper来构建内部的消息循环系统,如果当前线程没有Looper,那么就会报错。(注意:线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。而主线程,也叫UI线程,在被创建的时候就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。) Handler创建完毕后,内部的Looper以及MessageQueue就可以和Handler一起协同工作了。 Handler的工作主要包含消息的发送接收过程,然后通过Handler的post方法将一个Runnable投递到Handler内部的Looper中去处理,也可以通过Handler的send方法发送一个消息,这个消息同样会在Looper中去处理。(其实post方法最终也是通过send方法来完成的)

以send方法工作过程为例, 当Handler的send方法被调用时,它会调用MessageQueue的enqueueMessage方法将这个消息放入消息队列中,然后MessageQueue的next方法会返回这条消息给Looper,Looper发现有新消息到来时,就会处理这个消息,最终消息由Looper交给Handler来处理,即Handler的dispatchMessage方法会被调用,这时Handler就进入到了消息处理阶段,Handler的handleMessage方法就会被调用。因为Looper是运行在创建Handler所在的线程中的,这样一来Handler中的业务逻辑就被切换到创建Handler所在的线程中去执行了。

2、Message——主要用于在不同线程之间交换数据

Message是在线程之间传递的消息,它可以在内部携带少量的信息, 用于在不同线程之间交换数据。在整个消息处理机制中,message又叫task,封装了任务携带的信息和处理该任务的handler。有以下几点需要注意:

(1)尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。

(2)message如果只需要携带简单的int信息,请优先使用arg1和arg2来传递信息,这比Bundle更省内存。

(3)使用obj字段可以携带一个Object对象。

3、Looper(轮询器)——每个线程中MessageQueue的管家

工作原理

由于MessageQueue只是一个消息的存储单元,它不能去处理消息,而Looper则填补了这个功能,它会 以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待着。Handler的工作需要Looper,没有Looper的线程就会报错。可以 通过Looper.prepare()即可为当前线程创建一个Looper,接着通过Looper.loop()来开启消息循环。Looper提供了quit和quitSafely来退出一个Looper,二者的区别是:quit会直接推出Looper,而quitSafely只是设定了一个退出标记,然后把消息队列中已有的消息处理完毕后才安全的退出。Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send方法会返回false。

Looper中还有一个特殊的概念就是ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。ThreadLocal可以在不同的线程中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程地Looper。因此,Handler内部可以通过ThreadLocal来获取到当前线程地Looper。

每个线程中只有一个Looper对象。

4、MessageQueue(消息队列)——主要用于存放所有通过Handler发送的消息

工作原理

MessageQueue即消息队列,它 主要用于存放所有通过Handler发送的消息,它的内部存储了一组消息,以队列的形式对外提供插入和删除的工作。虽然叫消息队列,它的内部并不是真正的队列,而是采用单链表的数据结构来存储消息列表(单链表在插入和删除上比较有优势)。 MessageQueue主要包含两个操作:插入和读取。读取操作本身伴随着删除操作,插入和读取对应的方法分别为enqueueMessage和next,其中enqueueMessage的作用是往消息队列中插入一条消息,而next的作用是从消息队列中取出一条消息并将其从消息队列中移除。具体来说,next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里。当新消息到来时,next方法会返回这条消息并将其从单链表移除。

每个线程只有一个MessageQueue对象。

5、ThreadLocal

工作原理

ThreadLocal是一个线程内部地数据存储类,通过它可以在指定地线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。

ThreadLocal可以在不同的线程中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程地Looper。因此,Handler内部可以通过ThreadLocal来获取到当前线程地Looper。
(例如,在不同线程中访问同一个ThreadLocal的set和get方法,它们对ThreadLocal所做的读/写操作仅限于各自线程的内部,这就是为什么ThreadLocal可以在多个线程中互不干扰地存储和修改数据。)

使用场景

①一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,可以考虑采用ThreadLocal。以Handler为例,它需要获取当前线程的Looper,由于Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal能够可以轻松的实现Looper在线程中的存取。

②ThreadLocal可以在复杂逻辑下进行对象的传递。

6、ThreadLocal,Looper,MessageQueue,Message,Handler的之间关系

主线程无法进行时间比较繁长的任务,所以需要子线程进行处理,然而子线程无法进行UI的界面更新,所以我们需要使用handler来传递消息给主线程,让其完成UI的更新。由于主线程和子线程进行不同时间的工作,所以需要用MessageQueue来存放子线程的消息,Looper取出消息交给主线程响应。

(1)一个Thread至多有一个Looper,需要通过Looper.prepare()初始化和Looper.loop()处理分发给对应的Handler对象处理 。

(2)一个Looper有且有一个MessageQueue,通过Looper.loop()取出MessageQueue的Message进行处理,但是可以服务于多个Handler对象。

(3)MessageQueue是由Message对象组织成链表结构的消息队列,而每个Message对象都唯一对应一个Handler对象。


三、Handler导致的内存泄露问题

问题描述

Handler使用过程中,我们需要特别注意一个问题,那就是Handler可能会导致内存泄漏。
具体原因如下:

(1)Handler的生命周期与Activity不同,Handler会关联Looper来管理MessageQueue。这个队列在整个Application的生命周期中存在,因此Handler不会因Activity的finish()方法而被销毁。

(2)非静态(匿名)内部类会持有外部对象,当我们这样重写Handler时它就成为了一个匿名内部类,这样如果调用finish方法时Handler有Message未处理的话,就会导致Activity不能被销毁。
(例如:外部类Activity中定义了一个非静态内部类Handler,非静态内部类默认持有对外部类的引用。如果Activity突然关闭了,但是MessageQueue中的消息还没有处理完,那么Handler就会一直持有对外部Activity的引用,垃圾回收器无法回收Activity,从而导致内存泄漏。)

解决方法

将非静态内部类Handler和Runnable转为静态内部类,因为非静态内部类(匿名内部类)都会默认持有对外部类的强引用。改成静态内部类后,同时对外部类的引用设为弱引用,因为当一个对象只被弱引用依赖时它便可以被GC回收。

注意:要static和弱引用要同时使用,否则由于非静态内部类隐式持有了外部类Activity的引用,而导致Activity无法被释放)


四、Handler的主要用途

1、在非UI线程中完成耗时操作,在UI线程中去更新UI

2、在主线程中发送延时消息(推送未来某个时间点将要执行的Message或者Runnable到消息队列)

①传递Message。用于接受子线程发送的数据,并用此数据配合主线程更新UI。

在Android中,对于UI的操作通常需要放在主线程中进行操作。如果在子线程中有关于UI的操作,那么就需要把数据消息作为一个Message对象发送到消息队列中,然后,由Handler中的handlerMessage方法处理传过来的数据信息,并操作UI。当然,Handler对象是在主线程中初始化的,因为它需要绑定在主线程的消息队列中。

②传递Runnable对象。用于通过Handler绑定的消息队列,安排不同操作的执行顺序。

Handler对象在进行初始化的时候,会默认的自动绑定消息队列。利用类post方法,可以将Runnable对象发送到消息队列中,按照队列的机制按顺序执行不同的Runnable对象中的run方法。

实际上,无论Handler将Runnable还是Message加入MessageQueue,最终都只是将Message加入到MessageQueue。Handler的post Runnable对象这个方法只是对post Message进行了一层封装,所以最终都是通过Handler推送了一个Message罢了。我们来分析一下源码:

Post(Runnable)方法和SendMessage(Message)方法的源码:

1public final boolean post(Runnable r){ 2 return sendMessageDelayed(getPostMessager(r), 0); 3} 4 5
1public final boolean sendMessage(Message msg){ 2 return sendMessageDelayed(msg, 0); 3} 4 5

由源码可见,两个方法都是通过调用sendMessageDelayed方法实现的,因此,它们的底层逻辑是一致的。但是post方法底层调用sendMessageDelayed的时候,却是通过getPostMessager(r)来将Runnable对象来转为Message,从getPostMessage(r)代码可以看到:

1private static Message getPostMessage(Runnable r){ 2 Message m = Message.obtain(); 3 m.callback = r; 4 return m; 5} 6 7

最终runnable也是转化为一个Message,而这个Message只有一个被赋值的成员变量,就是Runnable的回调函数,也就是说,这个Message在进入MessageQueue之后,它只是一个“动作”,即我们Runnable的run方法里面的操作。

小结

①当我们需要传输很多数据时,我们可以使用sendMessage来实现,因为通过给Message的不同成员变量赋值可以封装成数据非常丰富的对象,从而进行传输;
②当我们只需要进行一个动作时,直接使用Runnable,在run方法中实现动作内容即可。


代码交流 2021