天天看点

Android多线程----异步消息处理机制之Handler详解

【正文】

虽然是国庆佳节,但也不能停止学习的脚步,我选择在教研室为祖国母亲默默地庆生。

在android当中,提供了异步消息处理机制的两种方式来解决线程之间的通信问题,一种是今天要讲的handler的机制,还有一种就是之前讲过的 asynctask 机制。

一、handler的引入:

我们都知道,android ui是线程不安全的,如果在子线程中尝试进行ui操作,程序就有可能会崩溃。相信大家在日常的工作当中都会经常遇到这个问题,解决的方案应该也是早已烂熟于心,即创建一个message对象,然后借助handler发送出去,之后在handler的handlemessage()方法中获得刚才发送的message对象,然后在这里进行ui操作就不会再出现崩溃了。具体实现代码如下:

Android多线程----异步消息处理机制之Handler详解
Android多线程----异步消息处理机制之Handler详解

上方第45行代码也可以换成:

上面的代码中,我们并没有在子线程中直接进行ui操作,而是创建了一个message对象,并将它的what字段的值指定为了一个整形常量update_text,用于表示更新textview这个动作。然后调用handler的sendmessage()方法将这条message发送出去。很快,handler就会收到这条message,并在handlemessage()方法,在这里对具体的message进行处理(需要注意的是,此时handlemessage()方法中的代码是在主线程中运行的)。如果发现message的what字段的值等于update_text,就将textview显示的内容更新。运行程序后,点击按钮,textview就会显示出更新的内容。

 注:如果从源码的角度理解,粗略的描述是这样的:

先是调用了handler的obtainmessage()方法得到message对象。在obtainmessage()方法里做的事情是:调用了message.obtain(this)方法,把handler作为对象传进来。在message.obtain(this)方法里做的事情是:生成message对象,把handler作为参数赋值给message的target属性。总的来说,一个handler对应一个looper对象,一个looper对应一个messagequeue对象,使用handler生成message,所生成的message对象的target属性,就是该对象。而一个handler可以生成多个message,所以说,handler和message是一对多的关系。

二、异步消息处理机制:

handler是android类库提供的用于接受、传递和处理消息或runnable对象的处理类,它结合message、messagequeue和looper类以及当前线程实现了一个消息循环机制,用于实现任务的异步加载和处理。整个异步消息处理流程的示意图如下图所示:

Android多线程----异步消息处理机制之Handler详解

根据上面的图片,我们现在来解析一下异步消息处理机制:

message:消息体,用于装载需要发送的对象。

handler:它直接继承自object。作用是:在子线程中发送message或者runnable对象到messagequeue中;在ui线程中接收、处理从messagequeue分发出来的message或者runnable对象。发送消息一般使用handler的sendmessage()方法,而发出去的消息经过处理后最终会传递到handler的handlermessage()方法中。

messagequeue:用于存放message或runnable对象的消息队列。它由对应的looper对象创建,并由looper对象管理。每个线程中都只会有一个messagequeue对象。

looper:是每个线程中的messagequeue的管家,循环不断地管理messagequeue接收和分发message或runnable的工作。调用looper的loop()方法后,就会进入到一个无限循环中然后每当发现messagequeue中存在一条消息,就会将它取出,并调用handler的handlermessage()方法。每个线程中也只会有一个looper对象。

了解这些之后,我们在来看一下他们之间的联系:

首先要明白的是,handler和looper对象是属于线程内部的数据,不过也提供与外部线程的访问接口,handler就是公开给外部线程的接口,用于线程间的通信。looper是由系统支持的用于创建和管理messagequeue的依附于一个线程的循环处理对象,而handler是用于操作线程内部的消息队列的,所以handler也必须依附一个线程,而且只能是一个线程。

我们再来对异步消息处理的整个流程梳理一遍:

当应用程序开启时,系统会自动为ui线程创建一个messagequeue(消息队列)和looper循环处理对象。首先需要在主线程中创建一个handler对象,并重写handlermessage()方法。然后当子线程中需要进行ui操作时,就创建一个message对象,并通过handler将这条消息发送出去。之后这条消息就会被添加到messagequeue的队列中等待被处理,而looper则会一直尝试从messagequeue中取出待处理消息,并找到与消息对象对应的handler对象,然后调用handler的handlemessage()方法。由于handler是在主线程中创建的,所以此时handlemessage()方法中的代码也会在主线程中运行,于是我们在这里就可以安心地进行ui操作了。

通俗地来讲,一般我们在实际的开发过程中用的比较多一种情况的就是主线程的handler将子线程中处理过的耗时操作的结果封装成message(消息),并将该message(利用主线程里的messagequeue和looper)传递到主线程中,最后主线程再根据传递过来的结果进行相关的ui元素的更新,从而实现任务的异步加载和处理,并达到线程间的通信。

通过上一小节对handler的一个初步认识后,我们可以很容易总结出handler的主要用途,下面是android官网总结的关于handler类的两个主要用途:

(1)执行定时任务:

指定任务时间,在某个具体时间或某个时间段后执行特定的任务操作,例如使用handler提供的postdelayed(runnable r,long delaymillis)方法指定在多久后执行某项操作,比如当当、淘宝、京东和微信等手机客户端的开启界面功能,都是通过handler定时任务来完成的。

我们接下来讲一下post。 

(2)线程间的通信:

在执行较为耗时的操作时,handler负责将子线程中执行的操作的结果传递到ui线程,然后ui线程再根据传递过来的结果进行相关ui元素的更新。(上面已有说明)

三、post:

对于handler的post方式来说,它会传递一个runnable对象到消息队列中(这句话稍后会进行详细解释),在这个runnable对象中,重写run()方法。一般在这个run()方法中写入需要在ui线程上的操作。

post允许把一个runnable对象入队到消息队列中。它的方法有:post(runnable)、postattime(runnable,long)、postdelayed(runnable,long)。详细解释如下:

boolean post(runnable r):把一个runnable入队到消息队列中,ui线程从消息队列中取出这个对象后,立即执行。

boolean postattime(runnable r,long uptimemillis):把一个runnable入队到消息队列中,ui线程从消息队列中取出这个对象后,在特定的时间执行。

boolean postdelayed(runnable r,long delaymillis):把一个runnable入队到消息队列中,ui线程从消息队列中取出这个对象后,延迟delaymills秒执行

void removecallbacks(runnable r):从消息队列中移除一个runnable对象。

下面通过一个demo,讲解如何通过handler的post方式在新启动的线程中修改ui组件的属性:

Android多线程----异步消息处理机制之Handler详解
Android多线程----异步消息处理机制之Handler详解

点击按钮,运行结果如下:

Android多线程----异步消息处理机制之Handler详解
Android多线程----异步消息处理机制之Handler详解

有一点值得注意的是:对于post方式而言,它其中runnable对象的run()方法的代码(37行至39行或者58至61行),均运行在主线程上(虽然看上去是写在子线程当中的),如果我们在这段代码里打印日志输出线程的名字,会发现输出的是main thread的名字。所以对于这段代码而言,不能执行在ui线程上的操作,一样无法使用post方式执行,比如说访问网络。 

我们现在来解释一下上面蓝色字体的那句话:

这个runnable对象被放到了消息队列当中去了,然后主线程中的looper(因为handler是在主线程中生成的,所以looper也在主线程中)将这个runnable对象从消息队列中取出来,取出来之后,做了些什么呢?为什么在执行pos的runnable对象的run()方法时,不是重新开启一个线程呢?要了解这个过程,只能求助android的源代码:

打开源码的目录sdk\sources\android-19\android\os,并找到handler.java这个文件。找到post方法:

上方的代码中, 可以看到,post方法其实就一行代码(326行),里面调用了sendmessagedelayed()这个方法,里面有两个参数。先看一下第一个参数getpostmessage(r):(719行)

上方的代码中,将runnable对象赋值给message的callback属性。注:通过查看message.java文件的源代码发现,callback属性是一个runnable对象:(91行)

我们再来分析一下上方getpostmessage()这个方法,该方法完成了两个操作:

一是生成了一个message对象,二是将r对象复制给message对象的callback属性。返回的是一个message对象。

再回到326行:

这行代码相当于:

现在应该好理解了:

第一个问题,如何把一个runnable对象放到消息队列中:实际上是生成了一个message对象,并将r赋值给message对象的callback属性,然后再将message对象放置到消息队列当中。

我们再看看一下looper做了什么。打开looper.java的dispatchmessage的方法:(136行)

这里面调用了dispatchmessage()方法,打开handler.java的dispatchmessage()方法:(93至104行)

Android多线程----异步消息处理机制之Handler详解
Android多线程----异步消息处理机制之Handler详解

上方第5行代码:因为这次已经给message的callback属性赋值了,所以就不为空,直接执行这行代码。即执行handlecallback()这个方法。打开handlecallback()方法的源码:(732至734行) 

看到这个方法,就真相大白了:message的callback属性直接调用了run()方法,而不是开启一个新的子线程。

现在可以明白了: 

第二个问题: looper取出了携带有r对象的message对象以后,做的事情是:取出message对象之后,调用了dispatchmessage()方法,然后判断message的callback属性是否为空,此时的callback属性是有值的,所以执行了handlecallback(message message),在该方法中执行了 message.callback.run()。根据java的线程知识,我们可以知道,如果直接调用thread对象或者runnable对象的run()方法,是不会开辟新线程的,而是在原有的线程中执行。 

因为looper是在主线程当中的,所以dispatchmessage()方法和handlemessage()方法也都是在主线程当中运行。所以post()里面的run方法也自然是在主线程当中运行的。 使用post()方法的好处在于:避免了在主线程和子线程中将数据传来传去的麻烦。

四、message:

handler如果使用sendmessage的方式把消息入队到消息队列中,需要传递一个message对象,而在handler中,需要重写handlemessage()方法,用于获取工作线程传递过来的消息,此方法运行在ui线程上。

对于message对象,一般并不推荐直接使用它的构造方法得到,而是建议通过使用message.obtain()这个静态的方法或者handler.obtainmessage()获取。message.obtain()会从消息池中获取一个message对象,如果消息池中是空的,才会使用构造方法实例化一个新message,这样有利于消息资源的利用。并不需要担心消息池中的消息过多,它是有上限的,上限为10个。handler.obtainmessage()具有多个重载方法,如果查看源码,会发现其实handler.obtainmessage()在内部也是调用的message.obtain()。

handler中,与message发送消息相关的方法有:

message obtainmessage():获取一个message对象。

boolean sendmessage():发送一个message对象到消息队列中,并在ui线程取到消息后,立即执行。

boolean sendmessagedelayed():发送一个message对象到消息队列中,在ui线程取到消息后,延迟执行。

boolean sendemptymessage(int what):发送一个空的message对象到队列中,并在ui线程取到消息后,立即执行。

boolean sendemptymessagedelayed(int what,long delaymillis):发送一个空message对象到消息队列中,在ui线程取到消息后,延迟执行。

void removemessage():从消息队列中移除一个未响应的消息。

五、通过handler实现线程间通信:

1、在worker thread发送消息,在mainthread中接收消息:

【实例】点击反扭,将下方的textview的内容修改为“从网络中获取的数据”

Android多线程----异步消息处理机制之Handler详解

【实际意义】点击按钮时,程序访问服务器,服务器接到请求之后,会返回字符串结果,然后更新到程序。

完整版代码如下:

xml布局文件代码如下:

Android多线程----异步消息处理机制之Handler详解
Android多线程----异步消息处理机制之Handler详解

mainactivity.java代码如下:

Android多线程----异步消息处理机制之Handler详解
Android多线程----异步消息处理机制之Handler详解

 这段代码的结构,和最上面的第一章节是一样的。

上方代码中,我们在子线程中休眠2秒来模拟访问网络的操作。

65行:用字符串s表示从网络中获取的数据;70行:然后我们把这个字符串放在message的obj属性当中发送出去,并在主线程中接收(36行)。

运行后结果如下:

Android多线程----异步消息处理机制之Handler详解

点击按钮后结果如下:

Android多线程----异步消息处理机制之Handler详解

点击按钮后,后台输出结果如下:

Android多线程----异步消息处理机制之Handler详解

可以看到,子线程的名字是:thread-1118,主线程的名字是:main。

2、在mainthread中发送消息,在worker thread中接收消息:

【实例】点击按钮,在在mainthread中发送消息,在worker thread中接收消息,并在后台打印输出。

【代码】完整版代码如下:

Android多线程----异步消息处理机制之Handler详解
Android多线程----异步消息处理机制之Handler详解
Android多线程----异步消息处理机制之Handler详解
Android多线程----异步消息处理机制之Handler详解

上方的第42行至54行代码:这是mainthread中发送消息,在worker thread中接收消息的固定写法。上面的三个步骤再重复一下:

准备looper对象

在workerthread当中生成一个handler对象

调用looper的loop()方法之后,looper对象将不断地从消息队列当中取出对象,然后调用handler的handlemessage()方法,处理该消息对象;如果消息队列中没有对象,则该线程阻塞

注意,此时handlemessage()方法是在worker thread中运行的。

运行程序后,当我们点击按钮,就会在后台输出“收到了消息对象”这句话:

Android多线程----异步消息处理机制之Handler详解

小小地总结一下:

首先执行looper的prepare()方法,这个方法有两个作用:一是生成looper对象,而是把looper对象和当前线程对象形成键值对(线程为键),存放在threadlocal当中,然后生成handler对象,调用looper的mylooper()方法,得到与handler所对应的looper对象,这样的话,handler、looper 、消息队列就形成了一一对应的关系,然后执行上面的第三个步骤,即looper在消息队列当中循环的取数据。

继续阅读