前语

Handler是Android开发中运用常用的机制,自然也是面试中的高频考点,咱们都清楚,在问到Handler时,都会供出他的好同伴MessageMessageQueneLooper

Handler中会延生出一些问题,这些问题包括:

  1. postDelay是怎么做到推迟的?
  2. Handler是怎么做到线程切换的?
  3. 主线程Looper#loop()死循环会卡顿吗?
  4. Handler内存走漏怎么处理?
  5. Handler、Looper、MessageQueue、Thread的对应关系?

接下来咱们带着问题来看看Handler的一些源码吧。

Base on Android12

根本运用

先来温习一下Handler的根底用法。

// 直接在主线程中创立Handler
@SuppressLint("HandlerLeak")
final Handler handler = new Handler() {
  @Override
  public void handleMessage(@NonNull Message msg) {
    LogUtils.INSTANCE.error(TAG, "来音讯了");
   }
};
​
// 在一个子线程中运用
new Thread(() -> handler.sendEmptyMessage(10086)).start();

示例中的Handler在主线程初始化,假如是子线程中的Handler,记住调用Looper#prepareLooper.loop(), 后边说为什么。

Handler的创立相关

首要来看一下Handler结构的办法。

  // 【无参结构办法,高版别现已废弃,不建议运用】
    @Deprecated
  public Handler() {
    this(null, false);
   }
    // 【带Looper参数的结构办法】
  public Handler(@NonNull Looper looper) {
    this(looper, null, false);
   }

咱们知道Handler需求和LooperMessageQueue相关。直观的看,Handler目标中需求持有对应的目标引证

public class Handler {
 ……
  final Looper mLooper;
  final MessageQueue mQueue;
}

所以带Looper参数的的结构办法目的就很明显了——你的Handler需求和哪个Looper相关。

主要看一下无参数的结构办法是怎么和Looper相关上的。

  public Handler(@Nullable Callback callback, boolean async) {
    ……
    //【要害点1】调用myLooper办法获取Looper目标
    mLooper = Looper.myLooper();
    if (mLooper == null) {
      throw new RuntimeException(
        "Can't create handler inside thread " + Thread.currentThread()
            + " that has not called Looper.prepare()");
     }
    mQueue = mLooper.mQueue; // 从Looper中取MessageQueue
    mCallback = callback;
    mAsynchronous = async;
   }
​
  static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
​
  public static @Nullable Looper myLooper() {
   // 【要害点2】从ThreadLocal中获取Looper
    return sThreadLocal.get();
   }

可见当构建Handler没有指定Looper时,会从ThreadLocal中去获取当时线程的Looper目标,假如为空,会抛反常,相信不少小同伴初学Android时都遇到过。那么当时线程的Looper是何时存储到ThreadLocal中的呢?当然便是调用Looper#prepare的时分。

  public static void prepare() {
    prepare(true);
   }
  private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
      throw new RuntimeException("Only one Looper may be created per thread");
     }
   // 【要害点3】存储当时线程的Looper目标,且只能初始化一次。
    sThreadLocal.set(new Looper(quitAllowed));
   }

这儿回答了为什么在子线程中创立Looper时,需求先调用prepare。那么为什么在主线程中创立的Handler不需求呢?

废话不多说,由于主线程在ActivityThread#main办法中现已调用了

  public static void main(String[] args) {
    ……
     // 【要害点4】
    Looper.prepareMainLooper();
​
    ……
    
    // 【要害点5】
    Looper.loop();
   }

所以在主线程中创立Handler不再需求初始化Looper等。

音讯入队

咱们用得比较多的办法便是postsendMessagesendMessageDelay办法,先挨个看看

post办法

  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;
   }
  • 能够看到,post接受一个Runnable类型的参数,并将其封装为一个Message目标,并且将Runnable参数赋值给msg的callback字段,这儿要记住,后边有用——Runnable什么时分履行的呢?
  • 最终调用的便是sendMessageDelayed

sendMessage办法

  public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
   }
  • 最终调用的也是sendMessageDelay,第二个参数是0。

sendMessageDelay办法

可见无论是post仍是sendMessage办法,最终都走到了sendMessageDelayed

  public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
      delayMillis = 0;
     }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
   }
​
  // 这儿留意第二个参数 @param updateMillis 是一个详细的时刻点。
  public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    ……
    return enqueueMessage(queue, msg, uptimeMillis);
   }
​
  private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
      long uptimeMillis) {
   // 【要害点6】这儿要留意target指向了当时Handler
    msg.target = this;
    
​
    if (mAsynchronous) {
      msg.setAsynchronous(true);
     }
      // 【要害点7】调用到了queue#enqueueMessage办法
    return queue.enqueueMessage(msg, uptimeMillis);
   }

最终会走到handler中的`enqueueMessage办法,然后走到quene#enqueneMessage(msg, uptimeMillis),从这个命名来看,便是将message放入了音讯行列,那么咱们来看看,是怎么放入行列的吧。

  boolean enqueueMessage(Message msg, long when) {
    ……
    // 【要害点8】对queue目标上锁
    synchronized (this) {
      ……
      msg.markInUse();
      msg.when = when; // msg的when时刻赋值
      Message p = mMessages;
      boolean needWake;
      if (p == null || when == 0 || when < p.when) {
        // New head, wake up the event queue if blocked.
          // 翻译:新的头结点,假如queue堵塞,则wakeup唤醒
        msg.next = p;
        mMessages = msg;
        needWake = mBlocked;
       } else {
        // Inserted within the middle of the queue.  Usually we don't have to wake
        // up the event queue unless there is a barrier at the head of the queue
        // and the message is the earliest asynchronous message in the queue.
        // 翻译:将音讯刺进到音讯行列中,通常我不需求进行唤醒操作,除非........
        needWake = mBlocked && p.target == null && msg.isAsynchronous();
        Message prev;
        for (;;) { // for循环,break结束时when < p.when,说明依照when进行排序刺进,或许尾节点
          prev = p;
          p = p.next;
          if (p == null || when < p.when) {
            break;  // 【要害点9】 找到刺进方位,条件尾部或许when从小到大的方位
           }
          if (needWake && p.isAsynchronous()) {
            needWake = false;
           }
         }
       // 履行刺进
        msg.next = p; // invariant: p == prev.next
        prev.next = msg;
       }
​
      // We can assume mPtr != 0 because mQuitting is false.
      if (needWake) {
        nativeWake(mPtr); // native办法,唤醒
       }
     }
    return true;
   }

这儿面将msg放到音讯行列中,能够看到这个行列是一个简单的单链表结构,依照msg的when进行的排序,并且进行了synchronized加锁,确保增加数据的线程安全。之所以选用链表的数据结构,原因是链表方便刺进。

初看源码的时分,应该忽略掉wakeup这些处理,重视msg是怎么参加行列即可。

到这儿,咱们了解了message是怎么参加音讯行列MesssageQueue。可是音讯什么时分履行,即重新Handler#handleMessage办法,以及post(Runnable)中的Runnable什么时分才履行。

音讯出队履行

咱们看完了音讯入队,接下来看音讯出队的方位。自然在MessageQueue中查找,很简单就定位到next办法,调用它的的办法是Loope#oopOnce,再往上找便是Looper#loop(留意我是Android12)。这个办法很熟悉吧。

  public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
      throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
     }
    ……
    // 【要害点10】死循环,只要loopOnce办法回来false时退出
    for (;;) {
      if (!loopOnce(me, ident, thresholdOverride)) {
        return;
       }
     }
   }
​
    
  private static boolean loopOnce(final Looper me,
      final long ident, final int thresholdOverride) {
    Message msg = me.mQueue.next(); // might block 【留意注释:可能堵塞】
    if (msg == null) {
      // No message indicates that the message queue is quitting.
      return false;
     }
    ……
    try {
     ……
      // 【要害点11】还记住这个target是什么吗?
      msg.target.dispatchMessage(msg);
      ……
     } catch (Exception exception) {
      ……
     } finally {
      ……
     }
        ……
    return true;
   }

loopOnce办法中主要便是调用me.mQueue.next()获取一个音讯msg,留意这儿可能堵塞。【要害点11】这儿调用了msg.target.dispatchMessage(msg)能够回头看看【要害点6】赋值的地方。所以这儿就将音讯分发给了对应的Handler去向理了。待会儿再看Handler#dispatchMessage,咱们接着看next办法是怎么取音讯的。

  Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
      return null;
     }
​
    ……
    int nextPollTimeoutMillis = 0;
      // 【要害点12】持续死循环
    for (;;) { 
      ……
      nativePollOnce(ptr, nextPollTimeoutMillis);
​
     // 加锁
      synchronized (this) {
        // Try to retrieve the next message.  Return if found.
        final long now = SystemClock.uptimeMillis(); // 当时时刻
        Message prevMsg = null;
        Message msg = mMessages; // msg 指向链表头结点
        if (msg != null && msg.target == null) { // 这个if能够先忽略
          // Stalled by a barrier.  Find the next asynchronous message in the queue.
          do {
            prevMsg = msg;
            msg = msg.next;
           } while (msg != null && !msg.isAsynchronous());
         }
        if (msg != null) {
         // 【要害点13】假如当时时刻小于头结点的when,更新nextPollTimeoutMillis,并在对应时刻安排妥当后poll通知
          if (now < msg.when) { 
            // Next message is not ready.  Set a timeout to wake up when it is ready.
            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
           } else {
            // Got a message. 开端获取音讯
            mBlocked = false;
            if (prevMsg != null) {
              prevMsg.next = msg.next;
             } else {
              mMessages = msg.next; // 更新头结点 
             }
            msg.next = null; // 断链处理,等候回来
            ……
            return msg;
           }
         } else {
          // No more messages.
          nextPollTimeoutMillis = -1;
         }
​
        // Process the quit message now that all pending messages have been handled.
        if (mQuitting) {
          dispose();
          return null; 
         }
​
        ……
       }
​
      ……
      // 下面是IdleHandler的处理,还不是很了解
     }
   }

next办法中也是一个死循环,不断的尝试获取当时音讯行列现已到时刻的音讯,假如没有满意的音讯,就会一向循环,这便是为什么会next会堵塞的原因

看完了next办法,获取到了msg,回到刚才的msg.target.dispatchMessage(msg),接着看Handler是怎么处理音讯的。

  public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
     // 【要害点14】假如音讯有CallBack则直接,优先调用callback
      handleCallback(msg);
     } else {
        // 【要害点15】假如Handler存在mCallback,优先处理Handler的Callback
      if (mCallback != null) {
        if (mCallback.handleMessage(msg)) {
          return;
         }
       }
        // 【要害点16】此办法会被重写,用户用于处理详细的音讯
      handleMessage(msg);
     }
   }
​
  private static void handleCallback(Message message) {
    message.callback.run();
   }

dispatchMessage办法中优先处理Messagecallback,再回头看看Handler#post办法应该知道callback是个啥了吧。

【要害点15】假如Handler设置了mCallback, 则优先判断mCallback#handleMessage的回来值,这个机制能够让咱们做一些勾子,监听Handler上的一些音讯。

【要害点16】应该不用多说了,这个办法一般在创立Handler时被重写,用于接收音讯。


到这儿,咱们就简单的把Handler的音讯机制看完了,咱们结合开篇的几个问题,做一个总结吧。

总结

  1. 一个线程只能有一个Looper,怎么确保?

    1. ThreadLocal
  2. Handler#post(Runnable)会将Runnable封装到一个Message中。

  3. MessageQueue选用单链表的实现方法,并且在存取音讯时都会进行加锁。

  4. Looper#loop选用死循环的方法,会堵塞线程。那么为什么主线程不会被堵塞?

    1. 由于Android是事件驱动的,很多的体系事件(点击事件、屏幕刷新等)都是经过Handler处理的,因此主线程的音讯行列,会一向有音讯的。
  5. Handler是怎么实现线程切换的?

    1. LooperMessageQueue和线程绑定的,也便是说这个音讯行列中的一切音讯,最终分给对应的Handler都是在创立Looper的线程。所以无论Handler在什么线程发送音讯,最终都回到创立Looper的线程中履行。(有点饶,能够自己好好捋一下,能够看一下第一节的运用)
  6. Thread和Looper、MessageQueue是1对1的关系,Looper、MessageQueue关于Handler和是一对多的关系。这儿要留意,一个详细的Handler实例,必定只相关一个Looper和queue的哟

上面便是整个Handler的音讯机制,还有一些扩展内容,一同看看。

扩展

音讯屏障

Android 体系为了确保某些高优先级的 Message(异步音讯) 能够被赶快履行,选用了一种音讯屏障(Barrier)机制。其大致流程是:先发送一个屏障音讯到 MessageQueue 中,当 MessageQueue 遍历到该屏障音讯时,就会判断当时行列中是否存在异步音讯,有的话则先跳过同步音讯(开发者自动发送的都属于同步音讯),优先履行异步音讯。这种机制就会使得在异步音讯被履行完之前,同步音讯都不会得到处理。咱们能够经过调用 Message 目标的 setAsynchronous(boolean async) 办法来自动发送异步音讯。而假如想要自动发送屏障音讯的话,就需求经过反射来调用 MessageQueue 的 postSyncBarrier() 办法了,该办法被体系隐藏起来了

屏障音讯的作用便是能够确保高优先级的异步音讯能够优先被处理,ViewRootImpl 就经过该机制来使得 View 的制作流程能够优先被处理

——————音讯屏障部分摘录自/post/690168…

IdleHandler

IdleHandler 是 MessageQueue 的一个内部接口,能够用于在 Loop 线程处于闲暇状态的时分履行一些优先级不高的操作,经过 MessageQueue 的 addIdleHandler 办法来提交要履行的操作。例如,ActivityThread 就向主线程 MessageQueue 增加了一个 GcIdler,用于在主线程闲暇时尝试去履行 GC 操作。

——————IdleHandler部分摘录自/post/690168…

Handler内存走漏

Handler作为内部类运用时,持有外部类的引证,比方Activity,所以当Activity销毁时,假如Handler中还有推迟任务,则走漏发生。因此要在合理的机遇移除Handler中的音讯。Handler.removeCallbacksAndMessages(null)


水平有限,请狠狠辅导。