Android线程间通信Handler源码详解
作者:Quincy_Ye 发布时间:2021-12-31 07:04:10
前言
在【Android】线程间通信 - Handler之使用篇主要讲了 Handler 的创建,发送消息,处理消息 三个步骤。那么接下来,我们也按照这三个步骤,从源码中去探析一下它们具体是如何实现的。本篇是关于创建源码的分析。
01、 用法
先回顾一下,在主线程和非主线程是如何创建 Handler 的。
//主线程
private val mHandler: Handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
1 -> {}
}
}
}
//子线程
Thread {
Looper.prepare()
val handler = object : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
1 -> {}
}
}
}
handler.sendEmptyMessage(1)
Looper.loop()
}.start()
02、源码
Handler 一共有 7 个构造方法。但最后都会直接或间接使用到以下两个构造方法。所以我们看看两个方法都做了什么事情吧。
//方法 1
//Handler.java
public Handler(@Nullable 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(); \\标识 1
if (mLooper == null) { \\ 标识 3
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue; \\标识 2
mCallback = callback;
mAsynchronous = async;
}
//方法 2
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
方法 2 更加的简单,那么我们就从它入手吧。在其中对 4 个变量进行赋值。分别是 mLooper, mQueue, mCallback, mAsynchronous
。方法 1 主要也是对 4 个变量进行赋值。它们有什么作用,我们先不管,后面会讲到。我们先来看看这方法 1 和方法 2 的区别是什么?
让我们聚焦到标识 1 和标识 2,mLooper, mQueue 来源是通过 Looper 这个类中获取的。那让我们跟进去看看。
//跟进标识 1
//Looper.java
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
看到是通过 mThreadLocal.get()
获得一个 Looper 实例。那么 mThreadLocal
的 Looper 又是哪里来的呢?让我们找找 mThreadLocal.set()
方法,就知道了!
//Looper.java
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.prepare()
中添加的。
这里需要注意!
"Only one Looper may be created per thread",每个线程只能调用一次
prepare()
,这也就意味着每个线程只有一个 Looper 。
可是看回在主线程中创建 Handler 的时候,我们并没有调用 Looper.prepare()
方法。但是也正常运行了。那是为什么呢?那是因为在 ActivityThread 中的 main()
已经调用了。
//ActivityThread.java
public static void main(String[] args) {
//...省略无关代码
Looper.prepareMainLooper(); \\标识 4
//...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//...
Looper.loop();
}
//跟进标识 4
//Looper.java
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
这里需要注意!
我们不仅不用调用,也不能调用。否则将会触发
IllegalStateException("The main Looper has already been prepared.")
异常。但是如果不是为主线程创建 Handler 的时候,我们就需要手动调用
Looper.prepare()
, 否则该线程中的 Looper 为空,会触发标识 3 的代码块。即会得到
RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()")
异常。
03、结语
图源 |《第一行代码》
最后结合《第一行代码》中异步消息处理机制的流程图。我们可以看出 Handler 创建过程主要是准备好了 Looper, MessageQueue 和 Handler 本身。
来源:https://juejin.cn/post/7155791766631743524
猜你喜欢
- 下面我们来探究Android如何实现关机,重启;在Android中这种操作往往需要管理员级别,或者rootAndroid实现的方式如下几种:
- 问题项目是springcloud项目,在maven install某一个项目时报错:程序包com.example.commons.appli
- 本文实例讲述了C#简单输出日历的方法。分享给大家供大家参考。具体如下:用C#输出日历,此功能可用于Ajax方式列出计划日程相关的内容,由于是
- IO流Java中IO流分为两种,字节流和字符流,顾名思义字节流就是按照字节来读取和写入的,字符刘是按照字符来存取的;常用的文件读取用的就是字
- 下载地址:https://www.jb51.net/database/588158.html?_=1522396455592运行程序,关闭工
- 线程池主要解决两个问题:一是当执行大量异步任务时线程池能够提供较好的性能。在不使用线程池时,每当需要执行异步任务时直接new一个线程来运行,
- 百度百科说法:Servlet(Server Applet)是Java Servlet的简称,称为小服务程序或服务连接器,用Java编写的服务
- jar包就指第三方提供的开源的API,这些API不属于JDK的,需要通过导入才能使用。添加和导入的区别注意:本文里的 导入 和 添加 jar
- 近几天又温习了一下SpringMVC的运行机制以及原理我理解的springmvc,是设计模式MVC中C层,也就是Controller(控制)
- 有时安全不得不考虑,看看新闻泄漏风波事件就知道了我们在用Spring boot进行开发时,经常要配置很多外置参数ftp、数据库连接信息、支付
- protobuf 是 google的一个开源项目,可用于以下两种用途:(1)数据的存储(序列化和反序列化),类似于xml、json等;(2)
- 如何只返回实体类中的部分字段在实体类上添加注解@JsonInclude(JsonInclude.Include.NON_EMPTY)表示实体
- 一、java final基本概念:1、主要用于修饰类、属性和方法:被final修饰的类不可以被继承被final修饰的方法不可以被重写被fin
- 使用Spring data JPA开发已经有一段时间了,这期间学习了一些东西,也遇到了一些问题,在这里和大家分享一下。前言:Spring d
- 前言首先,事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行扩展,以及提供了一些能让程序员更新方便操作事务的方式Spring
- 代码背景一个班级,有两类学生,A类:不学习,玩,但是玩的东西不一样,有的是做游戏,有的是看电视B类:放哨的学生,专门看老师的动向,如果老师进
- 线程的同步是保证多线程安全访问竞争资源的一种手段。线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源、什么时候需要考虑同
- import java.io.*;import java.text.SimpleDateFormat;import java.util.*;
- 什么是异步调用?异步调用是相对于同步调用而言的,同步调用是指程序按预定顺序一步步执行,每一步必须等到上一步执行完后才能执行,异步调用则无需等
- 本文实例讲述了Java实现的上传并压缩图片功能。分享给大家供大家参考,具体如下:先看效果:原图:1.33M处理后:27.4kb关键代码:pa