Android 消息机制

297 阅读11分钟

SDK 30

Handler

源码注释

A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler it is bound to a Looper. It will deliver messages and runnables to that Looper's message queue and execute them on that Looper's thread.

Handler 允许你发送和处理与线程 MessageQueue 关联的 Message 和 Runnable 对象。每个 Handler 实例与一个线程及该线程的 message queue 关联。但你创建一个新的 Handler,TA 与一个 Looper 绑定。Handler 将发送 message 和 runnable 给绑定 Looper 的 message queue,且在 Looper 的线程执行 message 和 runnale。

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

Handler 有两个主要的用途:(1)调度 message 和 runnable 在未来的某个时间点执行,(2)将一个在你自己线程外执行的 action 入队。

Scheduling messages is accomplished with the post(Runnable), postAtTime(java.lang.Runnable, long), postDelayed(Runnable, Object, long), sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long), and sendMessageDelayed(Message, long) methods. The post versions allow you to enqueue Runnable objects to be called by the message queue when they are received; the sendMessage versions allow you to enqueue a Message object containing a bundle of data that will be processed by the Handler's handleMessage(Message) method (requiring that you implement a subclass of Handler).

调度 message 通过 post(Runnable), postAtTime(java.lang.Runnable, long), postDelayed(Runnable, Object, long), sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long), 和sendMessageDelayed(Message, long)` 完成。post 版本的允许你将 Runnable 对象入队,在 message queue 收到时调用。sendMessage 版本的允许你将一个包含一些数据的 Message 对象入队,将在 Handler 的 handleMessage 方法中处理(需要你实现一个 Handler 的子类)。

When posting or sending to a Handler, you can either allow the item to be processed as soon as the message queue is ready to do so, or specify a delay before it gets processed or absolute time for it to be processed. The latter two allow you to implement timeouts, ticks, and other timing-based behavior.

当 Handler post 或 send 时,你可以让 TA 们在 message queue 准备好去做时处理或是指定一个被处理前的延迟或指定处理的绝对时间。后两者允许你实现 timeout,ticks 等基于时间的行为。

When a process is created for your application, its main thread is dedicated to running a message queue that takes care of managing the top-level application objects (activities, broadcast receivers, etc) and any windows they create. You can create your own threads, and communicate back with the main application thread through a Handler. This is done by calling the same post or sendMessage methods as before, but from your new thread. The given Runnable or Message will then be scheduled in the Handler's message queue and processed when appropriate.

当创建了一个你的应用的线程后,TA 的主线程被用于运行一个 message queue,来仔细管理顶层的应用对象(activities, broadcast receivers, etc)和 TA 们创建的窗口。你可以创建你自己的线程,通过 Handler 与应用的主线程交流。通过在你的新线程调用前述的 post 和 sendMessage 方法完成。给定的 Runnable 或 Message 将会被在 Handler 的 message queue 中调度,将在合适的时候被处理。

Handler#post

将 Runnable 添加到 message queue。Runnable 将会运行在 Handler attach 的线程。

Handler#sendMessage

Handler#sendMessage 时将会设置 Message 的 target 为 this,将 Message 与 Handler 绑定。

Handler#dispatchMessage

handleCallback(msg) message.callback.run()

mCallback.handleMessage(msg)

handleMessage(msg) 默认是空方法,如果想要处理 msg 需要实现这个方法。

Looper

源码注释

Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.

运行线程的 message loop 的类。线程默认没有一个与 TA 们相关联的 message loop。在线程中调用 prepare 来运行 loop,调用 loop() 来处理 message 直到 loop 停止。

Most interaction with a message loop is through the Handler class.

大多数与 message loop 的交互通过 Handler 类。

This is a typical example of the implementation of a Looper thread, using the separation of prepare() and loop() to create an initial Handler to communicate with the Looper.

这是一个 Looper 线程的实现的典型示例,使用单独的 prepare 和 loop 来创建初始的 Handler 来与 Looper 交流。

  class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();

          mHandler = new Handler(Looper.myLooper()) {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();
      }
  }

构造方法

在 Looper 的构造方法中初始化 MessageQueue。

Looper#prepare

调用构造方法,new 一个 Looper。将 Looper 存在 ThreadLocal 中。

Looper#loop

先从 ThreadLocal 中获取 Looper,再根据 Looper 获得 MessageQueue。再在一个无限循环中尝试获得下一个 Message。如果有则调用 msg.target.dispatchMessage(msg) 在 msg 对应 Handler 线程执行方法。

Message

源码注释

Defines a message containing a description and arbitrary data object that can be sent to a Handler. This object contains two extra int fields and an extra object field that allow you to not do allocations in many cases.

定义一个包含描述和任意数据对象的 message,可以被 Handler 发送。这个对象包含两个额外的 int 域,和一个额外的对象域,允许你在大多数情况下不分配。

While the constructor of Message is public, the best way to get one of these is to call Message.obtain() or one of the Handler.obtainMessage() methods, which will pull them from a pool of recycled objects.

尽管 Message的构造方法是 public 的,最好的获得一个 Message 的方法是调用 Message.obtain() 或 Handler.obtainMessage() 其中之一方法,这些方法可以从重用的对象池中得到 Message。

MessageQueue

源码注释

Low-level class holding the list of messages to be dispatched by a Looper. Messages are not added directly to a MessageQueue, but rather through Handler objects associated with the Looper.

底层类,包含被 Looper 分发的 message 列表。Message 不是直接添加到 MessageQueue 的,而是通过与 Looper 关联的 Handler 对象。

You can retrieve the MessageQueue for the current thread with Looper.myQueue().

你可以通过 Looper.myQueue() 取得当前线程的 MessageQueue。

ThreadLocal

源码注释

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). For example, the class below generates unique identifiers local to each thread. A thread's id is assigned the first time it invokes ThreadId.get() and remains unchanged on subsequent calls.

这个类提供 thread-local 变量。这些变量与普通的对应变量不同的是,每个访问变量的线程(通过其 get 或 set 方法)都有自己的、独立初始化的变量副本。ThreadLocal 实例通常是希望将状态与线程关联起来的类中的私有静态字段(例如,用户 ID 或事务 ID )。 例如,下面的类为每个线程生成本地的唯一标识符。线程的 id 在它第一次调用 ThreadId.get() 时就会被分配,并在后续调用中保持不变。

   import java.util.concurrent.atomic.AtomicInteger;

   public class ThreadId {
       // Atomic integer containing the next thread ID to be assigned
       private static final AtomicInteger nextId = new AtomicInteger(0);

       // Thread local variable containing each thread's ID
       private static final ThreadLocal<Integer> threadId =
           new ThreadLocal<Integer>() {
               @Override protected Integer initialValue() {
                   return nextId.getAndIncrement();
           }
       };
      
       // Returns the current thread's unique ID, assigning it if necessary
       public static int get() {
           return threadId.get();
       }
   }

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

每个线程持有一个对 thread-local 变量副本的隐式引用,只要 thread 是 alive 的且 ThreadLocal 实例是可访问的。在 thread 消失后,所有的 thread-local 实例都要被垃圾收集(除非存在其它对这些副本的引用)。

消息机制概述

Looper#prepare 后创建线程对应的 Looper。Looper 作为参数创建 Handler 对象,实现响应 Message 的方法。需要发送消息时,获得 Message 对象。再 Handler 发送 Message,把 Message 添加到 MessageQueue 中。Looper 在 loop 过程中知道有 MessageQueue 中消息发送来,将取出消息,调用 msg.target.dispatchMessage(msg) 在 msg 对应 Handler 线程执行响应 msg 的方法。

一些问题

主线程的 Looper 无限循环为什么不会导致应用卡住?

对于线程即是一段可执行的代码,当可执行代码执行完成后,线程生命周期便该终止了,线程退出。而对于主线程肯定不能运行一段时间后就自动结束了,那么如何保证一直存活呢??简单的做法就是可执行代码能一直执行下去,死循环便能保证不会被退出,例如:binder线程也是采用死循环方法,通过循环方式不同与Binder驱动进行读写操作,当然并非简单的死循环,无消息时会休眠,但是死循环又如何处理其他事物呢??通过创建新的线程。真正卡死主线程操作的是在回调方法onCreate、onStart、onResume等操作时间过长,会导致掉帧甚至ANR,Looper.loop()本身不会导致应用卡死。

ActivityThread#main,在 loop 之前建立 binder 通道(创建新的线程)。

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

    // Install selective syscall interception
    AndroidOs.install();

    // CloseGuard defaults to true and can be quite spammy.  We
    // disable it here, but selectively enable it later (via
    // StrictMode) on debug builds, but using DropBox, not logs.
    CloseGuard.setEnabled(false);

    Environment.initForCurrentUser();

    // Make sure TrustedCertificateStore looks in the right place for CA certificates
    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
    TrustedCertificateStore.setDefaultUserDirectory(configDir);

    // Call per-process mainline module initialization.
    initializeMainlineModules();

    Process.setArgV0("<pre-initialized>");

    Looper.prepareMainLooper();

    // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
    // It will be in the format "seq=114"
    long startSeq = 0;
    if (args != null) {
        for (int i = args.length - 1; i >= 0; --i) {
            if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                startSeq = Long.parseLong(
                        args[i].substring(PROC_START_SEQ_IDENT.length()));
            }
        }
    }
    ActivityThread thread = new ActivityThread();
    // 建立Binder通道 (创建新线程)
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

无限循环是不是十分消耗 CPU 资源?

主线程的死循环一直运行会不会特别消耗 CPU 资源呢?其实不然这里就涉及到 Linux pipe/epoll 机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,通过往 pipe 管道写端写入数据来唤醒主线程工作。这里采用的 epoll 机制,是一种 IO 多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步 I/O,即读写是阻塞的。 所以说,主线程大多数时候都是处于休眠状态,并不会消耗大量 CPU 资源。

除了手动创建的一些子线程,Android 创建的其它线程?

可以获取最顶层的线程组递归打印一下:

熟悉 ThreadGroup 的同学会知道,在 ThreadGroup 下有两个静态成员变量,分别是systemThreadGroupmainThreadGroupmainThreadGroup其实也是systemThreadGroup的子线程组,所以我们只需要通过反射获取到systemThreadGroup对象然后递归打印就行了,代码如下:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        fun printThreads(threadGroup: ThreadGroup) {
            "group name: ${threadGroup.name}".logI()
            // 本来想直接用反射获取子线程实例的,没想到threads被禁用了,好奇怪,源码里面明明没有@hide相关标识的
            // threadGroup::class.get<Array<Thread?>?>(threadGroup, "threads")?.filterNotNull()?.forEach { "thread name: ${it.name}".logI() }
            arrayOfNulls<Thread?>(threadGroup.activeCount()).apply { threadGroup.enumerate(this, false) }
                .filterNotNull().forEach { "thread name: ${it.name}".logI() }
            threadGroup::class.get<Array<ThreadGroup?>?>(threadGroup, "groups")?.filterNotNull()?.forEach { printThreads(it) }
        }
        printThreads(ThreadGroup::class.get(null, "systemThreadGroup")!!)
    }

日志输出:

I/(MainActivity.kt:34) invoke: group name: system
I/(MainActivity.kt:36) invoke: thread name: Signal Catcher
I/(MainActivity.kt:36) invoke: thread name: HeapTaskDaemon
I/(MainActivity.kt:36) invoke: thread name: ReferenceQueueDaemon
I/(MainActivity.kt:36) invoke: thread name: FinalizerDaemon
I/(MainActivity.kt:36) invoke: thread name: FinalizerWatchdogDaemon
I/(MainActivity.kt:36) invoke: thread name: Profile Saver

I/(MainActivity.kt:34) invoke: group name: main
I/(MainActivity.kt:36) invoke: thread name: main
I/(MainActivity.kt:36) invoke: thread name: Jit thread pool worker thread 0
I/(MainActivity.kt:36) invoke: thread name: Binder:26573_1
I/(MainActivity.kt:36) invoke: thread name: Binder:26573_2
I/(MainActivity.kt:36) invoke: thread name: Binder:26573_3
I/(MainActivity.kt:36) invoke: thread name: Binder:26573_4
I/(MainActivity.kt:36) invoke: thread name: RenderThread
I/(MainActivity.kt:36) invoke: thread name: magnifier pixel copy result handler
I/(MainActivity.kt:36) invoke: thread name: queued-work-looper
I/(MainActivity.kt:36) invoke: thread name: DefaultDispatcher-worker-1
I/(MainActivity.kt:36) invoke: thread name: DefaultDispatcher-worker-2
I/(MainActivity.kt:36) invoke: thread name: DefaultDispatcher-worker-3

可以看到,现在进程内一共有两个线程组:system 和 main。

Signal Catcher,好像挺眼熟的,但源码中搜不到,好吧,知识盲区了,我投降。

接着往下看,有四个 Daemon 线程,随便选一个全局搜一下:

preview

它们都在一个叫 Daemons 的类里面。找到一篇文章:www.freesion.com/article/240…

里面有解释这四个线程的作用:

  1. HeapTaskDaemon: 用来释放堆内存的;
  2. ReferenceQueueDaemon: 一些软、弱、虚引用的对象,当它们被回收时,会被添加到对应的 ReferenceQueue 中,这个操作就由 ReferenceQueueDaemon 来负责;
  3. FinalizerDaemon: 用来回调【即将被回收的对象】的finalize方法;
  4. FinalizerWatchdogDaemon: 监视 FinalizerDaemon 线程,如果在回调【即将被回收的对象】的finalize方法时超过了100_0000_0000纳秒(即10秒),那么进程会被强行kill掉;

最后一个,Profile Saver,不知道具体是做什么用的。

main 线程组中的线程比较多:

  1. main: 不用讲都知道是主线程;
  2. *Jit thread pool worker thread 0:*不知是在哪里创建的线程池;
  3. *Binder:26573_1、Binder:26573_2、Binder:26573_3、Binder:26573_4:*Bind通讯线程;
  4. *RenderThread:*用来同步渲染 BlockingGLTextureView 的线程;
  5. *magnifier pixel copy result handler:*不知道为什么会有这个;
  6. *queued-work-looper:*这是一个 HandlerThread (自带Looper);
  7. *DefaultDispatcher-worker-123:*因为我的测试 Demo 用了协程,这几个都是 coroutines 库中的线程池;

参考

源码

Android消息机制-Handler · Leo's Studio

Android中为什么主线程不会因为Looper.loop()里的死循环卡死? - 知乎

multithreading - Android default threads and their use - Stack Overflow

每日一问 | 启动了Activity 的 app 至少有几个线程?-玩Android - wanandroid.com