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


猜你喜欢
- 在有些产品的研发过程中,一般我们都有很多条数据记录在一个LOG文件中。在查看最新的数据记录都是从最开始保存的那条开始存储,所以,参考了网上一
- 参考文章Android中实时获取音量分贝值详解:https://www.jb51.net/article/64806.htmpublic c
- 前言使用Java8的新特性Stream流式处理,可以提高对于集合的一些操作效率,再配合lambda表达式,可以极致的简化代码,尤其还有并行流
- java数据类型与二进制详细介绍在java中Int 类型的变量占 4个字节Long 类型的变量占8个字节一个程序就是一个世界,变量是这个程序
- 通过邮件找回密码功能的实现1、最近开发一个系统,有个需求就是,忘记密码后通过邮箱找回。现在的系统在注册的时候都会强制输入邮箱,其一目的就是
- 概述 ScrollView也是一个容器,它是FrameLayout的子类,它的主要作用就是将超出物理屏幕的内容显示
- 1.对原生态jdbc程序中问题总结1.1 jdbc程序需求:使用jdbc查询mysql数据库中用户表的记录statement:向数据库中发送
- 一、 看效果二、上代码package com.framework.widget;import android.app.Activity;im
- 一、java端首先我使用的是java自带的对webservice的支持包来编写的服务端和发布程序,代码如下。webservice的接口代码:
- 题目:若希望循环队列中的元素都能得到利用,则需设置一个标志域tag,并以tag的值为0或1来区分队头指针front和队尾指针rear相同时的
- TCP/IP、UDP、Socket对TCP/IP、UDP、Socket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵。
- 本文实例为大家分享了Android系统工具类的具体代码,供大家参考,具体内容如下系统工具类public class systemUtil {
- 1.浅复制与深复制概念 ⑴浅复制(浅克隆) 被复制对象的所有变量都含有与原来的对象相同的值,
- 本篇文章介绍selenium 操作浏览器阅读目录浏览器最大化 前进,后退, 刷新截图操作模拟鼠标操作杀掉Windows浏览器进程浏览器最大化
- FFmpeg是一个开源免费跨平台的视频和音频流方案,属于自由软件,采用LGPL或GPL许可证(依据你选择的组件)。它提供了录制、转换以及流化
- 1. 概述官方JavaDocsApi: java.awt.FlowLayoutFlowLayout,流式布局管理器。按水平方向依次排列放置组
- java获取文件的inode标识符,如果文件被删除或者重命名,inode的值会发生变更,因此可以在第一次加载File之后记录inode,后续
- 目的了解ReentrantLock获取锁、释放锁的流程代码package com.company.aqs;import java.util.
- 一、什么是桥接模式桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又
- 使用Myeclipse搭建maven项目准备工作安装maven官网下载安装(http://maven.apache.org/)配置环境变量配