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

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

文章目录

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的基本用法

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

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

3. Handler机制解析

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

java 复制代码
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对象,看一下源码:

java 复制代码
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的构造函数:

java 复制代码
private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

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

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

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

java 复制代码
@Deprecated
    public 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分析:

java 复制代码
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:

java 复制代码
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源码:

java 复制代码
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方法,在这里处理单条消息:

java 复制代码
private static boolean loopOnce(final Looper me,
            final long ident, final int thresholdOverride) {
        Message msg = me.mQueue.next(); // might block
        if (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源码:

java 复制代码
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。

java 复制代码
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的方法调用逻辑:

java 复制代码
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的基本使用

java 复制代码
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;
    @Override
    protected 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() {
            @Override
            public void onClick(View view) {
                postMessage();
            }
        });
        // 定义 Handler 并重写 handleMessage
        mHandler = new Handler(Looper.getMainLooper()) {
            @Override
            public 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 复制代码
<?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">

    <Button
        android: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"
        />


    <TextView
        android: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 的线程;

相关推荐
杨筱毅6 小时前
【Android】Handler/Looper机制相关的类图和流程图
android·java·流程图
Kapaseker6 小时前
酷炫的文字效果 — Compose 文本着色
android·kotlin
努力进修7 小时前
【JavaEE初阶】 多线程编程核心:解锁线程创建、方法与状态的创新实践密码
android·java·java-ee
生莫甲鲁浪戴7 小时前
Android Studio新手开发第二十八天
android·ide·android studio
zhaoyufei1337 小时前
Android触屏TP驱动事件上报以及多点触摸
android
杨筱毅7 小时前
【Android】详细讲解ViewDragHelper的实现原理(不含代码版)
android
代码s贝多芬的音符15 小时前
ios android 小程序 蓝牙 CRC16_MODBUS
android·ios·小程序
2501_9159184117 小时前
iOS 混淆实战 多工具组合完成 IPA 混淆、加固与工程化落地(iOS混淆|IPA加固|无源码混淆|Ipa Guard|Swift Shield)
android·ios·小程序·https·uni-app·iphone·webview
雨白17 小时前
让协程更健壮:全面的异常处理策略
android·kotlin