【Android】Handler用法及原理解析

news2024/9/20 18:37:37

文章目录

  • 用处
  • 基本用法
    • 用法一:使用sendMessage和handleMessage方法
    • 用法二:使用post方法
  • 法一工作原理
    • Handler的sendMessage
    • Message
      • 成员变量
    • MessageQueue
    • Looper
      • 主线程自动初始化
      • 子线程手动创建
      • **prepare**
      • loop
    • Handler的dispatchMessage
  • 法二工作原理
    • Handler的post
    • Handler的dispatchMessage

Handler 是用于在不同线程之间进行消息传递的机制。它与 LooperMessageQueue一起工作,帮助在不同线程间传递和处理消息,特别是在主线程与子线程之间进行通信。

用处

  1. 子线程通过 Handler 更新 UI
  2. 用于线程间通信

基本用法

用法一:使用sendMessage和handleMessage方法

  1. 创建Handler:通常在主线程中创建一个Handler,用于处理从其他线程发送的消息。

    Handler handler = new Handler();
    
  2. 发送消息:在子线程中,通常使用handler.sendMessage()来向主线程发送消息。

    new Thread(new Runnable() {
        @Override
        public void run() {
            Message message = Message.obtain();
            message.what = 1;
            handler.sendMessage(message);
        }
    }).start();
    
  3. 处理消息:主线程中的Handler会调用handleMessage()方法来处理接收到的消息。

    Handler handler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 1:
                    // 处理消息
                    break;
            }
        }
    };
    

用法二:使用post方法

  1. 创建 Handler

    Handler handler = new Handler(Looper.getMainLooper());
    
  2. 使用 post 方法:可以直接在 Handler 中执行需要在主线程上运行的代码块。

    handler.post(new Runnable() {
        @Override
        public void run() {
            // 执行主线程任务
        }
    });
    

    区别:

    sendMessage: 适用于更复杂的消息处理和任务调度,特别是需要在多个线程之间通信时。它依赖于 Handler 类,通过消息机制来传递和处理任务。

    post: 更加简洁和直接,主要用于在主线程中执行任务,尤其是需要更新 UI 的情况。适合用于在视图中直接执行代码而不涉及复杂的线程通信。

法一工作原理

主要依靠以下

  1. Looper:负责不断从 MessageQueue 中取出消息并分发给相应的 Handler 进行处理。

  2. HandlerHandler 是一个用于发送和处理消息的类。它可以用来将 MessageRunnable 对象发送到消息队列中,并在对应的线程中处理这些消息。

  3. Message:用于在 HandlerLooper 之间传递消息。

  4. MessageQueueMessageQueue 是消息的容器,负责存储和管理待处理的消息。维护了一个消息队列,按顺序处理消息。

举个例子:

天气预报:子线程获取到了天气数据,然后使用Message将数据包装,通过Handler对象的sendMessage方法将Message对象插入到消息队列(MessageQueue)中,主线程通过Looper.loop方法死循环检查MessageQueue中是否有消息,主线程的 Handler 会通过 handleMessage() 方法接收这个 Message

Handler的sendMessage

handler.sendMessage(message);

img

sendMessageDelayedsendEmptyMessageAtTimesendEmptyMessageDelayedsendEmptyMessage

这些Handler类中发送消息的方法最终调用的方法都是sendMessageAtTime

sendMessageAtTime方法中再调用enqueueMessage插入消息到消息队列

image-20240911201151006

Message

Message 类用于 HandlerLooper 之间的消息传递

Message类定义一个包含描述和任意数据对象的消息,该消息可以发送给Handler 。此对象包含两个额外的 int 字段和一个额外的对象字段,允许您在许多情况下不进行分配。
虽然 Message 的构造函数是公共的,但获取其中一个的最佳方法是调用Message.obtain()Handler.obtainMessage()方法之一,它们会从回收对象池中拉出它们。

成员变量

部分成员变量分析

// what 字段是一个用户自定义的消息代码,用来标识消息的类型。
public int what;

//arg12 是一个整型值字段,用来传递简单的整型数据。
public int arg1;
public int arg2;

// obj 是一个任意的对象,允许发送者将任何类型的对象传递给接收者。
public Object obj;

// 可选的 Bundle,用于传递复杂数据。
Bundle data;

//  target 是消息最终要传递到的 Handler,负责处理该消息。
Handler target;

// 用于将 Message 对象串联在一起的指针,
Message next;

获取消息的首选方法是调用Message. obtain()

image-20240913093007364

最终实际都是通过obtain()方法获取的Message对象

obtain()源码:

// 从全局池返回一个新的 Message 实例。这允许我们在很多情况下避免分配新对象。
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

用于从全局池(sPool)中返回一个新的 Message 实例。如果池中有可用的消息对象,它将从池中获取,否则创建一个新的。可以优化性能

MessageQueue

MessageQueue 是一个用于保存消息(Message)的队列,其内部实现为一个单链表结构。

  1. enqueueMessage(Message msg, long when):用于将消息插入到队列的链表中,when 参数决定消息应该在什么时候被执行。消息按 when 的顺序排列,越早需要执行的消息排在前面。

  2. next():此方法用于从队列中取出下一个需要处理的消息。如果当前队列为空,线程会进入阻塞状态,直到新的消息到来。

Looper

用于运行线程消息循环的类。

Looper 是一种管理消息队列将消息分发到线程的机制。每个线程都有自己的消息队列和消息循环,这就是由 Looper 来实现的。

默认情况下,线程没有与之关联的消息循环;若要创建一个消息循环,请在要运行循环的线程中调用prepare ,然后loop以让其处理消息,直到循环停止。

主线程自动初始化

  • 主线程(UI线程)是应用启动时的默认线程,负责处理所有与用户界面相关的任务。Android 系统在应用启动时会自动为主线程创建一个 Looper,因此你无需手动调用 Looper.prepare()Looper.loop()
  • 这个主线程的 Looper 用来处理 UI 事件和系统消息,如用户点击、触摸事件等,这确保了 UI 的更新必须在主线程上执行

主线程ActivityThreadmain方法中自动初始化了:

public static void main(String[] args) {
    ...

    // 为主线程准备消息循环,创建与当前线程关联的Looper,并设置为主线程的Looper
    Looper.prepareMainLooper();

    ...
        
    // 初始化主线程的 Handler,用于处理消息队列中的消息
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    ...
        
    // 启动消息循环,开始处理消息队列中的消息,这一步将一直运行,直到应用程序终止
    Looper.loop();

    // 如果消息循环意外退出,抛出异常。正常情况下,主线程的消息循环不应该终止
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

子线程手动创建

  • 当你在 子线程 中需要处理消息时(例如子线程需要执行长时间运行的任务,并且希望在任务完成后更新 UI),你需要手动为这个子线程创建 Looper。步骤通常包括:

    1. 调用 Looper.prepare():为当前线程创建一个 Looper 实例。
    2. 创建 Handler
    3. 调用 Looper.loop():进入消息循环,不断从消息队列中获取消息并分发给相应的 Handler 处理。

    示例

    class LooperThread extends Thread {
           public Handler mHandler;
     
           public void run() {
               Looper.prepare();
     
               mHandler = new Handler(Looper.myLooper()) {
                   public void handleMessage(Message msg) {
                       // process incoming messages here
                   }
               };
     
               Looper.loop();
           }
    }
    

prepare

作用:

在当前线程中创建一个 Looper,以便线程可以拥有一个消息循环(MessageQueue)。通常在子线程中需要手动调用,主线程已经自动调用了 Looper.prepare()

由于Looper构造方法被私有了,只能通过调用 prepare() 方法

image-20240911213416670

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

这里的sThreadLocal是一个map类型容器,可以保证了1个线程中只有1个对应的Looper

1个线程中可以有多个Handler,1个线程中只有1个对应的Looper

如果已经存在Looper就抛出异常,否则new一个Looper放入sThreadLocal

image-20240911213829044

loop

  • Looper负责管理线程的消息循环,它在loop()方法中不断从MessageQueue中取出消息,并通过Handler的dispatchMessage()方法将消息分发给对应的Handler进行处理。
  public static void loop() {
        // 获取当前线程的looper对象
        final Looper me = myLooper();
        // 防止没有在该方法调用前调用prepare方法
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
      
        ...
            
        // 使用死循环来遍历消息队列,挑出需要执行的 Message 并分发
        for (;;) {
            if (!loopOnce(me, ident, thresholdOverride)) {
                return;
            }
        }
    } 
  • 在Looper的循环过程中,取出的每个Message对象都会通过HandlerdispatchMessage()方法传递给目标Handler,最终由目标Handler的handleMessage()方法处理消息。
private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    
    // 从消息队列中获取下一个消息。如果队列中没有消息,next() 可能会阻塞。
    // 当消息队列为空(即没有消息时),返回 null,表示消息队列正在退出。
    Message msg = me.mQueue.next(); 
    if (msg == null) {
        // 如果消息为空,表示消息队列正在退出,终止循环。
        return false;
    }

    ...
    
    try {
        // msg.target是指向一个Handler对象的引用,即消息的接收者
        // 分发消息到Handler进行处理,实际处理逻辑在msg.target.dispatchMessage()中。
        msg.target.dispatchMessage(msg);
        ...
    } catch (Exception exception) {
        ...
    } finally {
        ...
    }

    ...

    // 回收 Message 对象,避免对象的重复创建,提升性能。
    msg.recycleUnchecked();

    // 返回 true,表示循环可以继续处理下一个消息。
    return true;
}

Handler的dispatchMessage

没有使用post方法的话,调用重写的handleMessage方法

//  处理系统消息的方法。
public void dispatchMessage(@NonNull Message msg) {
    // 检查消息是否包含回调函数
    if (msg.callback != null) {
        // 如果有回调函数,处理回调
        handleCallback(msg);
    } else {
        // 如果没有回调函数
        // 检查是否设置了自定义的回调处理器
        if (mCallback != null) {
            // 如果自定义回调处理器处理了消息,直接返回
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 如果自定义回调处理器没有处理消息,则调用默认的消息处理方法
        handleMessage(msg);
    }
}

法二工作原理

Handler的post

handler.post(new Runnable() {
    @Override
    public void run() {
        
    }
});

post方法,一般以匿名内部类的形式发送Runnable对象,在Runnable对象重写的run()方法中直接对UI进行更新

image-20240913175006276


post类方法用以发送Runnable对象,send类方法用以发送Message对象,二者并不矛盾也不重复

观察Message类,可以发现安卓把Runnable对象作为Message的成员变量

image-20240913175515238


 sendMessageDelayed(getPostMessage(r), 0)

post方法又调用了sendMessageDelayed,注意**getPostMessage(r)**,在这个方法里就把我们的Runnable对象赋值到了Runnable类型的callback上了

private static Message getPostMessage(Runnable r) {
    // 获取一个 Message 实例
    Message m = Message.obtain();
    
    // 将传入的 Runnable 对象设置为 Message 的回调函数。
    m.callback = r;
    
    // 返回配置好的 Message 对象。
    return m;
}

Handler的dispatchMessage

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        // 如果消息包含 callback,调用 handleCallback 方法
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return; // 自定义回调处理器处理了消息
            }
        }
        handleMessage(msg); // 默认处理消息
    }
}

发现该消息有Runnable就调用handleCallback方法执行 Runnable 对象。

    private static void handleCallback(Message message) {
        message.callback.run();
    }

其他部分与法一原理一致



感谢您的阅读
如有错误烦请指正


参考:

  1. Handler类中发送消息-post()和postDelay()方法精炼详解
  2. android Handler机制原理解析(一篇就够,包你形象而深刻)_android handler的机制和原理-CSDN博客
  3. Handler 都没搞懂,拿什么去跳槽啊?0. 前言 做 Android 开发肯定离不开跟 Handler 打交道,它通 - 掘金 (juejin.cn)
  4. Android 消息机制(Handler + MessageQueue + Looper) - 知乎 (zhihu.com)
  5. Android——Handler一篇就看懂_android handler-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2132281.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习中最常见的50个问题(进阶篇)

机器学习中最常见的50个问题 进阶篇 1.解释SVM的工作原理。 SVM&#xff0c;全称支持向量机&#xff08;Support Vector Machine&#xff09;&#xff0c;是一种有监督学习算法&#xff0c;主要用于解决数据挖掘或模式识别领域中的数据分类问题。 SVM的工作原理是建立一个最…

TypeScript 扩展

扩展 ?:可选参数 可选链事实上并不是TypeScript独有的特性&#xff0c;它是ES11&#xff08;ES2020&#xff09;中增加的特性 可选链使用可选链操作符 ? 作用是当对象的属性不存在时&#xff0c;会短路&#xff0c;直接返回undefined&#xff0c;如果存在&#xff0c;那么…

小程序开发设计-小程序简介①

1.小程序与普通网页开发的区别&#xff1a; 1.运行环境不同&#xff1a; 网页运行在浏览器环境中。 小程序运行在微信环境中。 2.API不同&#xff1a; 由于运行环境不同&#xff0c;所以小程序中&#xff0c;无法调用DOM和BOM的API。但是&#xff0c;小程序中可以调用微信环境提…

摊牌了!一文教会你轻松上手豆包MarsCode 编程助手!

豆包MarsCode 编程助手是豆包旗下的 AI 编程助手&#xff0c;提供以智能代码补全为代表的 AI 功能。豆包MarsCode 编程助手支持主流的编程语言和 IDE&#xff0c;在开发过程中提供单行代码或整个函数的编写建议。此外&#xff0c;它还支持代码解释、单测生成和问题修复等功能&a…

收藏!6个PPT素材模板网站,快速做出好看的PPT

找PPT模板一定要收藏好这6个网站&#xff0c;能让你快速做出好看的PPT&#xff0c;重点十可以免费下载&#xff0c;赶紧收藏&#xff01; 1、菜鸟图库 ppt模板免费下载|ppt背景图片 - 菜鸟图库 菜鸟图库网有非常丰富的免费素材&#xff0c;像设计类、办公类、自媒体类等素材都…

时序必读论文05|PatchTST : 时序数据Patch已成趋势【ICLR 2023】

书接上回&#xff0c;我们在之前的文章已经分析了直接把transformer应用到时间序列预测问题的不足&#xff0c;其中我们总结了4个不足&#xff1a;分别是&#xff1a; 注意力机制的计算复杂度高&#xff0c;为 O(N^2)&#xff0c;并且计算得出的权重仅有少部分有用&#xff1b;…

【TCP三次握手+四次挥手(个人理解版本)】

TCP协议介绍 TCP&#xff08;传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的传输层通信协议&#xff08;它是全双工工作模式&#xff09;。以下是对它的具体介绍&#xff1a; 基本概念 定义&#xff1a;TCP是Transmission Control Protocol的缩写&#xff…

PHP无缝对接预订无忧场馆预订系统小程序源码

无缝对接&#xff0c;预订无忧 —— 场馆预订系统&#xff0c;让每一次活动都完美启航&#xff01; 一、告别繁琐流程&#xff0c;预订从未如此简单 你是否曾经为了预订一个合适的场馆而焦头烂额&#xff1f;繁琐的咨询、确认、支付流程&#xff0c;让人心力交瘁。但现在&…

如何利用Java进行快速的足球大小球及亚盘数据处理与分析

在当今信息爆炸的时代&#xff0c;大量的数据产生和积累&#xff0c;对于企业和个人来说&#xff0c;如何高效地处理和分析这些数据成为了一项重要的任务。Java作为一门强大的编程语言&#xff0c;提供了丰富的工具和库&#xff0c;可以帮助我们快速进行数据处理与分析。下面将…

vue3中实现拖拽排序(vue-draggable-next的使用)

1.安装插件 npm i vue-draggable-next 2.引入使用 <template> <vue-draggable-next v-model"list" tag"div" handle".warn-card" group"warngroup" ghost-class"ghost"class"mb10 warn-card-box" ani…

【mysql】逻辑运算符

逻辑运算符 逻辑运算符主要是为了判断表达式的真假,返回结果也是1,0,null OR 这里面或就是两个条件或的关系,比如我要department_id等于10和等于20的情况就可以使用或. SELECT last_name,salary,department_id FROM employees WHERE department_id10 OR department_id20 …

Unreal游戏初始化流程

前言 本文主要是总结Unreal在游戏启动时的初始化流程&#xff0c;包括讨论PIE和Standalone的区别&#xff0c;避免把一些初始化逻辑放在不合适的位置&#xff0c;比如我希望在所有Actor BeginPlay后执行某个逻辑&#xff0c;那我如果把它放在Subsystem的initialize中显然就会搞…

Golang使用ReverseProxy实现反向代理

目录 1.源码结构体 2.官方单机示例 3.使用示例 4.简单的http服务&#xff08;用于测试&#xff09; 1.源码结构体 type ReverseProxy struct {// Rewrite 必须是一个函数&#xff0c;用于将请求修改为要使用 Transport 发送的新请求。然后&#xff0c;其响应将原封不动地…

打造古风炫酷个人网页:用HTML和CSS3传递笔墨韵味

需要用到的背景大家可以自己找喜欢的风格!!! 当然俺把俺用的背景放到文章最后了哦&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 感谢关注和支持 长期更新哦~~~ 1. 简洁的页面布局&#xff1a;保持优雅和对称 在古风设计中&#xff0c;布局的对称性非常重要…

【知识图谱】3.Protege下载安装

一、Protege 1.相关介绍 Protg软件是斯坦福大学医学院生物信息研究中心基于Java语言开发的本体编辑和知识获取软件&#xff0c;或者说是本体开发工具&#xff0c;也是基于知识的编辑器&#xff0c;属于开放源代码软件。 这个软件主要用于语义网中本体的构建&#xff0c;是语义…

第15-02章:理解Class类并获取Class实例

我的后端学习大纲 我的Java学习大纲 1、Java反射机制原理图&#xff1a; 源代码通过Javac编译得到字节码文件&#xff0c;当我执行到new一个对象的时候&#xff0c;字节码文件会通过ClassLoader被加载&#xff0c;然后得到一个Class类对象&#xff0c;存放在堆中&#xff0c;加…

系统分析师10:知识产权与标准化

1 内容提要 保护范围与对象&#xff08;★★★★)保护期限&#xff08;★)知识产权人确定(★★★)侵权判断(★★★)标准的分类标准代号的识别 2 保护范围与对象 使用许可 按照被许可使用权的排他性强弱不同&#xff0c;可以将使用许可分为以下三种: ①独占使用许可-仅1个授权…

中国电子学会202406青少年软件编程(Python)等级考试试卷(一级)真题与解析

青少年软件编程(Python)等级考试试卷(一级) 分数:100题数:37 一、单选题(共25题,共50分) 1.在使用turtle绘制图形时,如果要控制小海龟移动到 x 坐标为 200,y 坐标为150 的位置,以下代码能够实现效果的是?( ) A. turtle.go(150, 200) B. turtle.go(200, 150) …

在线客服如何与呼叫系统结合使用?

以下是在线客服与呼叫系统结合使用的常见方式&#xff1a; 1.从客户接入角度 a多渠道整合&#xff1a; 将网站在线客服、手机APP在线客服、社交媒体平台&#xff08;如微信公众号、微博私信等&#xff09;以及呼叫系统的电话接入渠道整合在一个统一的平台上。例如&#xf…

HTTP跨域请求时为什么要发送options请求

跨域请求 浏览器同源策略同源策略一般限制Ajax网络请求&#xff0c;不能跨域请求server不会限制<link> <img> <script> <iframe> 加载第三方资源 JSONP实现跨域 <!-- aa.com网页 --> <script>window.onSuccess function(data) {consol…