前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android :安卓学习笔记之 Handler机制 的简单理解和使用

Android :安卓学习笔记之 Handler机制 的简单理解和使用

作者头像
233333
发布2024-06-25 15:00:19
2740
发布2024-06-25 15:00:19
举报

Handler机制

1、Handler使用的引出

有这样一个需求,当打开Activity界面时,开始倒计时,倒计时结束后跳转新的界面(思维活跃的朋友可能立马想到如果打开后自动倒计时,就类似于各个APP的欢迎闪屏页面),如下图:

可能觉得直接开启一个包含倒序循环的子线程就ok了,具体实现如下:

activity_main

MainActivity.java

但当点进入界面时,会发现程序奔溃了,logcat中错误日志如下(只有UI线程可以更改UI界面):

由此我们发现在安卓开发中,例如上面的示例,我们常常通过一个线程来完成某些操作,然后同步显示对应的视图控件UI上,通过上面的例子我们也知道了安卓中无法直接通过子线程来进行UI更新操作,对于这种情况,Android提供了一套异步消息处理机制Handler。

2、背景和定义

Handler一套 Android 消息传递机制,主要是子线程UI更细消息传递给主线程,从而主线程更新UI。

  • Android 主线程的UI,只能主线程更新。 如果多个线程都能更新,势必要「加锁」,还不如采用「单线程消息队列机制」
  • 主线程内部维护一个循环。没有消息时候,这个循环是阻塞的。新来消息(或者阻塞timeout)时会唤醒,接着处理新到来消息。

3、作用和意义

  • 在多线程的应用场景中,将工作线程中需更新UI的操作信息 传递到 UI主线程,从而实现 工作线程对UI的更新处理,最终实现异步消息的处理
  • 多个线程并发更新UI的同时 保证线程安全

4、主要参数

(1)Message,线程之间传递的消息,用于不同线程之间的数据交互。Message中的what字段用来标记区分多个消息,arg1、arg2 字段用来传递int类型的数据,obj可以传递任意类型的字段。

(2)Handler,用于发送和处理消息。其中的sendMessage()用来发送消息,handleMessage()用于消息处理,进行相应的UI操作。

(3)MessageQueue,消息队列(先进先出),用于存放Handler发送的消息,一个线程只有一个消息队列。

(4)Looper,可以理解为消息队列的管理者,当发现MessageQueue中存在消息,Looper就会将消息传递到handleMessage()方法中,同样,一个线程只有一个Looper。

5、工作原理及流程

Handler机制的工作流程主要包括4个步骤:

1、异步通信准备 2、消息发送 3、消息循环 4、消息处理

工作流程图:

5.1、对应关系

线程(Thread)、循环器(Looper)、处理者(Handler)之间的对应关系如下:

  • 1个线程(Thread)只能绑定 1个循环器(Looper),但可以有多个处理者(Handler)
  • 1个循环器(Looper) 可绑定多个处理者(Handler)
  • 1个处理者(Handler) 只能绑定1个循环器(Looper)

6、深入分析 Handler机制源码

6.1、Handler机制的核心类

Handler机制 中有3个重要的类:

  • 处理器 类(Handler)
  • 消息队列 类(MessageQueue)
  • 循环器 类(Looper)

6.2、核心方法

下面的源码分析将根据 Handler的使用步骤进行

  • Handler使用方式因发送消息到消息队列的方式不同而不同,共分为2种:使用Handler.sendMessage()、使用Handler.post()
6.3、方式1:使用 Handler.sendMessage()
6.3.1、 创建Handler类对象

步骤1:在主线程中 通过匿名内部类 创建Handler类对象

从上面可看出:

  • 当创建Handler对象时,则通过 构造方法 自动关联当前线程的Looper对象 & 对应的消息队列对象(MessageQueue),从而自动绑定了 实现创建Handler对象操作的线程

那么,当前线程的Looper对象 & 对应的消息队列对象(MessageQueue) 是什么时候创建的呢?

  • 在上述使用步骤中,并无 创建Looper对象 & 对应的消息队列对象(MessageQueue)这1步
6.3.1.1、隐式操作1:创建循环器对象& 消息队列对象

创建Looper对象主要通过方法:Looper.prepareMainLooper()、Looper.prepare(); 创建消息队列对象(MessageQueue)方法:创建Looper对象时则会自动创建,即:创建循环器对象(Looper)的同时,会自动创建消息队列对象(MessageQueue)

总结:

1、创建主线程时,会自动调用ActivityThread的1个静态的main();而main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象,同时也会生成其对应的MessageQueue对象

即 主线程的Looper对象自动生成,不需手动生成;而子线程的Looper对象则需手动通过Looper.prepare()创建 在子线程若不手动创建Looper对象 则无法生成Handler对象 2、根据Handler的作用(在主线程更新UI),故Handler实例的创建场景 主要在主线程

3、生成Looper & MessageQueue对象后,则会自动进入消息循环:Looper.loop(),即又是另外一个隐式操作。

6.3.1.2、隐式操作2:消息循环

此处主要分析的是Looper类中的loop()方法

总结:

  • 消息循环的操作 = 消息出队 + 分发给对应的Handler实例
  • 分发给对应的Handler的过程:根据出队消息的归属者通过dispatchMessage(msg)进行分发,最终回调复写的handleMessage(Message msg),从而实现 消息处理 的操作
  • 特别注意:在进行消息分发时(dispatchMessage(msg)),会进行1次发送方式的判断: 1、若msg.callback属性不为空,则代表使用了post(Runnable r)发送消息,则直接回调Runnable对象里复写的run() 2、若msg.callback属性为空,则代表使用了sendMessage(Message msg)发送消息,则回调复写的handleMessage(msg)

图表总结如下:

6.3.2、创建消息对象
6.3.3、在工作线程中 发送消息到消息队列中

多线程的实现方式:AsyncTask、继承Thread类、实现Runnable

总结

Handler发送消息的本质 = 为该消息定义target属性(即本身实例对象) & 将消息入队到绑定线程的消息队列中。具体如下:

6.3.4、总结

6.4、方式2:使用Handler.post()

实际上,该方式与方式1中的Handler.sendMessage()工作原理相同、源码分析类似,下面将主要讲解不同之处

6.4.1、在主线程中创建Handler实例
6.4.2、创建消息对象

消息对象的创建 = 内部 根据Runnable对象而封装

参考下一节(6.4.3)

6.4.3、在工作线程中 发送消息到消息队列中

从上面的分析可看出:

  • 消息对象的创建 = 内部 根据Runnable对象而封装;
  • 发送到消息队列的逻辑 = 方式1中sendMessage(Message msg)。

下面,我们重新回到步骤1前的隐式操作2:消息循环,即Looper类中的loop()方法

至此,你应该明白使用 Handler.post()的工作流程与Handler.sendMessage()类似,区别在于:

  • 不需外部创建消息对象,而是内部根据传入的Runnable对象 封装消息对象
  • 回调的消息处理方法是:复写Runnable对象的run()
6.4.4、总结

关于使用 Handler.post()的源码解析完毕,总结如下:

6.4.5、Handler.post和Handler.sendMessage的区别

6.5、MessageQueue分析

首先来看看构造函数

构造函数之上定义了很多native方法

native之上定义了几类数据结构,Message、ArrayList、SparseArray、数组

6.5.1、消息对象Message源码分析

Message有如下公有属性,供程序员调用:

Message有如下私有属性,用途如下:

Message的源码,我们可以得出如下结论,Message是一种链表结构,每个Message持有以下信息:

1、用于传递的数据,如what、arg1、arg2、obj 2、用于执行当前Message的Handler 3、用于执行当前Message的回调接口CallBack、子线程Runnable 4、当前Message的属性,如延时时间、执行标识、Bundle数据,下一个Message引用。这种结构构成了链表。

6.5.2、enqueueMessage是如何处理Message
  • 图中 1处会判断如果 Message 中的 target 没有被设置,则直接抛出异常; 图中2和 3 处会按照 Message 的时间 when 来有序得插入 MessageQueue 中,可以看出 MessageQueue实际上是一个链表维护的有序队列,只不过是按照 Message 的执行时间来排序。
  • 看到这里,思路似乎终止了,我们跟随Handler、MessQueue的脚步,只看到了Message被插入到MessageQueue的私有队列中。那我们产生的Message什么时候会背消费呢?

接下来我们看看Looper吧!

6.6、Looper源码分析

在任何线程要开启Loop,都要用Looper.prepare()+Looper.looper()的方式。

以APP主进程为例,APP进程启动入口的main方法,也是通过这种方式开启loop的。与子线程细微不同的是,主线程开启looper用的是prepareMainLooper。

6.6.1、Looper#构造函数原理

Looper构造函数做了两件事情,初始化消息队列MessageQueue对象,记录当前线程信息。

6.6.2、Looper.myLooper()原理

可以看到myLooper是从threadLocal中取出Looper对象。在Looper类中定义了如下变量sThreadLocal、mQueue、sMainLooper、mThread

6.6.3、Looper.prepare()原理

prepare就是 new 出一个 Looper。核心之处在于将 new 出的 Looper 设置到了线程本地变量 sThreadLocal 中。也就是说创建的 Looper 与当前线程发生了绑定。

Looper#prepareMainLooper原理

prepareMainLooper只有在APP进程启动的时候有用,并不推荐开发者调用这个函数。

6.6.4、Looper.loop()原理及总结
  • 1、 取出Looper对象
  • 2 、校验当前线程是否持有Looper,是否启动而来Looper.prepare
  • 3、从Looper中取出对应的MessageQueue,主线程Looper就取出主线程的MessageQueue,子线程就取出子线程MessageQueue
  • 4 、从MessageQueue中取出Message
  • 5、Message.target属性,即handler,调用Message绑定好的handler.dispatchMessage,处理消息。

也就是说,Message最终交由与Message绑定的Handler处理。Looper只是负责无限循环+从MessageQueue中读取。

6.7、Handler机制的源码总结

7、具体使用案例

1个简单 “更新UI操作” 的案例,主布局文件相同 = 1个用于展示的TextView,具体如下:

布局代码:activity_main.xml

7.1、使用 Handler.sendMessage()

7.1.1、方式1:新建Handler子类(内部类)

实验结果:

7.1.2、方式2:匿名内部类

实验结果

7.2、使用 Handler.post()

实验结果:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Handler机制
    • 1、Handler使用的引出
    • 2、背景和定义
    • 3、作用和意义
    • 4、主要参数
    • 5、工作原理及流程
      • 5.1、对应关系
      • 6、深入分析 Handler机制源码
        • 6.1、Handler机制的核心类
          • 6.2、核心方法
            • 6.3、方式1:使用 Handler.sendMessage()
            • 6.3.2、创建消息对象
            • 6.3.3、在工作线程中 发送消息到消息队列中
            • 6.3.4、总结
          • 6.4、方式2:使用Handler.post()
            • 6.4.1、在主线程中创建Handler实例
            • 6.4.2、创建消息对象
            • 6.4.3、在工作线程中 发送消息到消息队列中
            • 6.4.4、总结
            • 6.4.5、Handler.post和Handler.sendMessage的区别
          • 6.5、MessageQueue分析
            • 6.6、Looper源码分析
              • 6.6.1、Looper#构造函数原理
              • 6.6.2、Looper.myLooper()原理
              • 6.6.3、Looper.prepare()原理
              • 6.6.4、Looper.loop()原理及总结
            • 6.7、Handler机制的源码总结
            • 7、具体使用案例
              • 7.1、使用 Handler.sendMessage()
                • 7.1.1、方式1:新建Handler子类(内部类)
                • 7.1.2、方式2:匿名内部类
              • 7.2、使用 Handler.post()
              相关产品与服务
              消息队列 CMQ
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档