Handler 机制模型

1,010 阅读5分钟

Handler 机制模型

下文的“操作”指的是“操作序列”,操作序列就是多个代码行。

在 Android 中,有一些操作是不能在主线程做的,例如网络请求,所以需要另起一个线程去做网络请求。但是有一些操作又只能在主线程做,例如更新界面。所以网络请求完毕之后如果需要更新界面,就需要有一种机制:在网络线程中,通知主线程去完成更新界面的操作。这种机制叫 Handler 机制。

那么 Handler 机制是如何实现通知主线程去执行另一个线程想做的操作呢?整个机制的模型可以这么理解:在主线程中,会循环遍历一个任务表,另一个线程只需要把操作封装成任务,添加到这个任务表中,当主线程从任务表中拿到任务,就会执行那个任务。

除了把操作封装成任务,还可以用一些符号来标记一些操作,例如 A 代表操作 A, B 代表操作 B, 其他线程只需要把符号封装成任务,添加到任务表中。拿到任务后,主线程会根据任务的符号来判断是执行操作 A 还是操作 B️。

概括来说,handler 的模型就是循环遍历一个任务表,取出里面任务来执行。有两种方法添加任务,可以将操作封装成任务,当取出这种任务时会直接执行任务里面的操作,也可以将符号封装成任务,当取出这种任务时会根据符号来执行不同的操作。

Handler 使用分析

下面看一下模型是怎么还原到实际代码的。Handler 有很多种用法,下面先讲两种

Handler 的第一种用法

// 将这个 Runnable 里面的操作封装成任务并且添加到任务表
mainHandler.post(new Runnable() {
      @Override
      public void run() {
          //操作序列
      }
})

在另一个线程中将操作封装在 Runnable 中,然后 mainHandler.post 会将这个 Runnable 封装成任务并且添加到任务表。由于这里的 mainHandler 是和主线程绑定的,所以这个任务表是主线程的,主线程从任务表拿到任务,就会执行任务里面的 Runnable.run 方法。

Handler 的第二种用法

private Handler mainHandler = new Handler() {
      @Override
      public void handleMessage(Message msg) {
          // 根据任务的符号来执行不同的操作
          switch (msg.what) {
              case A:
                  // 操作A
                  break;
              case B:
                  // 操作B
                  break;    
          }
      }
  };
  
  private void sendMessageToMainThread() {
      new Thread(){
          @Override
          public void run() {
              // 将符号 A 封装成任务
              Message message = mainHandler.obtainMessage(A);
              // 将这个任务添加到任务表
              mainHandler.sendMessage(message);
          }
      }.start();
  }

在另一个线程中,通过 mainHandler.obtainMessage 将符号封装成任务,然后 mainHandler.sendMessage 会将这个任务添加到任务表。主线程从任务表拿到任务,就会调用 handler 的 handleMessage 方法,根据任务的符号来执行不同的操作。

任务与任务列表是什么?

上文一直说到“任务”: “把操作封装成任务”、“把符号封装成任务”, 那么什么是任务呢?是 mainHandler.post(new Runnable())里面的 Runnable 还是 mainHandler.sendMessage(message) 里面的 Message ?

打开handler的源码

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;
 }
 
 public final boolean sendMessage(@NonNull Message msg) {
     return sendMessageDelayed(msg, 0);
 }

可以看到 post 方法先调用 getPostMessage 将 runnable 封装成 Message, 再调用 sendMessageDelayed 方法。而 sendMessage 方法也调用了 sendMessageDelayed 方法。所以无论是操作还是符号,都会封装进 Message 里面。查看 Message 代码,会看到里面有这两个变量。

那么任务表是什么呢?是如何将任务添加到任务表里面的呢?继续追踪 sendMessageDelayed 方法,你会追踪到 MessageQueue 的 enqueueMessage 方法。

先不用理解全部代码,只看红色框住的两行代码,你可以很容易联想到链表。没错,任务表(MessageQueue)就是以链表的数据结构把任务(Message)组织起来的。

Looper 是什么?

本文一开始说的是怎么在另一个线程通知主线程去执行一个任务,如果不是主线程呢?可不可以是任意的线程?为了让任意一个线程都可以接收另一个线程的任务,Android 将这套机制抽取成 Looper。Looper 负责维护一个任务表,提供一个 loop 方法来遍历任务表执行任务。任务表会和一个 handler 绑定, 外界通过 handler 来往任务表里添加任务。任意线程只需要如下图在 run 方法调用 Looper 对象的 loop 方法,其他线程就可以通过 handler 机制与这个线程交互。

Looper.prepare

每个需要实现 handler 机制线程都有且只有一个 looper 对象,为了方便创建和获取 looper 对象,Android 使用了 ThreadLocal 。这样每个线程就不需要定义 looper 实例,每个线程在使用 loop 方法之前先调用 Looper.prepare 方法创建 looper 对象,并放到 ThreadLocal 里面,这样 looper 对象相当于和当前线程绑定起来。需要获取当前线程绑定的 looper 对象调用 Looper.myLooper 方法即可。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static @Nullable Looper myLooper() {
     return sThreadLocal.get();
}
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));
}

HandlerThread

为了进一步简化先调用 Looper.prepare ,再调用 Looper.loop 这样的样板代码,HandlerThread 的 run 方法实现了这些样板代码。用户线程可以继承 HandlerThread 来简化代码。

Handler 还有 schedule 功能

Handler 机制除了可以执行别的线程想做的任务,还可以调度本线程的一些任务,例如你想延迟3秒再执行任务, 可以调用 handler.postDelayed(runnable, 3000)。其实每个任务都记录着期望的执行时间,任务链表会将任务按时间排序。如果任务执行时间还没到,会先跳过。