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

相关文章

IntelliJ IDEA2025创建SpringBoot项目的实现步骤

《IntelliJIDEA2025创建SpringBoot项目的实现步骤》本文主要介绍了IntelliJIDEA2025创建SpringBoot项目的实现步骤,文中通过示例代码介绍的非常详细,对大家... 目录一、创建 Spring Boot 项目1. 新建项目2. 基础配置3. 选择依赖4. 生成项目5.

nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析(结合应用场景)

《nginx-t、nginx-sstop和nginx-sreload命令的详细解析(结合应用场景)》本文解析Nginx的-t、-sstop、-sreload命令,分别用于配置语法检... 以下是关于 nginx -t、nginx -s stop 和 nginx -s reload 命令的详细解析,结合实际应

Linux线程之线程的创建、属性、回收、退出、取消方式

《Linux线程之线程的创建、属性、回收、退出、取消方式》文章总结了线程管理核心知识:线程号唯一、创建方式、属性设置(如分离状态与栈大小)、回收机制(join/detach)、退出方法(返回/pthr... 目录1. 线程号2. 线程的创建3. 线程属性4. 线程的回收5. 线程的退出6. 线程的取消7.

MyBatis中$与#的区别解析

《MyBatis中$与#的区别解析》文章浏览阅读314次,点赞4次,收藏6次。MyBatis使用#{}作为参数占位符时,会创建预处理语句(PreparedStatement),并将参数值作为预处理语句... 目录一、介绍二、sql注入风险实例一、介绍#(井号):MyBATis使用#{}作为参数占位符时,会

创建Java keystore文件的完整指南及详细步骤

《创建Javakeystore文件的完整指南及详细步骤》本文详解Java中keystore的创建与配置,涵盖私钥管理、自签名与CA证书生成、SSL/TLS应用,强调安全存储及验证机制,确保通信加密和... 目录1. 秘密键(私钥)的理解与管理私钥的定义与重要性私钥的管理策略私钥的生成与存储2. 证书的创建与

PostgreSQL的扩展dict_int应用案例解析

《PostgreSQL的扩展dict_int应用案例解析》dict_int扩展为PostgreSQL提供了专业的整数文本处理能力,特别适合需要精确处理数字内容的搜索场景,本文给大家介绍PostgreS... 目录PostgreSQL的扩展dict_int一、扩展概述二、核心功能三、安装与启用四、字典配置方法

深度解析Java DTO(最新推荐)

《深度解析JavaDTO(最新推荐)》DTO(DataTransferObject)是一种用于在不同层(如Controller层、Service层)之间传输数据的对象设计模式,其核心目的是封装数据,... 目录一、什么是DTO?DTO的核心特点:二、为什么需要DTO?(对比Entity)三、实际应用场景解析

深度解析Java项目中包和包之间的联系

《深度解析Java项目中包和包之间的联系》文章浏览阅读850次,点赞13次,收藏8次。本文详细介绍了Java分层架构中的几个关键包:DTO、Controller、Service和Mapper。_jav... 目录前言一、各大包1.DTO1.1、DTO的核心用途1.2. DTO与实体类(Entity)的区别1

Java中的雪花算法Snowflake解析与实践技巧

《Java中的雪花算法Snowflake解析与实践技巧》本文解析了雪花算法的原理、Java实现及生产实践,涵盖ID结构、位运算技巧、时钟回拨处理、WorkerId分配等关键点,并探讨了百度UidGen... 目录一、雪花算法核心原理1.1 算法起源1.2 ID结构详解1.3 核心特性二、Java实现解析2.

使用Python绘制3D堆叠条形图全解析

《使用Python绘制3D堆叠条形图全解析》在数据可视化的工具箱里,3D图表总能带来眼前一亮的效果,本文就来和大家聊聊如何使用Python实现绘制3D堆叠条形图,感兴趣的小伙伴可以了解下... 目录为什么选择 3D 堆叠条形图代码实现:从数据到 3D 世界的搭建核心代码逐行解析细节优化应用场景:3D 堆叠图