为什么在子线程中创建 Handler 会抛异常?

886 阅读4分钟

Hi 大家好,我是 DHL,大厂程序员,公众号:ByteCode ,在美团、快手、小米工作过。搞过逆向,做过性能优化,研究过系统,擅长鸿蒙、Android、Kotlin、性能优化、职场分享。

微信小程序「猿面试」每日分享一道大厂面试题,涉及 JavaAndroid鸿蒙和ArkTS设计模式算法和数据结构 等内容。


Android 的 UI 框架设计为单线程模型,即所有的 UI 操作必须在主线程(也叫 UI 线程)中执行。为了方便在后台线程中执行耗时操作后更新 UI,Android 提供了 Handler 和 Looper 机制来协助线程之间的通信。

Handler 是用于处理线程间通信的一种机制,它允许你发送和处理 MessageRunnable 对象与一个线程的 MessageQueue 关联。每个 Handler 实例都会绑定到创建它的线程的 Looper 上。Looper 是用来循环取出 MessageQueue 中的消息,并分发给对应 Handler 处理的。

主线程与子线程的区别

在 Android 应用程序启动时,系统会为主线程(UI 线程)准备好 LooperMessageQueue。因此,在主线程创建 Handler 时不会有问题,因为它已经有了一个可用的 Looper

public final class ActivityThread {
    public static void main(String[] args) {
        // 设置主线程的Looper
        Looper.prepareMainLooper();
    }
}

public final class Looper {
    public static void prepareMainLooper() {
        ...
        sMainLooper = myLooper();
    }
}

然而,对于子线程,默认情况下是不会创建 Looper 的,如果尝试在一个子线程中创建一个 Handler 而该线程没有初始化 Looper,将会抛出一个异常。

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

异常原因分析

Handler 的构造函数中,获取当前线程的 Looper,用来将任务或消息发送到它所关联的 Looper 的消息队列中,并能处理发送过来的消息。如果没有 LooperHandler 就没有消息队列可以发送或处理消息。

public Handler() {
    // 获取当前线程的Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        // 如果Looper为空,则抛出异常
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    // 获取与Looper关联的消息队列
    mQueue = mLooper.mQueue;
    // ... 其他初始化代码
}

public static Looper myLooper() {
    // 获取当前线程的Looper,如果当前线程没有调用prepare(),则返回null
    return sThreadLocal.get();
}

子线程默认不会创建 Looper 对象,当在子线程中尝试创建 Handler 之前,如果没有先调用 Looper.prepare() 初始化 Looper,就会因为找不到绑定的 LooperHandler 是无法知道将消息或者 Runnable 发送到哪里的,因此会抛出 RuntimeException

Looper.prepare() 方法的作用是为当前线程初始化一个 Looper 对象。

public static void prepare() {
    // 如果当前线程已经有Looper,则抛出异常
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // 初始化一个Looper并设置给当前线程
    sThreadLocal.set(new Looper());
}

全文到这里就结束了,感谢你的阅读,如果文章对你有帮助,欢迎在看、点赞、分享给身边的小伙伴,你的点赞是我持续更新的动力。


更多大厂面试题,欢迎前往微信搜索小程序「猿面试」查看。微信小程序(猿面试)包含了 Java、Android、鸿蒙和ArkTS设计模式算法和数据结构 相关内容,

推荐阅读

开源新项目

  • 云同步编译工具(SyncKit),本地写代码,远程编译,欢迎前去查看 SyncKit

  • KtKit 小巧而实用,用 Kotlin 语言编写的工具库,欢迎前去查看 KtKit

  • 最全、最新的 AndroidX Jetpack 相关组件的实战项目以及相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,欢迎前去查看 AndroidX-Jetpack-Practice

  • LeetCode / 剑指 offer,包含多种解题思路、时间复杂度、空间复杂度分析,在线阅读