Android的消息机制其实也就是Handler相关的机制,对于它的使用应该熟之又熟了,而对于它的机制的描述在网上也一大堆【比如15年那会在网上抄了一篇https://www.cnblogs.com/webor2006/p/4837623.html对它的关系描述,但仅仅是背一背概念】,在面试时也时不时的会问起它,说实话从事Android这么多年也没自己从头到尾的去将它的工作机制详细的给挼一遍,所以这里写一篇关于它的整个机制的描述来加深对Handler的核心机制的进一步了解。

Android消息机制:

先来看一张关于整个消息机制的描述图,这个流程会在之后自己从0开始手写实现的,如下:

以上模型大致解释一下:

1、以Handler的sendMessage方法为例,当发送一个消息后,会将此消息加入消息队列MessageQueue中。
2、Looper负责去遍历消息队列并且将队列中的消息分发给对应的Handler进行处理。
3、在Handler的handleMessage方法中处理该消息,这就完成了一个消息的发送和处理过程。
这里从图中可以看到参与消息处理有四个对象,它们分别是 Handler, Message, MessageQueue,Looper。

其中在图中涉及到这两个状态:

这个在之后的源码分析中是能看到的。

ThreadLocal的工作原理:

ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有再指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。为啥要先说它呢?因为打好这个基础后有助于分析Android的消息机制,下面先举例来对它的工作原理有一个了解:

先来瞅一下ThreadLocal的源码:

接受一个泛型,那该泛型是怎么用的呢?

它里面有一个ThreadLocalMap的静态内部类,然后在我们往ThreadLocal存东西时最终的这个T会赋值给ThreadLocal中的Entry类中的value,如下:

下面来定义一下:

下面来调用一下:

此时看一下这个get()方法的实现:

接下来根据当前线程来获取ThreadLocalMap:

其中可以看一下该threadLocals在Thread初始化的情况:

所以。。回到get()主流程上来:

然后:

最后setInitialValue()方法就会返回cexo,然后整个get()方法就返回了:

这就是为啥我们的结果显示cexo的原因,好,下面将在子线程中再来打印一下:

其原因就不多分析了,跟在主线程的是一模一样的流程,都是由于我们重写了initialValue()方法。

下面再来看:

此时再来分析一下set的过程:

然后再拿时:

下面再来改一下程序,再改之前需要将ThreadLocal中设置的东东给清除掉,避勉内存泄漏,如下:

好,再来新建一个线程:

也就是通过ThreadLocal存放的值是跟线程绑定的,关于它的大致使用就了解到这,下面正式进入到核心的消息机制源码分析阶段了。

Android消息机制源码分析:

1、启动App创建全局唯一得Looper对象和全局唯一得MessageQueue消息对象:

先来看一下整体这块的流程图:

以此为蓝本,接下来分析一下这块流程的源码【以Android9.0为例】:

点击进入看一下:

看到了ThreadLocal的身影了,这就是为啥要先了解它的机制的原因之所在,好,此时流程就到了这:

然后看一下这个Looper创建的细节,就会创建全局唯一的消息队列,如下:

以上就是在主线程启动时创建Looper的大致过程。

2、Activity中创建Handler:

先贴一下整个这块的流程:

下面先回顾一下它的实际使用,比较简单,主要是根据实际的应用来过行源码底层分析会比较亲切:

先来看一下Handler()的构造:

3、发送消息:

整体流程:

咱们依照此流程来分析一下:

继续往里跟:

流程就跑到了这:

接下来来看一个这个enqueueMessage()方法:

其中可以看一下Message.target变量:

也就是每个消息都绑定了Handler,下面回到主流程:

也就是如流程图的这一步:

具体看一下在全局消息队列中的处理:

呃,貌似有点颠覆对队列的认知,不应该拿到消息往队列中插么,貌似这里就是简单的给它里面的成员变量赋了个值而已呢,是的,这个消息队列一定得要知道并非是我们认知中的那种,下面看一眼它的javadoc对它的描述:

以上是消息发送的大致流程。

4、处理消息:

先来看一下这块的大致流程图:

依据它再来看一下代码流程:

下面具体来看一下该loop()方法,首先也是获取全局唯一的Looper和MessageQueue对象:

接着则会循环从队列中取消息,将会调用消息队列绑定的Handler的相关的方法来对消息进行处理,如下:

那咱们再来看一下Handler中的这个消息分发是如何来处理该消息的:

消息阻塞和延时:

Looper 的阻塞主要是靠 MessageQueue 来实现的,在next()@MessageQuese 进行阻塞,在 enqueueMessage()@MessageQueue 进行唤醒。主要依赖 native 层的 Looper 依靠 epoll 机制 进行的。
nativePollOnce(ptr, nextPollTimeoutMillis); 这里调用naive方法操作管道,由nextPollTimeoutMillis决定是否需要阻塞
nextPollTimeoutMillis为0的时候表示不阻塞,为-1的时候表示一直阻塞直到被唤醒。

消息阻塞流程:

下面具体来看一下代码,先看在Looper中的loop()的阻塞相关:

接着则看MessageQueue的next()方法了:

那假如在这块阻塞了之后,那在主线程中不会引发ANR么?其实是不会的,原因简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制。

好,下面再详细的来分析其阻塞的一个整体流程:

假设loop for循环第一次、MessageQueue for循环也第一次:

接着就会往下执行,则会执行到这:

好继续:

假如它现在等于0,则会执行到这了:

此时再下一次循环中,则就会进入阻塞状态了:

而假如mIdleHandlers.size()>0,那么执行顺序就会发生变化了,如下:

好,接下来再假设mMessages不为null:

当退出循环时,如果找到了,则走如下条件分支:

下面具体再看下里面的条件:

而这个流程往下:

这就是消息延时的一个机制。

消息延时入队流程:

此时就需要回到MessageQueue中的enqueueMessage()方法了,如下:

而如果当前消息的when要大于上一条消息的when,则会走另一个条件分支了,如下:

先来看一下javadoc的说明:

而在理解这个分支代码之前需要理解一个东东:

对于Message对像池的大小是有大小的,那是多少呢?下面看下源码的定义:

那如果消息大小超过了这个对象池总个数呢,则是插不进去的,具体这块的代码如下:

其中sPool是一个静态变量:

了解了它的对象池之后,下面再回过头来理解这个条件:

比如上一个消息为:

然后再插入个新消息为:

此时进入条件时:

也就是处理完之后就成这样了:

同样的如果再来第三个msg:

同样的也会按从小到大的顺序来进行排序:

最后则会执行唤醒的条件,如下:

 

当执行唤醒时,则在next()中正在阻塞的就会被唤醒:

手写Handler消息核心机制:

经过这么大的篇幅来对Handler核心流程的源码进行了分析之后,接下来弄一个比较有“挑战”的事,从0开始手写一上handler发送及消息接收的“核心流程”,不涉及到延时相关的东东,因为那块太复杂了,下面从ActivityThread.main()中一直到Activity创建消息到接收消息手写实现一下,这里抛开Android环境以单元测试的方式来手写,先定义一个main()方法:

来模拟它:

然后我们先将Looper、Handler、MessageQueue、Message都创建一下:

好,在main()中首先得生成全局唯一的Looper对象,如下:

接下来实现这个方法:

校仿一下:

然后在Looper的构造方法中需要初始化MessageQueue,如原码中所示:

所以继续校仿:

好,接下来再来创建Handler,如这个流程:

它里面持有Looper和MessageQueue的引用,如系统源码所示:

所以咱们在我们的构造方法中来实例化一下:

然后来在MyLooper中实现myLooper()方法,还是校仿源码:

然后再来实例化MessageQueue:

然后我们在主线程中来创建一个Handler:

我们知道在实际使用时需要重写它里面的一个handleMessage()方法,所以咱们还得在MyHandler中来定义一下该方法,如下:

此时就可以重写方法了:

好,接下来在子线程中来发送消息,如这个流程:

其中消息里面得要有一些属性,这里只定义简单的几个,如下:

然后咱们继续来创建消息:

此时咱们再来定义发送消息的方法,先看一下源码是如何写的:

所以咱们也来写一下:

我们之前分析过MessageQueue的入队方法,它是采用对像池的方式来存储的,咱们这里简单一点,直接用阻塞队列来存放,重在模拟整个过程,如下:

好,最后就是开始Looper的消息循环了,如源码所示:

接下来这就是最后一步的实现了,下面来实现一下:

好,接下来则来看一下这个next()方法的实现:

这里就木有实现阻塞队列,还是重点看整体流程,好继续,先看一下系统源码,当拿到消息之后接下来是怎么处理的:

所以咱们校仿一下:

接下来就要分发消息的处理了,还是先来参照系统的方式:

所以咱们简单处理一下:

修改一下:

好,接下来就到最后的消息处理啦,如下:

Handler中常见问题分析:

  • 为什么不能在子线程中更新UI,根本原因是什么?

    mThread是UI线程,这里会检查当前线程是不是UI线程。那么为什么onCreate里面没有进行这个检查呢。这个问题原因出现在Activity的生命周期中,在onCreate方法中,UI处于创建过程,对用户来说界面还不可视,直到onStart方法后界面可视了,再到onResume方法后界面可以交互。从某种程度来讲,在onCreate方法中不能算是更新UI,只能说是配置UI,或者是设置UI的属性。这个时候不会调用到ViewRootImpl.checkThread(),因为ViewRootImpl没被创建。而在onResume方法后,ViewRootImpl才被创建。这个时候去交互界面才算是更新UI。
    setContentView只是建立了View树,并没有进行渲染工作(其实真正的渲染工作是在
    onResume之后)。也正是建立了View树,因此我们可以通过findViewById()来获取到View对象,但是由于并没有进行渲染视图的工作,也就是没有执行ViewRootImpl.performTransversal。同样View中也不会执行onMeasure(),如果在onResume()方法里直接获取View.getHeight()/View.getWidth()得到的结果总是0。

  • 为什么主线程用Looper死循环不会引发ANR异常?

    简单说就是在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,
    此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,
    通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制。

  • 为什么Handler构造方法里面的Looper不是直接new?
    如果在Handler构造方法里面new Looper,怕是无法保证保证Looper唯一,只有用Looper.prepare()才能保证唯一性,具体去看prepare方法。
  • MessageQueue为什么要放在Looper私有构造方法初始化?
    因为一个线程只绑定一个Looper,所以在Looper构造方法里面初始化就可以保证mQueue也是唯一的Thread对应一个Looper 对应一个 mQueue。
    谈到这点,发现咱们手写的代码中关于Looper的构造定义不对,当时是定义成了public了,如下:

    而系统中定义确实是私有的:

    所以修改一下:

  • Handler.post的逻辑在哪个线程执行的,是由Looper所在线程还是Handler所在线程决定的?
    由Looper所在线程决定的。逻辑是在Looper.loop()方法中,从MsgQueue中拿出msg,并且执行其逻辑,这是在Looper中执行的,因此有Looper所在线程决定。
  • MessageQueue.next()会因为发现了延迟消息,而进行阻塞。那么为什么后面加入的非延迟消息没有被阻塞呢?
    这是因为新消息在入列时,会存在唤醒的情况,如下:
  • Handler的dispatchMessage()分发消息的处理流程?

    Msg.callback 在mHandler1.post()中使用
    mCallback在new Handler是通过接口回调

    Post()和sendMessage()都是发送消息,加入消息队列得方式也是一样,区别在于处理消息得方式。通过跟踪源码,容易区分。

终于。。整个Handler相关的东东都梳理完了,真的,还是细节挺多的,不过这么走了一遍真的受益匪浅!!如果把整个全部消化,我想未来不管面试官怎么来问Android的消息机制都会非常轻松的面对!!!

Android Handler机制彻底梳理的更多相关文章

  1. Android Handler 机制总结

    写 Handler 原理的文章很多,就不重复写了,写不出啥新花样.这篇文章的主要是对 handler 原理的总结. 1.Android消息机制是什么? Android消息机制 主要指 Handler ...

  2. Android Binder机制彻底梳理二

    根据AIDL了解整体调用流程[重点分析AIDL流程]: 在上一次https://www.cnblogs.com/webor2006/p/11741743.html中我们已经对Android Binde ...

  3. Android Handler 机制 - Looper,Message,MessageQueue

    Android Studio 2.3 API 25 从源码角度分析Handler机制.有利于使用Handler和分析Handler的相关问题. Handler 简介 一个Handler允许发送和处理M ...

  4. Android Handler机制剖析

    android的handler机制是android的线程通信的核心机制 Android UI是线程不安全的,如果在子线程中尝试进行UI操作,程序就有可能会崩溃. Android中的实现了 接收消息的& ...

  5. Android Handler机制 (一个Thead中可以建立多个Hander,通过msg.target保证MessageQueue中的每个msg交由发送message的handler进行处理 ,但是 每个线程中最多只有一个Looper,肯定也就一个MessageQuque)

    转载自http://blog.csdn.net/stonecao/article/details/6417364 在android中提供了一种异步回调机制Handler,使用它,我们可以在完成一个很长 ...

  6. android Handler机制之ThreadLocal详解

    概述 我们在谈Handler机制的时候,其实也就是谈Handler.Message.Looper.MessageQueue之间的关系,对于其工作原理我们不做详解(Handler机制详解). Messa ...

  7. android——handler机制原理

    在android版本4.0及之后的版本中多线程有明确的分工,子线程可以写所有耗时的代码(数据库.蓝牙.网络服务),但是绝对不能碰UI,想碰UI跟着主线程走,那么我们如何才能让主线程知道我们要对 UI进 ...

  8. android handler机制和Timer采用

    Timer主要用于创建一个任务来定期运行. 创建继承Task该任务等级.即任务每次跑. private class MyTask extends TimerTask { @Override publi ...

  9. Android Binder机制彻底梳理一

    Binder架构图: 先来瞅一下它的整体架构图: 其中粉红部分是上层的Binder,而蓝色的则是下层的Binder,很显然上层的是依赖于下层的. 什么是Binder[有个大概了解]? 这里从几个层面来 ...

随机推荐

  1. Efficient and Accurate Arbitrary-Shaped Text Detection with Pixel Aggregation Network(利用像素聚合网络进行高效准确的任意形状文本检测)

    PSENet V2昨日刚出,今天翻译学习一下. 场景文本检测是场景文本阅读系统的重要一步,随着卷积神经网络的快速发展,场景文字检测也取得了巨大的进步.尽管如此,仍存在两个主要挑战,它们阻碍文字检测部署 ...

  2. Python Tkinter的学习

    Tkinter模块("Tk 接口")是Python的标准Tk GUI工具包的接口.Tk和Tkinter可以在大多数的Unix平台下使用,同样可以应用在Windows和Macinto ...

  3. Spirng学习指南-第一章(完)

    Spring学习指南 内容提要 ​ Spring框架是以简化J2EE应用程序开发为特定目标而创建的,是当前最流行的Java开发框架. ​ 本书从介绍Spring框架入手,针对Spring4.3和Jav ...

  4. 基于Keras搭建MLP

    Keras是一套基于Tensorflow.Theano及CNTK后端的高层神经网络API,可以非常友好地支持快速实验,本文从零开始介绍了如何使用Keras搭建MLP并给出两个示例. 基于Ubuntu安 ...

  5. Tarjan求有向图强连通分量 BY:优少

    Tarjan算法:一种由Robert Tarjan提出的求解有向图强连通分量的线性时间的算法. 定义给出之后,让我们进入算法的学习... [情境引入] [HAOI2006受欢迎的牛] 题目描述: 每头 ...

  6. 测试函数——python编程从入门到实践

    测试函数 学习测试,得有测试的代码.下面是一个简单的函数: name_function.py def get_formatted_name(first, last): ""&quo ...

  7. jQuery Ajax async=>false异步改为同步时,导致浏览器假死的处理方法

    今天做一个需求遇到了这么个情况,就是用户个人中心有个功能,点击按钮,可以刷新用户当前的积分,这个肯定需要使用到ajax的同步请求了,当时喀喀喀三下五除二写玩了,大概代码如下: /** * 异步当前用户 ...

  8. python学习-36 文件处理b模式

    文件处理b模式 1.以2进制的方式读取 f = open('test.txt','rb') #以b模式就不能指定encoding data = f.read() print(data) f.close ...

  9. t100 debug常用指令

    1.     r.d 程式代號 Find (Ctrl+F) => Find Next(F3). 設中斷點(Toggle):Double Click => Run/Continue the ...

  10. Docker容器挂载文件(转载)

    一.Docker pull 安装 Nginx 1.查看docker仓库中的 nginx 命令 # 使用 docker search 命令搜索存放在 Docker Hub 中的镜像 docker sea ...