Handler机制

概要

Handler机制,名字看上去感觉很高大上的样子,实际上本篇主要是简要分析一下Android SDK源码,查看一下Handler内部是如何来处理消息的,以及它与Looper类的关系。

我们知道Android提供了Handler和Looper来满足线程间的通信,Handler的主要作用就是负责跟子线程进行通讯,从而让子线程与主线程(UI线程)之间建立起协作的桥梁,因为在Android中主线程是非线程安全的,所以我们在更新UI的时候只能在主线程中更新,其他线程无法对主线程进行操作,采用Handler机制可以说完美的解决了UI更新的问题。

Looper类封装了一个消息循环,内部有一个消息队列,Handler类更像是一个工具类,它封装了消息处理和投递等接口,所以更多情况下我们只需要使用Handler就可以达到我们的需求。但是既然要知其所以然,“暗箱操作” (Looper类)更是我们关注的重点,先来看看它是如何做的。

Looper类分析

我们以Looper使用的一个常见例子来分析Looper类。

现在这里提前说明,上面的①②③三个步骤不可以颠倒,分析过源码我们就知道为什么了。

prepare函数

ThreadLocal是Java中的线程局部变量类,全名应该是Thread Local Variable。我觉得,它的实现和操作系统提供的线程本地存储(TLS)有关系。总之,该类有两个关键函数:

  • set:设置调用线程的局部变量。
  • get:获取调用线程的局部变量。

注意,set/get的结果都和调用这个函数的线程有关。ThreadLocal类可参考JDK API文档或Android API文档。

根据上面的分析可知,prepare会在调用线程的局部变量中设置一个Looper对象。这个调用线程就是LooperThread的run线程。先看看Looper对象的构造,其代码如下所示:

prepare函数很简单,它主要干了一件事:

在调用prepare的线程中,设置了一个Looper对象,这个Looper对象就保存在这个调用线程的TLV中。而Looper对象内部封装了一个消息队列。

也就是说,prepare函数通过ThreadLocal机制,巧妙地把Looper和调用线程关联在一起了。要了解这样做的目的是什么,需要再看第二个重要函数。

loop循环

通过上面的分析会发现,Looper的作用是:

  • Looper封装了一个消息队列。
  • Looper的prepare函数把这个Looper和调用prepare的线程(也就是最终的处理线程)绑定在一起了。
  • 处理线程调用loop函数,处理来自该消息队列的消息。

当事件源向这个Looper发送消息的时候,其实是把消息加到这个Looper的消息队列里了。那么,该消息就将由和Looper绑定的处理线程来处理。那么,事件源又是怎么向Looper消息队列添加消息的呢?

Looper、Message和Handler的关系

  • Looper、Message和Handler之间也存在 Looper中有一个Message队列,里边存储的是一个个待处理的Message。
  • Message中有一个Handler,这个Handler是用来处理Message的。

其中,Handler类封装了很多琐碎的工作。先来认识一下这个Handler。

Handler分析

Handler类分析

Handler中所包括的成员:

这几个成员变量是怎么使用的呢?这首先得分析Handler的构造函数。Handler一共有四个构造函数,它们主要的区别,是在对上面三个重要成员变量的初始化上。我们试对其进行逐一分析。

在上述构造函数中,Handler中的消息队列变量最终都会指向了Looper的消息队列,Handler为何要如此做?

根据前面的分析可知,Handler中的消息队列实际就是某个Looper的消息队列,那么,Handler做如此安排的目的何在?

在回答这个问题之前,我先来问一个问题:怎么往Looper的消息队列插入消息?

如果不知道Handler,这里有一个很原始的方法:

  • 调用Looper的myQueue,它将返回消息队列对象MessageQueue。
  • 构造一个Message,填充它的成员,尤其是target变量。
  • 调用MessageQueue的enqueueMessage,将消息插入消息队列。

这种原始方法的确很麻烦,且极容易出错。但有了Handler后,我们的工作就变得异常简单了。Handler更像一个辅助类,帮助我们简化编程的工作。

Handler和Message

Handle提供了一系列函数,帮助我们完成创建消息和插入消息队列的工作。这里只列举其中一二。要掌握详细的API,则需要查看相关文档。

看到上面这些函数可以想见,如果没有Handler的辅助,当我们自己操作MessageQueue的enqueueMessage时,得花费多大功夫!

Handler把Message的target设为自己,是因为Handler除了封装消息添加等功能外还封装了消息处理的接口。

Handler的消息处理

刚才,我们往Looper的消息队列中加入了一个消息,按照Looper的处理规则,它在获取消息后,会调用target的dispatchMessage函数,再把这个消息派发给Handler处理。Handler在这块是如何处理消息的呢?

dispatchMessage定义了一套消息处理的优先级,它们分别是:

  • Message如果自带了callback处理,则交给callback处理。
  • Handler如果设置了全局的mCallback,则交给mCallback处理。
  • 如果上述都没有,该消息则会被交给Handler子类实现的handleMessage来处理。当然,这需要从Handler派生并重载handleMessage函数。

在通常情况下,我们一般都是采用第三种方法,即在子类中通过重载handleMessage来完成处理工作的。

至此,Handler知识基本上讲解完了,可是在实际编码过程中还有一个重要问题需要警惕。下一节内容就将谈及此问题。

Looper和Handler的同步关系

Looper和Handler会有什么同步关系呢?它们之间确实有同步关系,而且如果不注意此关系,定要铸成大错!

同步关系肯定和多线程有关,看下面的一个例子:

线程1中创建线程2,并且线程2通过Looper处理消息。

线程1中得到线程2的Looper,并且根据这个Looper创建一个Handler,这样发送给该Handler的消息将由线程2处理。

但很可惜,上面的代码是有问题的。如果我们熟悉多线程,就会发现标有“注意”的那行代码存在着严重问题。myLooper的创建是在线程2中,而looper的赋值则在线程1,很有可能此时线程2的run函数还没来得及给myLooper赋值,这样线程1中的looper将取到myLooper的初值,也就是looper等于null。另外

这是因为,myLooper返回的是调用线程的Looper,即Thread1的Looper,而不是我们想要的Thread2的Looper。

对这个问题,可以采用同步的方式进行处理。你是不是有点迫不及待地想完善这个例子了?其实Android早就替我们想好了,它提供了一个HandlerThread来解决这个问题。

HandlerThread介绍

HandlerThread完美地解决了myLooper可能为空的问题。来看看它是怎么做的。代码如下所示:

HandlerThread很简单,小小的wait/ notifyAll就解决了我们的难题。为了避免重复发明轮子,我们还是多用HandlerThread类吧!

我们知道Thread线程是一次性消费品,当Thread线程执行完一个耗时的任务之后,线程就会被自动销毁了。如果此时我又有一个耗时任务需要执行,我们不得不重新创建线程去执行该耗时任务。然而,这样就存在一个性能问题:多次创建和销毁线程是很耗系统资源的。为了解这种问题,我们可以自己构建一个循环线程Looper Thread,当有耗时任务投放到该循环线程中时,线程执行耗时任务,执行完之后循环线程处于等待状态,直到下一个新的耗时任务被投放进来。这样一来就避免了多次创建Thread线程导致的性能问题了。

小结

文章开头的一个简单demo代码,现在就可以很清楚的知道为什么不可以颠倒顺序,①会新建一个Looper对象,②Handler总的成员变量mLooper会使用上一步①中的Looper对象,通过Looper.myLooper()获取,③中会使用一个msg.target.dispatchMessage(msg),target正是在②中创建的Handler对象。上面三步每一步都会使用上一步中所创建的对象。

在平常使用中我们很少使用Handler和Looper对象组合使用,是因为多数时候我们都是在主线程更新UI时候使用的,而主线程已经封装了Looper消息循环,我们可以在ActivityThread中看到Looper的影子,由于Android也是基于Java语言开发,main函数仍然作为程序入口,下面是main函数代码块:

本文核心内容是基于《深入理解Android卷i》中第五章第四节Looper和Handler类分析的内容,核心源码示例也出自该章节,虽然本书是针对Android2.2源码出的一本内核解析的书籍,但是也仍然不妨碍我们对于内部原理性东西的理解。

主线程—子线程—Handler—Looper—MessageQueue—Message之间的关系。如下图所示,图片转自Handler和他的小伙伴们(上)

handler-looper-message

本文地址www.sunnyang.com/303.html

One comment

发表评论