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的用法。

  1. 实例化Handler。重写handleMessage()方法完成对消息的处理。


    Handler实例化
  2. 创建线程。这里实现run方法,网络请求如果不熟悉可以暂时忽略,重点看画出来的部分,获取到数据后先实例化好Message 最后通过Handler的sendMessage方法发送。


    Paste_Image.png
  3. 开启线程


    开启线程

四、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) 延长指定的时间发送消息
  1. handleMessage方法主要用于处理消息,我们创建Handler时通常重写此方法。
  1. post系列的方法允许我们对Runnable对象执行入队操作(当它们被接收到的时候进入MessageQueue)。
  2. 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初始化时创建了一个消息队列,MessageQueue,同时关联了当前线程。
Looper的构造函数
  • 之后是prepare()方法。
    这里我们重点关注一下,ThreadLocal类,它是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。
    prepare方法主要是先判断是否已经有Looper实例,如果有就抛出错误,如果没有就实例化Looper并存储。
Looper的prepare()方法
  • 最后我们看下loop()函数。这个函数主要是不断地从消息队列中取出消息,并分发给消息关联的Handler。可以看到,方法内部先获取Looper实例,再获取消息队列,之后是个死循环,不断从队列中取出消息并分发给Message的target,即Handler。
Looper的loop()方法

七、异步消息处理机制总结

综合上面的内容,相信大家对整个机制已经了解的差不多了。最后我们以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的原因。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容