Handler 源码分析(一)

249 阅读2分钟

「这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

这里主要分析Handler 源码

一、Handler 定义

更新 UI 界面的机制,也就是消息处理,可以发送消息,处理消息

二、 原理

Handler:封装消息的发送(主要包括消息发送给谁)

Handler 内部是通过 post 方法将 Runnable 投递到 Handler 的 Looper 处理,或者通过 send

发送信息到 Looper 处理,post 最终也是调用 send 方法调用。

Looper:消息封装的载体。内部包含一个 MessageQueue,所有的 Handler 发送的消息

都走向这个消息队列;Looper.Looper 方法,就是一个死循环,不断地从 MessageQueue 取消

息,如果有消息就处理消息,没有消息就阻塞

它的特点是它跟它的线程是绑定的,处理消息也是在 Looper 所在的线程去处理,所以当我

们在主线程创建 Handler 时,它就会跟主线程唯一的 Looper 绑定,从而我们使用 Handler 在

子线程发消息时,最终也是在主线程处理,达到了异步的效果。

Handler 内部与 Looper 关联,handler->Looper->MessageQueue,handler 发送消息就是

向 MessageQueue 队列发送消息

MessageQueue:主要是两个操作,插入和读取,读取伴随着删除。enqueueMessage 和

next 两个操作,MQ 数据结构是通过链表完成,通过 enqueueMessage 完成入队的操作,即

链表的插入,而 next 里面是一个死循环,当有消息时会通过调用 next 方法从单链表里移出,

没有时则阻塞

总结:handler 负责发送消息,Looper 负责接收 handler 发送的消息,并把消息回传给

handler 自己,MessageQueue 存储消息

三、为什么在子线程实例化 handler 就不行呢?

handler在子线程中实例化Handler之前要注意先调用 Looper.prepare()  方法。不然会报下面的错误

  throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");   
public Handler() {
  this(null, false);
}

  public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
    final Class<? extends Handler> klass = getClass();
    if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) {
      Log.w(TAG, "The following Handler class should be static or leaks might occur: "+klass.getCanonicalName());
    }
  }
  // 获取我的轮询器
  mLooper = Looper.myLooper();
  if (mLooper == null) {
    // 在实例化 Handler 时, mLooper 为空的时候就会抛出这个异常
    throw new RuntimeException("Can't create handler inside thread that has not called
    Looper.prepare()");
  }
  mQueue = mLooper.mQueue;
  mCallback = callback;
  mAsynchronous = async;
}

问题找到了,Looper 为空,接下来载看看 Looper 里的

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
  public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}