Android学习笔记15 多线程编程之Handler异步消息处
异步消息处理机制,Android开发中必须要掌握的内容。作为面试中经常会提问的知识点,同时也是开发中经常要打交道的部分,我们必须要对它透彻地研究,明白实现原理,熟悉整个流程,以及Handler、Message、Looper等不同角色之间的关系。下面是自己对于这部分的学习总结。
一、背景介绍
二、异步消息处理机制概述
三、示例与解析
四、Handler详解
五、Message与MessageQueue详解
六、Looper详解
七、异步消息处理机制总结
一、背景介绍
这部分是背景介绍,我们用过Handler,用过Message,可能了解过Looper等等,但是Android为什么要有这一套异步消息处理机制?
当一个应用启动时,如果它没有其它组件在运行的话,Android系统会为这个应用创建一个Linux进程,进程里有一个执行线程(这个执行线程叫主线程,也称为UI线程)。默认情况下,同一个应用的所有组件都运行在相同的进程和线程里。
系统绝对不会为一个组件的实例创建单独的线程。运行于同一进程的所有组件均在 UI 线程中实例化,并且对每个组件的系统调用均由该线程进行分发。因此,响应系统回调的方法(例如,报告用户操作的 onKeyDown()方法或生命周期回调方法)始终在进程的 UI 线程中运行。
Android 的单线程模式有两个务必要遵守的原则,一是不要阻塞 UI 线程,二是不要在 UI 线程之外访问Android这些界面UI元素。
到这里,我们应该初步了解了什么是UI线程,Android的单线程模式。但是,在开发过程中我们可能会有这样的需要,想要联网获取数据,然后将数据显示到界面上,因为联网获取数据是个比较耗时的任务,为了不阻塞UI线程,我们需要创建一个新的线程来进行数据获取,但是这样就会造成数据获取到的时候不是在UI线程里的,我们不能直接在非UI线程里更新界面,所以要把数据发送到UI线程里。怎么发送呢?这时,异步消息处理机制就出现了。
二、异步消息处理机制概述
想要搞清楚异步消息处理机制,我们必须要搞清楚它的几个关键知识点之间的关系,包括Handler、Message、MessageQueue、Looper。
下面先对整个流程总体上有个把握。我们以上面提到的联网获取数据的需求为例。
首先,我们想要联网获取数据,考虑到联网获取数据可能是个耗时的任务,我们需要开启一个新的线程,在其中完成操作。开启线程的方法有好几种,但是经常使用的是先定义一个实现接口Runable的类,然后在方法run()里访问网络数据,最后在需要的地方实例化Thread对象,传入之前定义的类的实例,调用方法start()来开启线程。
上一步我们在run方法里访问网络,获取到数据后需要把它发送到UI线程里。怎么发送,在创建线程之前,我们先实例化一个Handler。这样在run方法里我们可以通过实例化的Handler把数据发送到UI线程里,数据在发送过程中要用Message作为载体。
Handler,直接翻译就是处理器,它可以发送Message,也可以处理Message。在UI线程里实例化Handler,然后可以利用它把非UI线程里的Message发送到Message Queue里,之后它可以对从Message Queue里出来的消息进行处理。
Looper,在平时的使用中我们一般会与Handler、Message打交道比较多,但是Looper是背后运行不可或缺的一个部分,它的作用主要是不断从MessageQueue中取出Message,分发给Handler,让Handler处理。
三、示例与解析
下面看一个例子,可以很直观地了解Handler和Message的用法。
-
实例化Handler。重写handleMessage()方法完成对消息的处理。
Handler实例化 -
创建线程。这里实现run方法,网络请求如果不熟悉可以暂时忽略,重点看画出来的部分,获取到数据后先实例化好Message 最后通过Handler的sendMessage方法发送。
Paste_Image.png -
开启线程
开启线程
四、Handler详解
1、概述
前面已经提到,Handler可以译为处理器,它的主要作用有两个,一是定时执行Message和Runnable,二是让一个执行动作进入队列,在另外的线程里执行。
Handler允许我们发送和处理与线程的相关的Message以及Runnable对象。每个Handler实例都关联着一个单线程,同时也关联着那个线程的消息队列。当我们创建一个新的Handler时,它就与创建它的线程以及线程的消息队列相关联,也就是说,它将会传递Message和Runnable给MessageQueue,并在它们从MessageQueue出来时执行它们。
2、用法
Handler的第一个作用是定时执行Message和Runnable,常用方法:
方法 | 简介 |
---|---|
handleMessage(Message) | 对接收到的消息进行处理 |
post(Runnable) | 让Runnable对象入队 |
postAtTime(Runnable, long) | 指定的时间入队 |
postDelayed(Runnable, long) | 延长指定的时间入队 |
sendEmptyMessage(int) | 发送只有标识的消息 |
sendMessage(Message) | 发送消息 |
sendMessageAtTime(Message, long) | 指定的时间发送消息 |
sendMessageDelayed(Message, long) | 延长指定的时间发送消息 |
- handleMessage方法主要用于处理消息,我们创建Handler时通常重写此方法。
- post系列的方法允许我们对Runnable对象执行入队操作(当它们被接收到的时候进入MessageQueue)。
- sendMessage系列方法允许我们对Message对象执行入队操作,Message对象包含数据,这些数据将会在Handler的handleMessage(Message)方法中被处理。
五、Message与MessageQueue详解
1、Message
我们在新的线程里获取到数据时,首先把数据给封装到Message里,再利用Handler来发送。Message是一个可以被发送给Handler,包含标识和数据的对象。Message对象包含两个额外的int类型变量和一个额外的对象,利用它们大多数情况下我们不用再做内存分配相关工作。实例化Message最好的方法是调用Message.obtain()或Handler.obtainMessage(),因为这两个方法是从一个可回收利用的对象池中获取Message的。
2、MessageQueue
MessageQueue就是Looper分发的Message集合,Message不是直接添加到MessageQueue中的,而是通过与Looper关联的Handler对象来完成的。我们可以使用方法Looper.myQueue()来获取当前线程的MessageQueue。
六、Looper详解
Looper,就是用于运行一个线程的消息队列的类。默认情况下线程没有关联的消息队列,可以通过调用方法prepare()来创建队列,然后调用loop()来不断分发消息直到都处理完。下面可以看看Looper类解析。
Looper的构造函数
- 首先是构造函数。 可以看到Looper初始化时创建了一个消息队列,MessageQueue,同时关联了当前线程。
Looper的prepare()方法
- 之后是prepare()方法。
这里我们重点关注一下,ThreadLocal类,它是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。
prepare方法主要是先判断是否已经有Looper实例,如果有就抛出错误,如果没有就实例化Looper并存储。
Looper的loop()方法
- 最后我们看下loop()函数。这个函数主要是不断地从消息队列中取出消息,并分发给消息关联的Handler。可以看到,方法内部先获取Looper实例,再获取消息队列,之后是个死循环,不断从队列中取出消息并分发给Message的target,即Handler。
七、异步消息处理机制总结
综合上面的内容,相信大家对整个机制已经了解的差不多了。最后我们以Android开发艺术探索作者任玉刚的一段话作总结。
异步消息处理机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueue和Looper的支撑。
MessageQueue的中文翻译是消息队列,顾名思义它的内部存储了一组消息,其以队列的形式对外提供插入和删除的工作,虽然叫做消息队列,但是它的内部存储结构并不是真正的队列,而是采用单链表的数据结构来存储消息列表。
Looper的中文翻译为循环,在这里可以理解为消息循环,由于MessageQueue只是一个消息的存储单元,它不能去处理消息,而Looper就填补了这个功能,Looper会以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待着。
Looper中还有一个特殊的概念,那就是ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。大家知道,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,那么Handler内部如何获取到当前线程的Looper呢?这就要使用ThreadLocal了,ThreadLocal可以在不同的线程之中互不干扰地存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。
当然需要注意的是,线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。大家经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。