Android 中的 Thread 和 Handler

其实很长一段时间我并不知道Handler究竟是如何工作的,接触 RxJava 之前切换线程只是简单的handler.post(() -> {})。但随着所做项目的更新迭代我需要知道关键组件的执行原理,不然无法为代码质量负责。

习惯在 OneNote 中记录学习和工作笔记,这些文本容量已经累积到 57MB,我觉得是时候停下来好好整理一遍,用它们更精细的填充技术栈。这篇文章是一个开始,能描述清楚一个组件才意味着真正理解它。

Main Thread

Android 的 Thread 即是 Java 线程,当 App 启动时,系统会为它创建一个 Linux Process 和一个 Execution Thread。默认情况下此 App 的所有组件都会运行在这个进程的单一执行线程中,包括 UI 产生的触控事件的分发处理,所以此线程又被称为 UI Thread 或 Main Thread。

Called from wrong thread

Only the original thread that created a view hierarchy can touch its views.

为性能考虑,Android 的 UI 操作是线程不安全的,即多线程操作 UI 时可能出现状态不一致的情况,所以 Android 强制要求只能在主线程操作 UI,否则抛出CalledFromWrongThreadException异常。

override onCreate(savedInstanceState: Bundle?) {
    ...
    // 主线程操作 UI
    tvShow.text = "UI Thread"
    // 抛出的 Runnable 会在 View Tree 完成后执行
    tvShow.post {
        Thread {
            // 非主线程操作 UI 会抛出 CalledFromWrongThreadException
            tvShow.text = "Other Thread"
        }.start()
    }
}

ANR

ANR 即 Application Not Responding,应用程序无响应。Android 作为手持设备通过 UI 触控与用户交互,产生的触控事件会在主线程进行由ActivityView的层层分发,交给对应组件处理后再刷新 UI,这个过程的迅速完成会让用户感觉到操作顺滑。

如果负责分发事件的主线程被阻塞(通常是因为在主线程执行耗时操作),则用户点击屏幕后触控事件迟迟不能向下传递,处理组件无法获取事件并给出反馈,用户看到的是点击屏幕却无任何反应,App 像卡住一样。如果阻塞时间大于 5 秒,Android 系统就会弹出 ANR 提醒用户强制关闭程序。

所以在 Android 中使用线程必须遵循 2 条规则:

  • 只在主线程操作 UI。
  • 不能阻塞主线程。

什么是 Handler

已知 Android 需要在工作线程执行耗时操作再切换到主线程刷新 UI,这个切换线程的动作即可使用Handler实现,而且它其实能将任意线程中的MessageRunnable发送到任意线程中处理。

// 创建处理 Runnable 的 Handler
val handler = Handler()
override fun onCreate(savedInstanceState: Bundle?) {
    ...
    Thread {
        // 在工作线程执行耗时操作
        doSth()
        // 切换到主线程刷新 UI
        handler.post {
            Toast.makeText(this, "Work done", Toast.LENGTH_SHORT).show()
        }
        // View 的 post() 内部实现其实也是 Handler
        view.post {
            Toast.makeText(this, "Post from view", Toast.LENGTH_SHORT).show()
        }
    }.start()
}

理解Handler如何将RunnableMessage发送到另一个线程需要注意LooperMessageQueueThreadLocal,从创建Handler说起:

// 创建 Handler 实际使用的构造器
public Handler(Callback callback, boolean async) {
    // 获取 Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
            + " that has not called Looper.prepare()");
        }
    // 获取 Looper 的 MessageQueue
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

创建Handler会尝试获取一个Looper和它的MessageQueue,获取不到则抛异常。

获取的是什么Looper呢,继续看:

// Looper.myLooper() 方法实现
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

了解ThreadLocal就知道,它获取的是此线程私有的Looper,即这个Looper是和此线程绑定的。

那么这个Looper有什么作用呢,看一下Handler.post(Runnable)的实现:

public final boolean post(Runnable r) {
    // 将 Runnable 封装成 Message,继续传递
   return  sendMessageDelayed(getPostMessage(r), 0);
}

最终调用:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    // 从 Looper 中获取的 MessageQueue
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    // 将 Runnable 封装的 Message 添加到一个 Handler 持有的 MessageQueue 中,即从 Looper 获取的 Queue
    return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        // 将该 Handler 实例保存到 Message 中
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

可以看到,切换线程使用的Handler.post(Runnable)实际上只是把Runnable封装成Message,并添加到一个该HandlerLooper那里获取的MessageQueue中,那么是谁来从Queue中取出Message并处理呢?

答案就是Looper,看 2 个Looper的关键方法:

// Looper.prepare()
private static void prepare(boolean quitAllowed) {
    // 检查该线程是否已经创建过 Looper,一个线程只允许创建一个 Looper
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    // 创建该线程私有的 Looper
    sThreadLocal.set(new Looper(quitAllowed));
}
// Looper 的构造器
private Looper(boolean quitAllowed) {
    // 创建 MessageQueue
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

Looper.prepare()会检查当前线程是否已经创建过Looper,没有的话就创建一个新并设置为该线程私有,留心的话,其实Handler构造时从线程获取的Looper就是在这里创建的。Looper构造时会在内部创建一个MessageQueue,即是Handler.post(Runnable)用到的那个Queue

// Looper.loop(),这里只取关键逻辑,完整逻辑请看源码
public static void loop() {
    // 确保已经执行过 Looper.prepare()
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // 获取 Looper 的 MessageQueue
    final MessageQueue queue = me.mQueue;
    // 进入阻塞线程的无限循环
    for (;;) {
        Message msg = queue.next(); // might block
        try {
            // 取出 Message 中保存的 Handler 实例,即发送该 Message 的 Handler,调用它的相关方法处理这个 Message
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
    }
}

Looper.loop()会进入一个无限的等待循环,不断检查MessageQueue中是否有新的Message,一旦获取到新的Message,就取出其在post()时保存的Handler实例,调用该Handler的方法来处理Message,这就是整个Handler.post(Runnable)的执行轨迹。

至于最关键的线程切换是如何实现的,需注意Looper.prepare()执行时所在的线程,它就是Runnable事件被处理时所在的线程,和线程启用Looper有关。

前面提到Handler创建时要求线程必须有Looper,即执行过Looper.prepare(),一个典型的支持Looper的线程是这样创建的:

class CusThread : Thread() {
    lateinit var handler: Handler
    override fun run() {
        Looper.prepare()
        handler = Handler()
        // 阻塞线程,等待处理 Handler 分发的事件
        Looper.loop()
    }
}

实际这个线程在创建Handler之后就因为执行Looper.loop()而阻塞,在监听MessageQueue等待Handler分发事件。注意Looper.loop()是在该线程中执行,结合之前的源码分析,通过Handler发送的Runnable都会在此线程中执行,而Handler.post(Runnable)这个行为可以在任意线程中进行,这就实现了在其它线程发送事件到指定线程处理,即广义的切换线程。

使用CusThread示例:

val thread = CusThread()
thread.start()
...
// 在其它线程中发送事件到 CusThread 线程中处理
Thread {
    thread.handler.post {
        // 这里的代码会在 CusThread 中执行
    }
}.start()
// 必要时退出 Looper.loop() 引起的线程阻塞,结束该执行线程
thread.handler.looper.quite()

使用 HandlerThread

普通线程需创建Looper才可使用HandlerAndroid提供一个默认创建好Looper的线程HandlerThread,原理和上面CusThread大同小异,只是多些细节控制,可以这样使用它:

val handlerThread = HandlerThread("")
handlerThread.start()
// 创建 Handler 时传入指定的 Looper,这样它就不会去获取创建线程的 Looper
val handler = Handler(handlerThread.looper)
...
// 在其它线程中发送事件到 CusThread 线程处理
Thread {
    handler.post {
        // 这里的代码会在 HandlerThread 执行
    }
}.start()
// 必要时退出 Looper.loop() 引起的线程阻塞,结束该执行线程
handler.looper.quite()

总结

Handler机制总结起来大概是这样:Handler创建时需要一个Looper,可以是指定的Looper或默认使用当前线程的LooperHandler获取此LooperMessageQueue,当Handler.post(Runnable)执行时,HandlerRunnable封装成Message并携带该Handler实例,发送到从Looper那里拿到的MessageQueue中。Looper在其创建线程中不断检查MessageQueue是否有新Message,有的话就取出,调用附带的Handler实例方法处理,因为Handler发送事件的线程和Looper执行事件的线程一般不同,这样就能实现线程切换。

Main Thread 的特殊性

可能已经注意到,既然创建Handler时要求该线程必须有Looper否则抛出异常,那么 Android 的主线程为什么可以直接创建Handler呢?

其实,能创建即说明这个主线程默认已经有Looper,Android 实际是由事件驱动,主线程相当于一直都在“阻塞”(即Looper.loop()),它在等待新事件进行处理,所谓“不能阻塞主线程”实际是指不能阻塞主线程的事件处理。体会一下区别,很有意思。

arrow_upward