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实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

idea中创建新类时自动添加注释的实现

《idea中创建新类时自动添加注释的实现》在每次使用idea创建一个新类时,过了一段时间发现看不懂这个类是用来干嘛的,为了解决这个问题,我们可以设置在创建一个新类时自动添加注释,帮助我们理解这个类的用... 目录前言:详细操作:步骤一:点击上方的 文件(File),点击&nbmyHIgsp;设置(Setti

Python 中的异步与同步深度解析(实践记录)

《Python中的异步与同步深度解析(实践记录)》在Python编程世界里,异步和同步的概念是理解程序执行流程和性能优化的关键,这篇文章将带你深入了解它们的差异,以及阻塞和非阻塞的特性,同时通过实际... 目录python中的异步与同步:深度解析与实践异步与同步的定义异步同步阻塞与非阻塞的概念阻塞非阻塞同步

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

Spring MVC使用视图解析的问题解读

《SpringMVC使用视图解析的问题解读》:本文主要介绍SpringMVC使用视图解析的问题解读,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring MVC使用视图解析1. 会使用视图解析的情况2. 不会使用视图解析的情况总结Spring MVC使用视图

Spring 中使用反射创建 Bean 实例的几种方式

《Spring中使用反射创建Bean实例的几种方式》文章介绍了在Spring框架中如何使用反射来创建Bean实例,包括使用Class.newInstance()、Constructor.newI... 目录1. 使用 Class.newInstance() (仅限无参构造函数):2. 使用 Construc

利用Python和C++解析gltf文件的示例详解

《利用Python和C++解析gltf文件的示例详解》gltf,全称是GLTransmissionFormat,是一种开放的3D文件格式,Python和C++是两个非常强大的工具,下面我们就来看看如何... 目录什么是gltf文件选择语言的原因安装必要的库解析gltf文件的步骤1. 读取gltf文件2. 提

Java中的runnable 和 callable 区别解析

《Java中的runnable和callable区别解析》Runnable接口用于定义不需要返回结果的任务,而Callable接口可以返回结果并抛出异常,通常与Future结合使用,Runnab... 目录1. Runnable接口1.1 Runnable的定义1.2 Runnable的特点1.3 使用Ru

Spring 中 BeanFactoryPostProcessor 的作用和示例源码分析

《Spring中BeanFactoryPostProcessor的作用和示例源码分析》Spring的BeanFactoryPostProcessor是容器初始化的扩展接口,允许在Bean实例化前... 目录一、概览1. 核心定位2. 核心功能详解3. 关键特性二、Spring 内置的 BeanFactory