Handler 源码解析——Handler的创建

2024-08-29 14:58
文章标签 源码 创建 解析 handler

本文主要是介绍Handler 源码解析——Handler的创建,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

Android 提供了Handler和Looper来来满足线程间的通信,而前面我们所说的IPC指的是进程间的通信。这是两个完全不同的概念。

Handler先进先出原则,Looper类用来管理特定线程内消息的交换(MessageExchange);

1、为什么会有Handler机制?

我们刚说Handler机制的主要作用是将某一任务切换到特定的线程来执行,我们做项目可能都遇到过ANR(Application Not Response),这就是因为执行某项任务的时间太长而导致程序无法响应。这种情况我们就需要将这项耗时较长的任务移到子线程来执行,从而消除ANR。而我们都知道Android规定访问UI只能在主线程中进行,如果在子线程中访问UI,那么程序就会抛出异常。而Android提供Handler就是为了解决在子线程中无法访问UI的矛盾。

2、Handler源码解析

子线程中创建Handler为啥会报错?

首先,我们先看一个例子,我们在子线程中创建一个Handler。

  new Thread(new Runnable() {@Overridepublic void run() {new Handler(){@Overridepublic void handleMessage (Message message){super.handleMessage(message);}};}},"MyThread").start();

我们运行时会发现,会抛出异常:Can't create handler inside thread that has not called Looper.prepare()

  java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()at android.os.Handler.<init>(Handler.java:204)at android.os.Handler.<init>(Handler.java:118)at com.example.bthvi.myconstrainlayoutapplication.MainActivity$1$1.<init>(MainActivity.java:21)at com.example.bthvi.myconstrainlayoutapplication.MainActivity$1.run(MainActivity.java:21)at java.lang.Thread.run(Thread.java:764)

究竟是为什么会抛出异常呢?下面我们通过Handler源码来看看。

Handler的构造方法

当我们创建Handler对象的时候调用的是下面的方法:

    /*** Default constructor associates this handler with the {@link Looper} for the* current thread.** If this thread does not have a looper, this handler won't be able to receive messages* so an exception is thrown.*/public Handler() {this(null, false);}

我们看到注释中说:++默认的构造函数将这个Handler与当前的线程的Looper关联,如果当前线程没有Looper,那么这个程序将无法接收消息,因此会抛出异常。++ 究竟是怎么抛出异常的呢?我们继续往下看:

    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();//注释1if (mLooper == null) {throw new RuntimeException(//注释2"Can't create handler inside thread that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;}

我们看到在这里开始有个if语句,由于FIND_POTENTIAL_LEAKS默认值是false所以我们不需要去管它,注释1处,这里调用了Looper.myLooper(),我们看看它的源码:

Looper.myLooper()

    /*** Return the Looper object associated with the current thread.  Returns* null if the calling thread is not associated with a Looper.*/public static @Nullable Looper myLooper() {return sThreadLocal.get();}

这个方法的作用就是返回与当前线程相关连的Looper对象。这里又调用了ThreadLocal.get(),

ThreadLocal

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后只有在特定的线程中可以获取到存储的数据,对于其他线程来说则无法获取到。ThreadLocal用一句大白话来讲解,++就是看上去只new了一份,但在每个不同的线程中却可以拥有不同数据副本的神奇类。++ 其本质是ThreadLocal中的Values类维护了一个Object[],而每个Thread类中有一个ThreadLocal.Values成员,当调用ThreadLocal的set方法时,其实是根据一定规则把这个线程中对应的ThreadLocal值塞进了Values的Object[]数组中的某个index里。这个index总是为ThreadLocal的reference字段所标识的对象的下一个位置。
下面我们来看它的get方法。

ThreadLocal.get()

    public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}

在该方法的第二行调用了getMap方法。就是去获取当前线程的ThreadLocalMap对象。但是这个对象是在什么时候创建的呢?

ThreadLocalMap getMap(Thread t) {return t.threadLocals;}

因为抛出异常了所以我们猜测这个值可能是null。说到这里可能有些迷茫,我们回头看看注释1处那里紧接着,我们看到如果获取到的myLooper为空(至于为什么会返回为空我们看完后面回过头来看就很明白了)的的话,就会抛出我们前面看到的异常。

        ......mLooper = Looper.myLooper();//注释1if (mLooper == null) {throw new RuntimeException(//注释2"Can't create handler inside thread that has not called Looper.prepare()");}......

异常中说如果在子线程中创建Handler必须要调用Looper.prepare()方法,那么我们想肯定是在调用该方法的时候做了一些操作,可能跟后面消息的接收和处理相关,通过后面的源码我们会发现其实这个方法是对当前线程创建一个Looper对象,我们来看源码:

Looper.prepare()

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

我们调用是没有传参的prepare,他会调用内部的传参的prepare方法。我们看到注释3处又调用了ThreadLocal.get(),假设我们第一次调用Looper.prepare(),那么这个值肯定是空的。如果不是空的话前面Looper.myLooper()就不会为空,也就不会抛出异常了。(那么当在一个线程中第二次调用该方法的时候,他的返回值就不会是空,系统会抛出异常一个线程只能创建一个Looper。也就是说一个子线程中Looper.prepare()只能调用一次。)所以这里肯定走ThreadLocal.set()方法,并且新建了一个Looper对象作为入参:

ThreadLocal.set()

    public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}

我们看到第二行还是调用了getMap由于第一次调用,所以这个返回值还是空的。所以应该走了createMap(),下面我们看看它的源码:

  void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}

我们看到这里才对当前线的ThreadLocal进行了赋值。这个方法中我们看到ThreadLocal已Map的结构存储了当前线程对应的Looper。以线程为Entry也就是Key,以Looper为Value。我们回过来再看,Looper.myLooper()。

   public static @Nullable Looper myLooper() {return sThreadLocal.get();}public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);//注释4if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}

其实发现他就是去获取当前线程的Looper。当没有调用Looper.prepare()来创建Looper时,当前线程的ThreadLocalMap对象为空,所以前面的ThreadLocal.get()方法会调用setInitialValue这个方法,

    private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;}

我们看到这个方法返回了value,value是通过一个方法返回的,下面我们看看这个方法:

 protected T initialValue() {return null;}

这个方法单纯的就是返回null,所以也就是相当于ThreadLocal.get()返回null===>Looper.myLooper()返回null。所以Handler会在注释2处抛出异常。

总结

回过头来,我们仔细想想为什么会抛出异常来?就是因为:
Handler对象是基于Looper的,每个Handler必须有一个Looper,这个Looper是在调用Looper.prepare()的时候创建的,这个Looper会以Map的形式存储在当前线程的ThreadLocal中。当当掉用Looper.myLooper()方法就是去在当前线程的ThreadLocal中拿到当前线程的Looper对象,没有拿到就会抛出异常。

其他文章

1、Gradle

gradle 详解——你真的了解Gradle吗?

一分钟帮你提升Android studio 编译速度

2、Flutter

Flutter从入门到实战

3、源码

深入理解HashMap原理(一)——HashMap源码解析(JDK 1.8)

深入理解HashMap原理(二)——手写HashMap

Handler 源码解析——Handler的创建

4、热修复

Android学习——手把手教你实现Android热修复

Android热修复——深入剖析AndFix热修复及自己动手实现

手撸一款Android屏幕适配SDK
Android自定义无压缩加载超清大图

这篇关于Handler 源码解析——Handler的创建的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1118190

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

顺序表之创建,判满,插入,输出

文章目录 🍊自我介绍🍊创建一个空的顺序表,为结构体在堆区分配空间🍊插入数据🍊输出数据🍊判断顺序表是否满了,满了返回值1,否则返回0🍊main函数 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞+关注+评论+收藏(一键四连)哦~ 🍊自我介绍   Hello,大家好,我是小珑也要变强(也是小珑),我是易编程·终身成长社群的一名“创始团队·嘉宾”