【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 的线程;

相关推荐
xiangpanf9 小时前
Laravel 10.x重磅升级:五大核心特性解析
android
robotx12 小时前
安卓线程相关
android
消失的旧时光-194313 小时前
Android 面试高频:JSON 文件、大数据存储与断电安全(从原理到工程实践)
android·面试·json
dalancon14 小时前
VSYNC 信号流程分析 (Android 14)
android
dalancon14 小时前
VSYNC 信号完整流程2
android
dalancon14 小时前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android
用户693717500138415 小时前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
程序员Android15 小时前
Android 刷新一帧流程trace拆解
android
墨狂之逸才16 小时前
解决 Android/Gradle 编译报错:Comparison method violates its general contract!
android
阿明的小蝴蝶16 小时前
记一次Gradle环境的编译问题与解决
android·前端·gradle