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

相关文章

python常见环境管理工具超全解析

《python常见环境管理工具超全解析》在Python开发中,管理多个项目及其依赖项通常是一个挑战,下面:本文主要介绍python常见环境管理工具的相关资料,文中通过代码介绍的非常详细,需要的朋友... 目录1. conda2. pip3. uvuv 工具自动创建和管理环境的特点4. setup.py5.

全面解析HTML5中Checkbox标签

《全面解析HTML5中Checkbox标签》Checkbox是HTML5中非常重要的表单元素之一,通过合理使用其属性和样式自定义方法,可以为用户提供丰富多样的交互体验,这篇文章给大家介绍HTML5中C... 在html5中,Checkbox(复选框)是一种常用的表单元素,允许用户在一组选项中选择多个项目。本

Python包管理工具核心指令uvx举例详细解析

《Python包管理工具核心指令uvx举例详细解析》:本文主要介绍Python包管理工具核心指令uvx的相关资料,uvx是uv工具链中用于临时运行Python命令行工具的高效执行器,依托Rust实... 目录一、uvx 的定位与核心功能二、uvx 的典型应用场景三、uvx 与传统工具对比四、uvx 的技术实

Python中使用uv创建环境及原理举例详解

《Python中使用uv创建环境及原理举例详解》uv是Astral团队开发的高性能Python工具,整合包管理、虚拟环境、Python版本控制等功能,:本文主要介绍Python中使用uv创建环境及... 目录一、uv工具简介核心特点:二、安装uv1. 通过pip安装2. 通过脚本安装验证安装:配置镜像源(可

SpringBoot排查和解决JSON解析错误(400 Bad Request)的方法

《SpringBoot排查和解决JSON解析错误(400BadRequest)的方法》在开发SpringBootRESTfulAPI时,客户端与服务端的数据交互通常使用JSON格式,然而,JSON... 目录问题背景1. 问题描述2. 错误分析解决方案1. 手动重新输入jsON2. 使用工具清理JSON3.

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

Redis过期删除机制与内存淘汰策略的解析指南

《Redis过期删除机制与内存淘汰策略的解析指南》在使用Redis构建缓存系统时,很多开发者只设置了EXPIRE但却忽略了背后Redis的过期删除机制与内存淘汰策略,下面小编就来和大家详细介绍一下... 目录1、简述2、Redis http://www.chinasem.cn的过期删除策略(Key Expir

Go学习记录之runtime包深入解析

《Go学习记录之runtime包深入解析》Go语言runtime包管理运行时环境,涵盖goroutine调度、内存分配、垃圾回收、类型信息等核心功能,:本文主要介绍Go学习记录之runtime包的... 目录前言:一、runtime包内容学习1、作用:① Goroutine和并发控制:② 垃圾回收:③ 栈和

Spring组件实例化扩展点之InstantiationAwareBeanPostProcessor使用场景解析

《Spring组件实例化扩展点之InstantiationAwareBeanPostProcessor使用场景解析》InstantiationAwareBeanPostProcessor是Spring... 目录一、什么是InstantiationAwareBeanPostProcessor?二、核心方法解

深入解析 Java Future 类及代码示例

《深入解析JavaFuture类及代码示例》JavaFuture是java.util.concurrent包中用于表示异步计算结果的核心接口,下面给大家介绍JavaFuture类及实例代码,感兴... 目录一、Future 类概述二、核心工作机制代码示例执行流程2. 状态机模型3. 核心方法解析行为总结:三