Handler 细节分析

2023-11-08

Handler 常见问题分析


今天总结一下这个Handler 的一些常常被问的问题,有借鉴,也有总结。如果后面还有新的问题,会添加上去。

1. Handler 机制中涉及到那些类,各自的功能是什么

主要的类就4Handler Lopper MessageQueue Message 主要是这4个

  • Handler 的作用就是将Message对象发送到MessageQueue中,同时将自己的引用赋值给Message#target
  • Looper 的作用是将Message 对象从MessageQueue中取出来,并将其交给Handler#dispatchMessage(Message) 这里需要注意的是不是调用**Handler#handleMessage(Message)**方法。
  • MessageQueue的作用就是负责插入和取出Message
  • 这里的ThreadLocal 不在此记录,因为这是JDK 中本身自带的

2. 有哪些发送消息的方法

sendMessage(Message msg)
sendMessageDelayed(Message msg,long uptimeMillis)
sendMessageAtTime(Message msg,long when)
sendEmptyMessage(int what)
sendEmptyMessageDelayed(int what,long uptimeMillis)
sendEmptyMessageAtTime(int what,long when)
Post(Runnable r)
postDelayed(Runnable r,long uptomeMillis)

方法有好多重,其实归根结底就 sendMessageAtTime(Message msg , long when) 这一个

3. MessageQueue 中的 Message 是有序的吗?排序的依据是什么

  • 是有序的。Queue都是有序的 Set 才是无序的
  • 这里的排序依据是Message#when这个字段,表示一个相对时间,改值是由 MessageQueue#enqueueMessage(Message msg , Long when) 方法设置的。
final boolean enqueueMessage(Message msg, long when) {
     // ....
        boolean needWake;
        synchronized (this) {
            if (mQuiting) {
                RuntimeException e = new RuntimeException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                return false;
            }

            msg.when = when;
            Message p = mMessages;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
        }
        if (needWake) {
            nativeWake(mPtr);
        }
        return true;
    }

  • 当两个 Message#when 一致时插入序按先后顺序

4. Message#when 是指的是什么

Message#when 是一个时间,用于表示 Message 期望被分发的时间,该值是SystemClock#uptimeMillis()delayedMillis 之和

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

SystemClock#uptimeMillis() 是一个表示当前时间的一个相对时间,它代表的是 自系统启动开始从0开始的到调用该方法时相差的毫秒数 。

5. Message#when 为什么不用System.currentTimeMillis() 来表示

/**
     * Returns the current system time in milliseconds since January 1, 1970
     * 00:00:00 UTC. This method shouldn't be used for measuring timeouts or
     * other elapsed time measurements, as changing the system time can affect
     * the results.
     *
     * @return the local system time in milliseconds.
     */
    public static native long currentTimeMillis();

System.currentTimeMillis() 表示的是从1970-01-01 00:00:00 到当前时间的毫秒数值,我们可以通过修改系统时间达到修改改值的目的,所以改值是不可靠的值。 可能会导致 延迟消息失效
同时 Message#when 只是用 时间差 来表示先后关系,所以只需要一个相对时间就可以达到目的,它可以是从系统启动开始计时的,也可以是从APP启动开始计时的,甚至可以定期重置的,(所有消息都减去同一个值,不过这样就复杂了没有必要)。

6. 子线程中可以创建Handler 对象吗?

  • 先调用Looper.prepare() 在当前进程中初始化一个Looper
Looper.prepare();
Handler handler = new Handler();
// ....
// 这一步可别少了
Looper.loop();
  • 通过构造的形式传入一个Looper
Looper looper = .....;
Handler handler = new Handler(looper);

7. Handler 是如何与 Looper 进行关联的

  • 通过构造方法传参
  • 直接调用无参构造方法 其里面有一个自动绑定的过程 mLooper = Looper.myLooper();
   public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper(); //就是在这里了
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

8. Looper 是如何与Thread 关联的

这里是通过 ThreadLocal 进行关联的,这个可以从 Looper#prepare() 方法看出来

 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
 //....
  private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

Looper 中有一个 ThreadLocal 类型的 sThreadLocal静态字段,Looper通过它的 getset 方法来赋值和取值。

public static Looper myLooper() {
        return sThreadLocal.get();
    }
...
private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

由于 ThreadLocal是与线程绑定的,所以我们只要把 LooperThreadLocal 绑定了,那 LooperThread 也就关联上了

9. Handler 有哪些构造方法

Handler 可以传的参数仅有两种 LooperHandler#Callback
那么就可以推算出一共有多少种构造方法了 一共四种

  • 无参
  • Looper
  • Callback
  • LooperCallback
public Handler() {
    this(null, false);
}
public Handler(Callback callback) {
    this(callback, false);
}
public Handler(Looper looper) {
    this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}

还有一个隐藏的 boolean 的async ,不过这个不是开放的API

10. 在子线程中如何获取当前线程的 Looper

Looper.myLooper()

内部原理就是同过上面提到的 sThreadLocal#get() 来获取 Looper

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

11. 如果在任意线程获取主线程的 Looper

Looper.getMainLooper()

12.如何判断当前线程是不是主线程

  1. Looper.myLooper() == Looper.getMainLooper()
  2. Looper.getMainLooper().getThread() == Thread.currentThread()
  3. Looper.getMainLooper().isCurrentThread()

13.Looper.loop() 会退出吗?

不会自动退出,但是我们可以通过 Looper#quit() 或者 Looper#quitSafely() 让它退出。

两个方法都是调用了 MessageQueue#quit(boolean) 方法,当 MessageQueue#next() 方法发现已经调用过 MessageQueue#quit(boolean) 时会 return null 结束当前调用,否则的话即使 MessageQueue 已经是空的了也会阻塞等待。

14.MessageQueue#next 在没有消息的时候会阻塞,如何恢复?

当其他线程调用 MessageQueue#enqueueMessage 时会唤醒 MessageQueue,这个方法会被 Handler#sendMessageHandler#post 等一系列发送消息的方法调用。

boolean enqueueMessage(Message msg, long when) {
    // ...
    synchronized (this) {
        // ...
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // ...
        }
        if (needWake) {
            nativeWake(mPtr); // 唤醒
        }
    }
    return true;
}

15. Looper.loop() 方法是一个死循环为什么不会阻塞APP

因为所有让我们会觉得的卡住的都被放到了MessageQueue 里面,然后通过Looper 取出并交给 Handler 进行执行了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Handler 细节分析 的相关文章

  • onBeaconServiceConnect 未调用

    和以前一样 我使用 Android Beacon 库 它已经工作了 我可以通过 BLE 低功耗蓝牙找到信标 但是现在 更新到最新版本的库后 现在方法onBeaconServiceConnect 不再跑了 请告诉我我需要做什么才能让它发挥作用
  • Twitter 登录说明

    我想在 Android 中创建一个 Twitter 应用程序 为此 我想创建一个登录页面并登录到 Twitter 为此 我们需要消费者密钥和消费者密钥 这是什么意思 要创建此登录页面 除了 Twitter 帐户之外 我们还需要其他任何东西吗
  • Android:使用 OAuth 访问 google 任务时出现问题

    由于 google 任务没有公共 api 我想编写解决方法并像浏览器一样请求数据 然后解析结果以进一步显示 为了访问数据 我使用 google 实现了 OAuth 身份验证来访问此 url https mail google com htt
  • 通过 WhatsApp 发送消息

    由于我发现了一些较旧的帖子 表明 Whatsapp 不支持此功能 我想知道是否发生了变化 以及是否有办法打开与我通过意图发送的号码进行 Whatsapp 聊天 UPDATE请参阅https faq whatsapp com en andro
  • 自定义选择器活动:SecurityException UID n 无权 content:// uri

    我正在构建一个选择器应用程序来替换本机 Android 共享对话框 它工作正常 除非我尝试通过长按图像 gt 共享图像从 Chrome 共享图像 我发现 Google 没有捕获异常 它崩溃了 所以我可以通过 Logcat 查看它 在 Goo
  • 带有一、二和三个按钮的 Android 警报对话框

    我不经常发出警报 但每次发出警报时 我都会花一些时间来阅读文档 https developer android com guide topics ui dialogs html并弄清楚如何去做 由于我现在不得不这样做几次 所以我将在下面写一
  • Service 和 IntentService,运行从服务器轮询数据库值的服务哪个更好?

    我读过很多关于Service and IntentService 然而 当做出决定时 我没有足够的信心选择使用哪种类型来创建一个后台服务 该服务将在一定时间间隔内从数据库轮询数据 并在获得所需数据时停止它 因为数据代表请求的状态 例如 订购
  • 安卓定位不准确

    我正在尝试获取当前用户的位置 我试图重构我的代码以获得更好的结果 但我只是不断得到关于准确度的荒谬位置 它在 900 600 米之间 如何才能得到更好的结果 使其精度达到50m以内 这是我的代码 package com agam mapsl
  • 播放 SoundCloud 曲目

    我可以在 Android 应用程序中播放 SoundCloud 中的曲目吗 我正在尝试这段代码 但它不起作用 String res https api soundcloud com tracks 84973999 stream client
  • 出现错误错误:res/menu/mainMenu.xml:文件名无效:必须仅包含[a-z0-9_。]

    我是安卓新手 刚刚开始使用 我在 res 文件夹中创建了一个文件 menu mainMenu xml 但我得到了错误 Error res menu mainMenu xml invalid file name must contain on
  • Camera.open()返回NULL Android开发

    我正在按照构建相机应用程序的教程进行操作http developer android com tools device html http developer android com tools device html我对 Camera o
  • glTexImage2D: 之前出错:( 0x506 内部 0x1908 格式 0x1908 类型 0x1401

    当使用 Android Studio 运行模拟器时 我在模拟器屏幕上看不到任何内容 一切都是黑色的 我得到以下事件日志 模拟器 glTexImage2D 出错了 0x506 内部 0x1908 格式 0x1908 类型 0x1401 我已经
  • 如何在TableLayout中创建三列

    我正在开发一个使用的屏幕TableLayout 在这里我可以轻松创建两列 但我怎样才能创建三列呢 这里有一个例子
  • Java 文件上传速度非常慢

    我构建了一个小型服务 它从 Android 设备接收图像并将其保存到 Amazon S3 存储桶中 代码非常简单 但是速度非常慢 事情是这样的 public synchronized static Response postCommentP
  • 使用 SQLITE 按最近的纬度和经度坐标排序

    我必须获得一个 SQLite SQL 语句 以便在给定初始位置的情况下按最近的纬度和经度坐标进行排序 这是我在 sqlite 数据库中的表的例句 SELECT id name lat lng FROM items EXAMPLE RESUL
  • 在游戏视图下添加 admob

    我一直试图将 admob 放在我的游戏视图下 这是我的代码 public class HoodStarGame extends AndroidApplication Override public void onCreate Bundle
  • 在 KitKat 4.4.2 中获取 SDard 路径和大小

    我在 Google Play 上有一个设备信息应用程序 在该应用程序中我有存储信息 我知道 Android 4 4 在访问外部 SD 卡方面发生了一些变化 内部似乎没有给我带来问题 我的问题是 如何可靠地获取 KitKat 上 SD 卡的大
  • Android 中循环事件的星期几和时间选择器

    我想创建一个控件 允许用户在我的 Android 活动中选择一周中的某一天 星期一 和一天中的某个时间 下午 1 00 找不到任何关于此的好帖子 好吧 我想我已经明白了 我只是不喜欢这个解决方案 因为我在一周中的某一天使用的微调器与时间选择
  • 如何在基本活动中使用 ViewBinding 的抽象?

    我正在创建一个基类 以便子级的所有绑定都将设置在基类中 我已经做到了这一点 abstract class BaseActivity2 b AppCompatActivity private var viewBinding B null pr
  • Android 材料芯片组件崩溃应用程序。无法膨胀 xml

    Tried Chip来自两个支持库的组件 com google android support design 28 0 0 rc01和材料 com google android material material 1 0 0 rc01 堆栈

随机推荐

  • 我在Hadoop云计算会议的演讲

    点击下载演讲稿 由中科院计算所主办的 Hadoop 中国2010云计算大会 于9月4日在北京召开 淘宝网作为国内最大的Hadoop应用商之一赞助与参与了这次会议 我有幸代表淘宝在大会上分享了淘宝在分布式数据处理实践的内容 下面是ppt的一个
  • STM32基础10--实时时钟(RTC)

    目录 前言 RTC框图 STM32实时时钟电路 功能需要 STM32CubeMx配置RTC 配置RCC 配置RTC 配置时间 闹钟 唤醒 开启中断 设置中断优先级 功能代码实现 STM32Cude生成RTC初始化 自定义触发闹钟次数变量 重
  • 淘宝分类导航条;纯css实现固定导航栏

    效果如下 页面如下
  • DELPHI代码

    unit Unit1 mode objfpc H interface uses Classes SysUtils FileUtil Forms Controls Graphics Dialogs ExtCtrls StdCtrls LazL
  • YY社招面试(java高级开发)

    k8s pod之间的通信 网络模式 健康检查 探针 CAP和base理论 分库分表策略 取模 一致性哈希 订单表怎么设计和分库分表 项目扩展性 高可用性 IOC AOP 跳表 redis内存模型 其他记不起来了 jvm内存 分布式锁 spr
  • AXI4-Stream协议的信号以及Xilinx提供的从AXI到AXI-Stream转换的IP核区别

    AXI4 Stream协议是一种用来连接需要交换数据的两个部件的标准接口 它可以用于连接一个产生数据的主机和一个接受数据的从机 当然它也可以用于连接多个主机和从机 该协议支持多种数据流使用相同共享总线集合 允许构建类似于路由 宽窄总线 窄宽
  • LaTex论文格式模板

    LaTex论文格式模板 效果如图 代码 使用的是工具是 VSCode texlive TEX program xelatex documentclass 12pt a4paper article 文档格式 usepackage ctex h
  • RSync详解

    简介 rsync remote synchronize 是一款实现远程同步功能的软件 它在同步文件的同时 可以保持原来文件的权限 时间 软硬链接等附加信息 rsync是用 rsync 算法 提供了一 个客户机和远程文件服务器的文件同步的快速
  • 大数据期末课设~基于spark的气象数据处理与分析

    目录 一 项目背景 3 二 实验环境 3 三 实验数据来源 4 四 数据获取 5 五 数据分析 17 六 数据可视化
  • 【论文精读AAAI_2022】MobileFaceSwap: A Lightweight Framework for Video Face Swapping

    论文精读AAAI 2022 MobileFaceSwap A Lightweight Framework for Video Face Swapping 一 前言 Abstract Introduction Related Work Fac
  • Using a debugger

    Java IDE 中最有用的特性之一就是它们的 debuggers 它可以接入到运行着你的应用的JVM中 允许你在任何位置暂停代码的执行 以便检查应用的状态 要调试 Play 应用 需要将其以 debug 模式启动 然后把你的 debugg
  • FastDFS集群部署和同步机制

    如何选择tracker 当集群中有多个tracker server时 由于tracker之间是对等的关系 客户端在upload文件时可以任意选择一个tracker 如何选择storage 当选定group后 tracker会在group内选
  • 面部五官迁移算法(Python)

    面部器官互换指的是 将一个人的面部器官换到另一个人的脸上 比如将A的眼睛换到B的眼睛上 算法的实现技术要点为 关键点检测 人脸对齐 mask制作 色差矫正 mask融合 关键点检测 是使用的dlib81个关键点模型 人脸对齐是基于放射变换做
  • 爬虫项目七:Python对唯品会商品数据、评论数据的爬取

    文章目录 前言 一 商品数据 1 分析页面 2 分析url 3 解析数据 二 评论数据 1 抓包 2 分析url 3 获取数据 三 总结 前言 用Python爬取唯品会商品数据 评论数据 提示 以下是本篇文章正文内容 下面案例可供参考 一
  • binascii模块常用四个方法介绍

    python代码中处理加解密时 可能你会用到base64模块来进行编码 而base64模块实际上又去调用binascii模块 那么binascii模块中最常用的四个方法 函数 是哪些呢 具体什么用途呢 介绍四个方法之前 先说明一点 bina
  • 开源游戏引擎介绍

    2D Allegro cc Main http www allegro cc 老牌子了 和SDL同时是很经典两个EG开发组件 最近貌似在和PY进行联合 ClanLib ClanLib Game SDK http www clanlib or
  • erp服务器配置

    erp服务器配置 如果是做ERP服务器的话 推荐用双路四核的 这样比较有扩展性 如果以后客户端数量增加了或者数据库文件越跑越大 对性能要求增加 双路服务器的扩展性优势就出来了 你可以看看国产品牌正睿的这款最新SNB E架构的双路四核服务器
  • 【华为OD机试python】拔河比赛【2023 B卷

    华为OD机试 真题 点这里 华为OD机试 真题考点分类 点这里 题目描述 公司最近准备进行拔河比赛 需要在全部员工中进行挑选 选拔的规则如下 按照身高优先 体重次优先的方式准备比赛阵容 规定参赛的队伍派出10名选手 请实现一个选拔队员的小程
  • 汕头刀片服务器维修,刀片服务器怎么拆外壳

    刀片服务器怎么拆外壳你知道吗 下面学习啦小编为大家整理了刀片服务器内部结构详解的内容 欢迎参阅 刀片服务器拆外壳的方法 首先是龙芯刀片CB50 A的机箱 乍看起来很眼熟 原来是曙光TC2600刀片服务器机箱 熟悉的朋友知道 著名的超级计算机
  • Handler 细节分析

    Handler 常见问题分析 1 Handler 机制中涉及到那些类 各自的功能是什么 2 有哪些发送消息的方法 3 MessageQueue 中的 Message 是有序的吗 排序的依据是什么 4 Message when 是指的是什么