总结了一下Handler收发消息的源码

Handler

Android整个ui体系都是围绕handler的消息机制,handler可以在子线程发送消息,在主线程接收处理消息,从而实现线程的跨越,所有有必要对其进行更加深入的掌握

从一般的使用上看,handler的使用分为发送消息和接收消息

//实例化一个handler对象
val mHandler = Handler(Looper.getMainLooper()){
       //do some thing in main thread
       false
  }

//通过handler对象发送消息
mHandler.sendEmptyMessage(-1)

消息的发送

使用还是很熟悉的,发送端发送消息,除了发送空的消息,还可以传递一个带参数的Messge对象,还有延时发送消息的api方法

handler发送消息的方法.png

具体的看一下其中的调用流程

发送消息的调用关系.png

这里有一些需要注意的点

  • Message的实例化是通过Message.obtain()方法,而不是直接new Message()

  • Message对象会持有发送者Handler的引用,并赋值在msg.target 属性上

  • 在msg入队之前,会判断mAsynchronous属性的值,并将msg对应的值进行赋值

通过Handler的send操作,就会产生一个附带有额外信息的Message对象和相应的延迟时间属性,那么看看mQueue这个队列是从哪来的

Handler中的Looper
//Handler
public class Handler {
//mQueue是Handler的一个成员属性
 final MessageQueue mQueue;
 final Looper mLooper;

public Handler(@Nullable Callback callback, boolean async) {
       //...省略
       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;
       mCallback = callback;
       mAsynchronous = async;
  }

public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
       mLooper = looper;
       mQueue = looper.mQueue;
       mCallback = callback;
       mAsynchronous = async;
  }
}

Handler中的mQueue和mLooper属性都是在Handler()构造函数中进行赋值引用的,可以看出handler中持有的MessageQueue是从对应的Looper中拿到的,那看一下Looper这个类

public final class Looper {
   //ThreadLocal的常量
   static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
   private static Looper sMainLooper;  // guarded by Looper.class
   final MessageQueue mQueue;
   final Thread mThread;
   private Printer mLogging;

   public static @Nullable Looper myLooper() {
       return sThreadLocal.get();
  }

   //Looper的创建
   public static void prepare() {
       prepare(true);
  }

  //从ThreadLocal中去获取对应的Looper,这个方法只能调用一次,如果重复调用,会抛出异常
   private static void prepare(boolean quitAllowed) {
       if (sThreadLocal.get() != null) {
           throw new RuntimeException("Only one Looper may be created per thread");
      }
       //会直接通过new Looper()的方式创建一个Looper,并调用ThreadLocal$set进行保存
       sThreadLocal.set(new Looper(quitAllowed));
  }
}

//Looper初始化后调用的保存方式
public class ThreadLocal<T> {
   public T get() {
     //获取当前线程
       Thread t = Thread.currentThread();
       ThreadLocalMap map = getMap(t);
       if (map != null) {
           ThreadLocalMap.Entry e = map.getEntry(this);
           if (e != null) {
               @SuppressWarnings("unchecked")
               T result = (T)e.value;
               return result;
          }
      }
       return setInitialValue();
  }

   public void set(T value) {
       //获取当前线程
       Thread t = Thread.currentThread();
       //从下面可以Thread类可以看出,每一个线程会有一个ThreadLocalMap的成员属性
       ThreadLocalMap map = getMap(t);
       if (map != null)
     //Map其实就是一个hashmap,里面会有一个Entry数组,Entry保存了一个键值对,而key的类型就是ThreadLocal
           map.set(this, value);
       else
           createMap(t, value);
  }

   //获取对应线程的ThreadLocalMap
   ThreadLocalMap getMap(Thread t) {
       return t.threadLocals;
  }
}

public class Thread implements Runnable {
   ThreadLocal.ThreadLocalMap threadLocals = null;
}

static class ThreadLocalMap {
  static class Entry extends WeakReference<ThreadLocal<?>> {
           Object value;
           Entry(ThreadLocal<?> k, Object v) {
               super(k);
               value = v;
          }
    }

   private Entry[] table;

   private void set(ThreadLocal<?> key, Object value) {
           Entry[] tab = table;
           //...省略

           tab[i] = new Entry(key, value);
      }
}
小结一下:Looper的初始化和线程唯一
  • Handler构造函数初始化,传入了Looper参数就直接赋值,没有传入就通过Looper.myLooper()获取

    构造函数让Handler的mLooper对象有值,并且将其中的MessageQueue进行传递

  • Looper.myLooper()的函数->调用其内部的 sThreadLocal.get()

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

    sThreadLocal是一个常量,所以得看其内部具体的get()逻辑

  • ThreadLocal的set()/get()方法都是先获取当前的线程#Thread.currentThread()#,然后获取线程内部的ThreadLocalMap对象,这个Map对象的key就是ThreadLocal的一个弱引用

    Looper初始化的时候,调用#prepare()方法,会通过new Looper()的方法直接实例化一个Looper对象,通过ThreadLocal的set方法,会将这个Looper对象设置到对应线程的ThreadLocalMap属性中,这个属性其实是一个HashMap,对应的key就是Looper中的常量ThreadLocal,所以线程通过这个key会找到唯一对应的Looper值

    不同的线程对应的ThreadLocalMap是不同的对象,虽然都是looper中的常量ThreadLocal作为key,但是容器不一样,所以Looper是线程唯一的

    线程内的Handler可以直接sendmessage()吗
    //在线程里面调用一个不传Looper参数的Handler构造方法
    //调用Looper.myLooper()->sThreadLocal.get() 由于该线程没有初始化设置Looper对象,所以这样写会崩溃
    new Thread(new Runnable() {
               @Override
               public void run() {
                   //线程里面
    //               Looper.prepare();
    //               Looper.loop();
    
                   Handler mHandler = new Handler(msg -> {
                       return false;
                  }
                  );
    
                   mHandler.sendEmptyMessage(-1);
              }
          }).start();
    
    //错误信息,线程没有调用Looper.prepare(),所以找不到对应的looper对象
    java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()

通过上面的流程,对Handlder和Looper的关系有一个理解了

Handler中有一个Looper的成员属性,默认情况下,这个Looper对象是handler所属当前线程的唯一值

主线程中的Looper
public final class ActivityThread extends ClientTransactionHandler {
       public static void main(String[] args) {
           //...省略各种不相关的代码
           Looper.prepareMainLooper();

           Looper.loop();
      }
    }
    
    ->Looper.java
      public static void prepareMainLooper() {
           prepare(false);
           synchronized (Looper.class) {
               if (sMainLooper != null) {
                   throw new IllegalStateException("The main Looper has already been prepared.");
              }
               sMainLooper = myLooper();
          }
      }

app进程的main()函数在ActivityThread类中,从里面可以看出,最终也是调用了Looper$prepare()方法,对我们熟悉的主线程进行Looper的初始化

Looper中的MessageQueue
public final class Looper {
    private Looper(boolean quitAllowed) {
           mQueue = new MessageQueue(quitAllowed);
           mThread = Thread.currentThread();
      }
    }

从上文可以看到,Looper.prepare()方法中,会直接new一个Looper对象,而Looper的构造函数中,也是直接new了一个MessageQueue对象

queue.enqueueMessage

最前面的handler发送逻辑,最后msg对象和延迟时间都是通过queue.enqueueMessage()进行操作的,现在就分析一下怎么入队的

public final class MessageQueue {
       private final boolean mQuitAllowed;
       Message mMessages;

     boolean enqueueMessage(Message msg, long when) {
           //target就是handler的引用
           if (msg.target == null) {
               throw new IllegalArgumentException("Message must have a target.");
          }
    
       //子线程可以使用主线程的handler进行发送消息,入队的操作需要同步锁
           synchronized (this) {
               //退出循环了,后续入队的直接抛异常
               if (mQuitting) {
                   IllegalStateException e = new IllegalStateException(
                           msg.target + " sending message to a Handler on a dead thread");
                   Log.w(TAG, e.getMessage(), e);
                   //消息的回收复用方法
                   msg.recycle();
                   return false;
              }
    
               msg.markInUse();
               msg.when = when;
               Message p = mMessages;
               boolean needWake;
               //消息是个链表,先取出头节点
               //判断插入链表头部的情况,空队列,不需要等待的消息来了,如果当前插入的消息在头消息更早的时间
               if (p == null || when == 0 || when < p.when) {
                   // New head, wake up the event queue if blocked.
                   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 (;;) {
                       prev = p;
                       p = p.next;
                       if (p == null || when < p.when) {
                           break;
                      }
                       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);
              }
          }
           return true;
      }
    }
    
    //结合Message的数据结构
    public final class Message implements Parcelable {
     //链表的下一个节点的引用
     Message next;
     //用来回收复用加锁
     public static final Object sPoolSync = new Object();
     //对象池也是使用的链表
     private static Message sPool;
     private static int sPoolSize = 0;
    
     private static final int MAX_POOL_SIZE = 50;

     public static Message obtain() {
           synchronized (sPoolSync) {
               if (sPool != null) {
                   //取头节点进行复用
                   Message m = sPool;
                   //对象池头节点后移一位
                   sPool = m.next;
                   //清理复用对象的引用关系
                   m.next = null;
                   m.flags = 0; // clear in-use flag
                   sPoolSize--;
                   return m;
              }
          }
           //回收池里面没有可复用的对象,直接new
           return new Message();
      }

       //回收消息对象
       void recycleUnchecked() {
           //清洗对象的属性值
           flags = FLAG_IN_USE;
           what = 0;
           arg1 = 0;
           arg2 = 0;
           obj = null;
           replyTo = null;
           sendingUid = UID_NONE;
           workSourceUid = UID_NONE;
           when = 0;
           target = null;
           callback = null;
           data = null;
    
           //因为有链表的首尾关系,需要加锁
           synchronized (sPoolSync) {
               //最大回收池大小是50个
               if (sPoolSize < MAX_POOL_SIZE) {
                   //回收的对象是插在头部的,将回收对象的next指针指向当前的头节点
                   next = sPool;
                   //头节点又引用当前回收的对象
                   sPool = this;
                   sPoolSize++;
              }
          }
      }
    }
Message入队过程
  • Message使用了链表的数据结构,通过next属性指向下一个Message对象

  • Message中有个sPool的回收池,回收池就是Message的链表队列

  • 通过obtain()方法会从回收池中去取头节点,存在会将回收池后移,如果不存在直接new出一个对象

  • 通过recycleUnchecked()回收Message,也是清空属性后插入到头节点

  • MessageQueue就是一个Message的链表,入队的时候,通过判断Message的延迟时间,进行排序插入

    通过上面分析,handler发送消息入队的操作,最终会得到一个按延迟时间排序的有序链表,发送端基本就分析完了,接下来看一看消息的消费

    消息的消费

    上面注意到的点,在Looper.prepare()初始化了Looper对象后,然后不断的往Looper中的messageQueue发送消息,代码层面对Looper的操作就只有 Looper.loop()方法,所以消息的消费就在这里面

###Looper.java
    public static void loop() {
    //获取当前线程的looper
           final Looper me = myLooper();

    //取出对应的MessageQueue
           final MessageQueue queue = me.mQueue;
    
    //消息消费的关键代码,通过一个死循环进行轮询取消息
           for (;;) {
               Message msg = queue.next(); // might block
               if (msg == null) {
                   // No message indicates that the message queue is quitting.
                   return;
              }
    
               //Looper中如果设置Printer对象,消息的消费过程会输出日志
               final Printer logging = me.mLogging;
               if (logging != null) {
                   logging.println(">>>>> Dispatching to " + msg.target + " " +
                           msg.callback + ": " + msg.what);
              }

               //...省略
               try {
                   //取出发送消息的handler,回调到我们的界面的地方
                   msg.target.dispatchMessage(msg);
              } catch (Exception exception) {
                  //...
              } finally {
                  //...
              }

               if (logging != null) {
                   logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
              }

             //消息回收复用
               msg.recycleUnchecked();
          }
      }

Message的消费还是比较简单,通过Looper的死循环,不断的从消息队列中去取消息,然后将对应的消息分发出去,就能回调到我们熟悉的dispatchMessage()方法中了,最后将消息进行回收复用

值得注意的点

  • Looper对象中如果设置了Printer属性,在消息分发的过程中,开始处理和处理结束都会打印日志输出
public interface Printer {
  /**
    * Write a line of text to the output. There is no need to terminate
     * the given string with a newline.
    */
   void println(String x);
 }

Printer是一个接口,这个点可以用来做简单的页面FPS监控,页面的刷新最终也是通过handler机制发送的一个发送一个message进行刷新绘制

总结

终于从前到后从源码中找到关键代码,梳理了一遍常用的消息机制handler的发送和消费过程,其中涉及到相关的Looper和Message对象,记录了Looper的线程唯一性的原因

一些有所了解还没整理总结的点

  • 对于looper死循环的退出(exit())和主线程死循环没有ANR相关的epoll机制暂时没有分析,epoll的底层原理理解起来有点麻烦,通俗一点就是messagequeue队列里面没有message需要处理的时候,会通过epoll机制进行休眠,然后在需要处理的时候wake唤醒进行消息处理
  • message的target==null的情况下,消息屏障处理异步消息的流程没有分析

handler这个贯穿整个app的生命周期,也经常在群里看到讨论Message的复用机制,MessageQueue的设计,Looper的线程唯一性,MessageQueue的池子大小等问题,还是得梳理源码,一下就清晰了,如果有理解错误的地方,欢迎指导

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容