当前位置: 首页 > news >正文

【Android】从源码角度理解Handler机制

【Android】从源码角度理解Handler机制

文章目录

  • 【Android】从源码角度理解Handler机制
    • 1. Handler简介
      • 一个线程可以有多少个Looper?有多少个MessageQueue?
      • 为什么主线程不会因为Looper阻塞?
    • 2. Handler的基本用法
    • 3. Handler机制解析
      • 3.1 Looper的创建
      • 3.2 Handler与线程的关联
      • 3.3 Looper.loop()
      • 3.4 线程的切换
      • 3.5 小结
    • 4. Handler的基本使用
    • 5. 总结

1. Handler简介

Handler通常用来做主线程与子线程之间的通信工具。比如,在子线程中进行耗时操作(网络请求、数据库操作等),子线程在获取数据后,使用Handler来进行UI更新:子线程拿到数据后,不直接更新界面,而是把数据通过一个消息(Message)发送出去;主线程这边,会提前准备好一个“接收器”——Handler对象。这个Handler对象会专门接受子线程发送来的数据,把获取到的数据显示在界面上。

接下来介绍Handler机制的四个组成部分:

  • Handler:消息的发送者和处理者

  • MessageQueue:消息队列,存储待处理的消息

  • Looper:消息循环器,不断从队列中取出消息并分发,同时负责关联线程

  • Message:消息载体,包含数据和目标 Handler

你可以把以上四个部分想象成一条生产线的不同部分:

  • Message就是待加工的产品,线程之间要传递的消息就记录在这上面;

  • Handler就像一个生产线上的工人,线程要发送消息,就通过Handler的sendMessage()方法发送出去;收到产品后,也是由Handler(目标Handler)handleMessage方法读取并处理该产品。

  • MessageQueue就像一条流水线上的传送带,所有发出去的”产品“都会先放在这里,排着队等待被加工处理。每个线程有且只有一条自己的“传送带”

  • Looper这个角色就有点像线长,负责管理每条流水线,它会不断检查(通过执行loop方法进入一个无限循环)传送带上有没有新“产品”。一旦发现有,它就会把产品取出来,然后递给对应的Handler(工人)去处理。

简单来说,就是线程A加工了一件“产品”(Message),交给自己的“工作人员”(Handler)发出去,这件产品会先放在“传送带”(MessageQueue)上。然后“线长”(Looper)会不停地检查传送带,一看到有产品,就把它取出来,交给对应的“工人”(Handler)去加工处理。这样,线程之间就能顺利地传递信息了。

在这里插入图片描述

一个线程可以有多少个Looper?有多少个MessageQueue?

一个独立的Thread最多只能拥有一个Looper实例,并且每个Looper都与其唯一的MessageQueue紧密关联。因此,如果一个Thread被配置为运行消息循环(即它拥有一个Looper),那么它将精确地拥有一个Looper和一个MessageQueue。这种一对一的映射是Android健壮的异步处理模型的基础,确保了线程间通信的可预测性和安全性,尤其对于至关重要的主(UI)线程而言。

为什么主线程不会因为Looper阻塞?

  • Android 主线程的 Looper.loop()在消息空闲时会通过 epoll进入休眠状态
  • 当有新消息(如触摸事件、Handler消息)时会被 native 层唤醒

2. Handler的基本用法

Handler handler = new Handler(){@Overridepublic void handleMessage(final Message msg) {//这里接受并处理消息}
};
//发送消息
handler.sendMessage(message);// 传递数据
handler.post(runnable);// 执行代码 

实例化一个Handler重写handleMessage()方法,然后在需要的地方调用send以及post系列方法就行了,非常简单易用,同时支持延时消息。

3. Handler机制解析

一个普通线程如果使用Handler,那么完整的示例应该是这样的:

class LooperThread extends Thread {public Handler mHandler;public void run() {Looper.prepare();mHandler = new Handler() {public void handleMessage(Message msg) {// process incoming messages here}};Looper.loop();}

我们可以通过这个示例,一步步理解Handler到底是怎么实现的。

3.1 Looper的创建

首先在run方法中通过Looper.prepare();创建一个Looper对象,看一下源码:

public static void prepare() {prepare(true);}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));}

在有参方法中,借助ThreadLocal类来检查当前线程中是否已经存在Looper,存在则抛出异常,因为一个线程只能有一个Looper。如果不存在,创建一个新的Looper并存入当前线程的ThreadLocal。简单来说就是Handler通过ThreadLocal类,实现Looper和线程的绑定功能通常来说UI线程(主线程)会自动创建Looper,其他线程则需要手动调用

继续看Looper的构造函数:

private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}

构造函数中创建了一个MessageQueue,同时Looper持有当前线程。

这就说明一个线程只能有一个Looper和一个MessageQueue。

需要注意:Android主线程,即UI线程执行的是另一个函数prepareMainLooper()

@Deprecatedpublic static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}

可以看到有两处区别:

  • 增加了一个sMainLooper变量,方便在其他线程获取主线程的Looper,即getMainLooper()
  • 执行prepare()时参数是false,也就是不允许退出,所以主线程的Looper没法手动退出。

3.2 Handler与线程的关联

从创建Handler分析:

public Handler(@Nullable Callback callback, boolean async) {// ...mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;// ...}

在创建Handler时,首先通过Looper的myLooper()方法获取线程的Looper:

public static @Nullable Looper myLooper() {return sThreadLocal.get();}

myLooper的代码很简单,是通过ThreadLocal类来获取线程的Looper,这里就体现了ThreadLocal的作用:自动根据当前线程来获取对应的Looper,这样避免了传参,更加安全(防止传错)

回到构造方法中:得到线程的Looper后,检查Looper是否存在,不存在就抛出异常,也就是说在创建Handler之前一定要先创建Looper。Looper存在则会把Looper持有的MessageQueue共享到Handler中。

到此,Handler就拿到了Looper和MessageQueue对象,就与Looper关联上了。也就是说,Handler与线程的关联是靠Looper实现的

3.3 Looper.loop()

最后就是启动Looper,执行Looper.loop(),loop源码:

public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}if (me.mInLoop) {Slog.w(TAG, "Loop again would have the queued messages be executed"+ " before this one completed.");}me.mInLoop = true;// Make sure the identity of this thread is that of the local process,// and keep track of what that identity token actually is.Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();// Allow overriding a threshold with a system prop. e.g.// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'final int thresholdOverride = getThresholdOverride();me.mSlowDeliveryDetected = false;for (;;) {if (!loopOnce(me, ident, thresholdOverride)) {return;}}}

loop中最核心的就是for循环,实现无限循环处理队列中单条消息。

可以看到每次循环会执行loopOnce方法,在这里处理单条消息:

private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {Message msg = me.mQueue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.// 注意,这里是线程结束。如果仅仅是MessageQueue为空,并不执行到这里,那种情况在queue.next()中,后面会讲到。return false;}try {// 处理消息msg.target.dispatchMessage(msg);if (observer != null) {observer.messageDispatched(token, msg);}} catch (Exception exception) {// ...} finally {// ...}// ...}

loopOnce中,循环每一次都通过me.mQueue.next();获取消息,然后调用msg.target.dispatchMessage(msg);处理消息。这里target就是一个Handler对象,所以就调用Handler中的dispatchMessage方法分发Message给对应对象处理。

在这里插入图片描述

Handler中dispatchMessage源码:

public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}

可以看到如果msg有callback,先执行callback,这个callback就是一个Runnable。

当我们使用handler的post(Runnable)等函数,handler会自动将runnable对象包装成Message,并将runnable对象赋值给这个msg的callback。

public final boolean post(@NonNull Runnable r) {return  sendMessageDelayed(getPostMessage(r), 0);}private static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;return m;}

如果没有callback则继续执行,最后会给handleMessage(msg)函数处理,这就是我们非常熟悉的了,我们创建handler会重写这个方法处理msg。

总结: Looper.loop() 是个死循环,会不断调用 MessageQueue.next() 获取 Message ,并调用 msg.target.dispatchMessage(msg) 回到了 Handler 来分发消息,以此来完成消息的回调

3.4 线程的切换

梳理一下3.3的方法调用逻辑:

Thread.foo(){Looper.loop()-> MessageQueue.next()-> Message.target.dispatchMessage()-> Handler.handleMessage()
}

显而易见,Handler.handleMessage() 所在的线程最终由调用 Looper.loop() 的线程所决定。

平时我们用的时候从异步线程发送消息到 Handler,这个 Handler 的 handleMessage() 方法是在主线程调用的,所以消息就从异步线程切换到了主线程。

图解:

在这里插入图片描述

3.5 小结

Handler 的背后有着 Looper 以及 MessageQueue 的协助,三者通力合作,分工明确。

  • Looper :负责关联线程以及消息的分发在该线程下**从 MessageQueue 获取 Message,分发给 Handler **;
  • MessageQueue :是个队列,负责消息的存储与管理,负责管理由 Handler 发送过来的 Message ;
  • Handler : 负责发送并处理消息,面向开发者,提供 API,并隐藏背后实现的细节。

Handler 发送的消息由 MessageQueue 存储管理,并由 Loopler 负责回调消息到 handleMessage()。

线程的转换由 Looper 完成,handleMessage() 所在线程由 Looper.loop() 调用者所在线程决定。

4. Handler的基本使用

package com.example.handlertest;import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;public class MainActivity extends AppCompatActivity {private Handler mHandler;private TextView textView;private Button button;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);EdgeToEdge.enable(this);setContentView(R.layout.activity_main);ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);return insets;});textView = findViewById(R.id.textView);button = findViewById(R.id.btn);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {postMessage();}});// 定义 Handler 并重写 handleMessagemHandler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:String data = (String) msg.obj;textView.setText(data);break;case 2:Toast.makeText(MainActivity.this, "请求失败", Toast.LENGTH_SHORT).show();break;}}};}private void postMessage() {Toast.makeText(this, "加载中,请稍后...", Toast.LENGTH_SHORT).show();// 在子线程发送消息new Thread(() -> {try {// 模拟网络请求Thread.sleep(2500);boolean isSuccess = true;// 通过 Message 传递数据Message msg = mHandler.obtainMessage();if (isSuccess) {msg.what = 1;msg.obj = "请求成功,数据已加载";} else {msg.what = 2;}mHandler.sendMessage(msg); // 发送消息} catch (InterruptedException e) {e.printStackTrace();}}).start();}
}

在主线程中定义 Handler 并重写 handleMessage,处理成功则简单设置textView的文本为msg,失败就弹一个提示失败的Toast

另外,定义一个按钮模拟耗时操作,比如模拟2.5秒的网络请求,在子线程中通过mHandler.obtainMessage();使用主线程的Handler从消息池中复用Message(也可以每次new Message,不推荐),设置msg后发送消息到主线程的MessageQueue中处理,默认是处理成功,效果如下:

在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/main"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><Buttonandroid:id="@+id/btn"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="执行请求"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintBottom_toTopOf="@id/textView"android:layout_marginBottom="20dp"/><TextViewandroid:id="@+id/textView"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="Hello World!"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>

5. 总结

1.Handler 的背后有 Looper、MessageQueue 支撑,Looper 负责消息分发,MessageQueue 负责消息管理;

2.在创建 Handler 之前一定需要先创建 Looper;

3.Looper 有退出的功能,但是主线程的 Looper 不允许退出;

4.Runnable 被封装进了 Message,可以说是一个特殊的 Message;

5.Handler.handleMessage() 所在的线程是 Looper.loop() 方法被调用的线程,也可以说成 Looper 所在的线程,并不是创建 Handler 的线程;

http://www.dtcms.com/a/515925.html

相关文章:

  • docker技术之部署docker
  • node框架做网站国外浏览器推荐
  • 悬赏平台 wordpress免费网站优化怎么做
  • java数据结构--LinkedList与链表
  • 【笔记--如何安装python环境】
  • 汇川H5U 威纶通HMI双仿真编程
  • 平均指数移动(EMA)
  • 可灵AI邀请码
  • 做外贸的网站怎么建立矿大师德建设网站
  • C语言需要掌握的基础知识点之前缀和
  • Java Optional orElse orElseGet orElseThrow()
  • windows10 wordpress十堰seo优化
  • 优选算法:01 双指针巧解移动零问题
  • 消息队列Kafka
  • 做游戏陪玩网站连锁销售网站制作
  • 【数字逻辑】数字逻辑实验实战:74HC151实现逻辑函数+74HC138搭全加器(附接线步骤+避坑指南)
  • Ubuntu上vue3 vite使用MBTiles搭建地图服务器
  • CClink转EtherCAT协议转换落地——汇川PLC管控球磨机CClink伺服案例
  • wordpress handsome长沙seo免费诊断
  • ChatGPT Atlas 发布:把 AI 直插进浏览器的一次重构
  • 第1章:初识Linux系统——第9节:安装服务软件、维护文件系统安全与文件权限配置实例
  • openAI发布的AI浏览器:什么是Atlas?(含 ChatGPT 浏览功能)macOS 离线下载安装Atlas完整教程
  • 西安市高陵区建设局网站聊城网站制作信息
  • ssh别名和多服务器同步文件
  • 苏州建设网站的公司软件开发和编程的区别
  • Linux I²C 总线驱动开发:从架构到实战的完整指南
  • Eureka控制台页面参数说明 ​
  • 智慧养老+适老化改造:科技与温情的双向奔赴,让晚年生活更有尊严
  • 什么是网络安全,网络空间安全有哪些安全?
  • 深圳网站建设要多少钱网站开发h5技术