Handler机制详解

Handler是什么

  • 先来看官方文档对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 the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.

    Handler可以让我们发送和处理和线程的MessageQueue关联的MessageRunnable对象。每个Handler实例都与一个线程和该线程的消息队列相关联。当创建一个Handler时,Handler就会被绑定到创建它的线程/消息队列。创建之后,Handler就可以发送消息(messages) 和任务(runnables)到该消息队列,并且在消息出队的时候执行它们。所以Handler其实是Android为我们提供的一套消息机制。

Handler作用

  • 同样看看官方文档中的描述

    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. 让消息(messages) 和任务(runnables)在将来某个时刻执行
    2. 将要在不同于自己线程上执行的操作(messages和runnables)插入队列

    第一个作用很好理解,也就是说可以通过Handler延时执行一些操作;第二个怎么来理解呢?前面有提过,每个Handler都会和一个线程/消息队列绑定起来。那么通过该Handler发送消息和任务就会到该线程的消息队列中并且执行。所以可以通过Handler将一些操作放到其它线程中去执行。举个例子,Android是不允许在子线程中更新UI的,所以在子线程中可以通过Handler将更新UI的操作放到主线程/UI线程中执行

  • 下面这一段描述其实就为第二个作用进行了说明,并且我们知道了在主线程中会自动维护一个消息队列用于维护

    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.

    为应用程序创建进程的时候,主线程也就是ActivityThread会专门启动一个消息队列对于管理顶级的应用程序对象,比如Activity、BroadcastReceiver等等以及它们创建的任何窗口。可以创建自己的线程,并通过Handler与主应用程序线程进行通信。

Handler的基本使用

  • Handler的最简单的使用方式如下:

    • 创建Handler
    //创建Handler
    private Handler mHandler = new Handler() {
          @Override
          public void handleMessage(Message msg) {
              switch (msg.what){
                  //do sth
              }
          }
      };
    
    • 发送empty消息
      int what = 1;
      mHandler.sendEmptyMessage(what);
    
    • 携带对象
    class Person {
          String name;
          int age;
    
          public Person(String name, int age) {
              this.name = name;
              this.age = age;
          }
      }
      Message message =mHandler.obtainMessage();
      message.what = 1;
      message.obj = new Person("ygg",25);
      mHandler.sendMessage(message);
    
    • 发送延时消息
    mHandler.sendMessageDelayed(message,1000);
    
    • 发送runnable
    Runnable runnable = new Runnable() {
              @Override
              public void run() {
                  //do sth
              }
          };
    mHandler.postDelayed(runnable,1000);
    
    • 移除message和runnable
    int what = 1;
    mHandler.removeMessages(what);
    mHandler.removeCallbacks(runnable);
    //移除所有消息和任务
    mHandler.removeCallbacksAndMessages(null);
    
    • 拦截消息
     private Handler mHandler = new Handler(new Handler.Callback() {
          @Override
          public boolean handleMessage(Message msg) {
              if (msg.what == 1){
                  //do sth
                  return  true;
              }
              return false;
          }
      }) {
          @Override
          public void handleMessage(Message msg) {
              switch (msg.what){
                  //do sth
              }
          }
      };
    

    在创建的时候可以传入一个Callback对消息进行一个拦截,Callback中有一个具有返回值的handleMessage方法,返回true表示需要拦截该消息,拦截后下面返回值为voidhandleMessage方法不会再被调用

    • 指定Looper
    private Handler mHandler = new Handler(Looper.getMainLooper());
    

    关于Looper,会在后面进行讲解,在这我们先知道可以为Handler 指定Looper,这也是在非UI线程中和UI线程交互的实现方式,将Looper指定为UI线程的Looper,发送的消息就会到UI线程中处理

    • 其它使用方式
        runOnUiThread(new Runnable() {
              @Override
              public void run() {
      
              }
          });
      
       //Activity.class
       public final void runOnUiThread(Runnable action) {
          if (Thread.currentThread() != mUiThread) {
              mHandler.post(action);
          } else {
              action.run();
          }
      }
      
      在子线程中更新UI,我们常常使用runOnUiThread,可以看到实际上它内部也是使用Handler实现的,如果不是在UI 线程就交给Handler处理,否则直接执行
      private View view;
      view.post(new Runnable() {
              @Override
              public void run() {
      
              }
          });
      
      //View.class
       public boolean post(Runnable action) {
          final AttachInfo attachInfo = mAttachInfo;
          if (attachInfo != null) {
              return attachInfo.mHandler.post(action);
          }
      
          // Postpone the runnable until we know on which thread it needs to run.
          // Assume that the runnable will be successfully placed after attach.
          getRunQueue().post(action);
          return true;
      }
      
      我们常在Activity#onCreate中用View.post方式,在里面获取控件的宽高等操作,内部实际上也是利用Handler实现的,需要注意在Activity#onCreate时这里的AttachInfo是为空的,所以走的是下面的getRunQueue().post(action);,它会先把Runnable用数组存起来,等到第一次测量完成后才执行,所以我们可以用该方法获取到宽高。关于View.post,想了解更多信息的话可以关注博主,之后会写一篇文章进行分析。
  • 需要注意的点

    1. 对于Message的创建使用Handler.obtainMessage()而不是new Message()
      先来看看obtainMessage方法
    //Handler.class
    public final Message obtainMessage()
      {
          return Message.obtain(this);
      }
    
    //Message .class
    public static Message obtain(Handler h) {
          Message m = obtain();
          m.target = h;
    
          return m;
      }
    
    /**
       * Return a new Message instance from the global pool. Allows us to
       * avoid allocating new objects in many cases.
       */
      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;
              }
          }
          return new Message();
      }
    

    可以看到obtainMessage方法会从一个公共缓存池返回一个Message实例,避免频繁的分配对象。所以使用该方法可以对Message对象进行复用,从而减少Message对象大量的创建。

    1. Handler的创建应该改为静态内部类,在Activity关闭的时候应该调用``清空消息和任务,如果需要在Handler内部使用Activity,采用弱引用方式,避免内存泄漏
    private Handler mHandler = new MyHandler(this);
    
    static class MyHandler extends Handler{
    
          WeakReference<Activity> mWeakReference;
    
          public MyHandler(Activity activity) {
              mWeakReference = new WeakReference<Activity>(activity);
          }
    
          @Override
          public void handleMessage(Message msg){
              final Activity activity = mWeakReference.get();
              if(activity!=null){
                // do sth
              }
          }
      }
    
      @Override
      protected void onDestroy() {
          super.onDestroy();
          mHandler.removeCallbacksAndMessages(null);
      }
    

    在Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,静态的内部类不会持有外部类的引用。如果发送了一个延时Message,此时MessageQueue就会持有一个Message对象。再来看发送消息是如何处理

    //Handler.class
     public final boolean sendMessage(Message msg) {
          return sendMessageDelayed(msg, 0);
      }
    
    public final boolean sendMessageDelayed(Message msg, long delayMillis){
          if (delayMillis < 0) {
              delayMillis = 0;
          }
          return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
      }
    
     public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
          MessageQueue queue = mQueue;
          if (queue == null) {
              RuntimeException e = new RuntimeException(
                      this + " sendMessageAtTime() called with no mQueue");
              Log.w("Looper", e.getMessage(), e);
              return false;
          }
          return enqueueMessage(queue, msg, uptimeMillis);
      }
    
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
          msg.target = this;
          if (mAsynchronous) {
              msg.setAsynchronous(true);
          }
          return queue.enqueueMessage(msg, uptimeMillis);
      }
    

    可以看到最终会调用enqueueMessage方法入队,此时MessageQueue 便会持有一个Message对象,关注这行代码:msg.target = this;,将Handler对象赋值给Message的target变量,所以Message会持有Handler对象,如果Handler是非静态内部类的话,它又持有Activity,最终就会形成MessageQueue ->Message->Handler->Activity这样一条引用链,当Activity退出时还有消息没有被执行就会导致Activity不会被回收,最终导致Activity泄漏。

    1. 在子线程中创建Handler时必须指定Looper或者调用Looper.prepare()Looper.loop()为线程创建一个Looper并启动Looper
    new Thread(new Runnable() {
              //指定Looper
              private Handler mHandler = new Handler(Looper.getMainLooper());
              
              @Override
              public void run() {
                  //先为线程创建一个Looper,并启动
                  Looper.prepare();
                  Looper.loop();
                  Handler mHandler1 = new Handler();
              }
          }).start();
    

    从下面的源码中可以看到在子线程中创建Handler,会对Looper进行一个检测,如果为空的话,会报错。至于为什么需要调用Looper.prepare() ,在了解了Handler、Looper、MessageQueue、Message之间的关系之后就可以解释了。

     public Handler() {
          this(null, false);
      }
    
     public Handler(Callback callback, boolean async) {
          //建议创建成STATIC的
          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());
              }
          }
    
          //检测有没有设置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;
          mCallback = callback;
          mAsynchronous = async;
      }
    

Handler、Looper、MessageQueue、Message之间的关系

  • Handler与Message之间的关系
    首先Message是一个Parcelable对象,作为消息的载体。主要注意targetcallbacknext
    public final class Message implements Parcelable {
        public int what;
        public int arg1;
        public int arg2;
        public Object obj;
        Handler target;
        Runnable callback;
        Message next;
      //是否是异步消息
      public boolean isAsynchronous() {
          return (flags & FLAG_ASYNCHRONOUS) != 0;
      }
       
       //设置消息是否是异步的,异步则意味着它不受 Looper同步障碍影响。
        public void setAsynchronous(boolean async) {
          if (async) {
              flags |= FLAG_ASYNCHRONOUS;
          } else {
              flags &= ~FLAG_ASYNCHRONOUS;
          }
      }
    }
    
    1.Handler target
    注意到它是Handler类型,还记得前面Handler是如何发送一个消息的吗?最终会调用
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
          msg.target = this;
          if (mAsynchronous) {
              msg.setAsynchronous(true);
          }
          return queue.enqueueMessage(msg, uptimeMillis);
      }
    
    关注msg.target = this;,也就是说当Handler发送一个Message的时候,Message会将发送它的Handler记录下来。这也就是它们之间的关系:Message会持有发送它的Handler对象。最终是为了在该Handler的handleMessage处理该消息
    2.Runnable callback
    callback是一个Runnable对象,还记得Handler可以直接发送一个Runnable吗?
    public final boolean post(Runnable r) {
         return  sendMessageDelayed(getPostMessage(r), 0);
      }
    
     private static Message getPostMessage(Runnable r) {
          Message m = Message.obtain();
          m.callback = r;
          return m;
      }
    
    实际上也是将Runnable对象包装成一个Message,而Message的callback就是发送的Runnable对象。先记住这一点,在消息分发的时候会用到。
    1. Message next
      next也是一个Message类型,相信大家都有见到过这种写法——链表。实际上MessageQueue是一个单链表形式的队列
  • ThreadLocal简介
    为了便于理解线程与Looper以及MessageQueue之间的关系,在这里对ThreadLocal做一个简要的介绍。ThreadLocal可以提供一个线程的局部变量。每一个线程通过ThreadLocal#set保存和ThreadLocal#get访问的数据都有它们自己的副本。也就是说通过ThreadLocal保存的数据是线程隔离的。举个例子

    static final ThreadLocal<Integer> sThreadLocal = new ThreadLocal<>();
    
     new Thread(new Runnable() {
              @Override
              public void run() {
                  sThreadLocal.set(1);
                  try {
                      Thread.sleep(2000);
                      sThreadLocal.get();//1
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }).start();
    
          new Thread(new Runnable() {
              @Override
              public void run() {
                  sThreadLocal.set(2);
                  new Thread(new Runnable() {
                      @Override
                      public void run() {
                          sThreadLocal.set(3);
                          sThreadLocal.get();//3
                      }
                  }).start();
                  try {
                      Thread.sleep(2000);
                      sThreadLocal.get(); //2
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                 
                  
              }
          }).start();
    

    事例非常简单。在每个线程中获取的都会是该线程本身保存的值。

  • 接下来看一下ThreadLocal#setThreadLocal#get方法,帮助我们理解这个过程

    //ThreadLocal.class
    
      public void set(T value) {
          Thread t = Thread.currentThread();
          ThreadLocalMap map = getMap(t);
          if (map != null)
              map.set(this, value);
          else
              createMap(t, value);
      }
    
      void createMap(Thread t, T firstValue) {
          t.threadLocals = new ThreadLocalMap(this, firstValue);
      }
    
      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();
      }
    
      ThreadLocalMap getMap(Thread t) {
          return t.threadLocals;
      }
    

    ThreadLocalMapThreadLocal的一个内部类,在这里我们不深入讲它是如何插入和获取值的。就当它是一个容器。比如HashMap之类的,可以用来保存插入的内容。我们可以看到整个流程其实非常的清晰

    1. 调用ThreadLocal.set实际上会为当前线程创建一个容器(ThreadLocalMap),也就是t.threadLocals,最终保存的内容是放在这个容器当中的
    2. 调用ThreadLocal.get实际上是从当前线程取出之前创建的容器(ThreadLocalMap),然后从这个容器中获取对应的内容。
    3. 也就是说实际上每一个线程都会有一个自己的容器(ThreadLocalMap),那么保存在里面的内容肯定是它自己独享的,和其它线程之间不会互相干扰。需要注意在线程退出(exit)的时候会自动释放该容器。
  • Looper、MessageQueue之间的关系

    1. MessageQueue 简介
    public final class MessageQueue {
      //Native消息机制中MessageQueue的地址
      private long mPtr; // used by native code
    
      //处理的消息
      Message mMessages;
      //可以添加一个IdleHandler,用于在每次空闲(没有消息处理)时回调,可以利用该特性进行一些优化处理一些需要在主线程执行又比较耗时操作,在系统空闲时才执行
      private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
      private IdleHandler[] mPendingIdleHandlers;
      // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout.
      //用来判断 next()方法是否因为调用pollOnce() 在阻塞
      private boolean mBlocked; 
    
     private native static long nativeInit();
     //阻塞调用该方法的线程,类似于Object.wait,不过会在阻塞timeoutMillis之后唤醒线程
     private native void nativePollOnce(long ptr, int timeoutMillis);
     //唤醒线程以继续执行
     private native static void nativeWake(long ptr);
    
      MessageQueue(boolean quitAllowed) {
          //是否可以停止
          mQuitAllowed = quitAllowed;
          mPtr = nativeInit();
      }
    
     //消息入队
     boolean enqueueMessage(Message msg, long when) {
     
          synchronized (this) {
              msg.when = when;
              Message p = mMessages;
              boolean needWake;
              if (p == null || when == 0 || when < p.when) {
                  // 新消息入队,队列中没有消息或者新消息不需要延时,或者新消息的执行时间比当前队列中最早执行的消息执行时间还要早,插入头部
                  msg.next = p;
                  mMessages = msg;
                  //如果当前正在阻塞即mBlocked == true,则唤醒线程
                  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 next() {
         
          final long ptr = mPtr;
          if (ptr == 0) {
              return null;
          }
    
          ...
          int nextPollTimeoutMillis = 0;
          for (;;) {
        
              //阻塞nextPollTimeoutMillis时间,第一次是0,所以会往下执行
              nativePollOnce(ptr, nextPollTimeoutMillis);
    
              synchronized (this) {
                  //获取系统开机到现在的时间
                  final long now = SystemClock.uptimeMillis();
                  Message prevMsg = null;
                  Message msg = mMessages;
                  //msg.target == null表示是屏障信息,则需要找到队列中下一个异步消息执行
                  if (msg != null && msg.target == null) {
                      // 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) {
                      //比较时间,还没有到要执行的时间,需要等待
                      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;
                          if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                          msg.markInUse();
                          return msg;
                      }
                  } else {
                      // No more messages.
                      //没有更多的消息要处理,-1表示一直阻塞
                      nextPollTimeoutMillis = -1;
                  }
    
                  // Process the quit message now that all pending messages have been handled.
                  //结束直接返回空
                  if (mQuitting) {
                      dispose();
                      return null;
                  }
    
                  // If first time idle, then get the number of idlers to run.
                  // Idle handles only run if the queue is empty or if the first message
                  // in the queue (possibly a barrier) is due to be handled in the future.
                  //没有消息的第一时间,获取IdleHandler的个数
                  if (pendingIdleHandlerCount < 0
                          && (mMessages == null || now < mMessages.when)) {
                      pendingIdleHandlerCount = mIdleHandlers.size();
                  }
                 //没有设置IdleHandler,继续阻塞
                  if (pendingIdleHandlerCount <= 0) 
                      // No idle handlers to run.  Loop and wait some more.
                      mBlocked = true;
                      continue;
                  }
    
                  if (mPendingIdleHandlers == null) {
                      mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                  }
                  mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
              }
    
              // Run the idle handlers.
              // We only ever reach this code block during the first iteration.
              for (int i = 0; i < pendingIdleHandlerCount; i++) {
                  final IdleHandler idler = mPendingIdleHandlers[i];
                  mPendingIdleHandlers[i] = null; // release the reference to the handler
                  //执行IdleHandler
                  boolean keep = false;
                  try {
                      keep = idler.queueIdle();
                  } catch (Throwable t) {
                      Log.wtf(TAG, "IdleHandler threw exception", t);
                  }
    
                  if (!keep) {
                      synchronized (this) {
                          mIdleHandlers.remove(idler);
                      }
                  }
              }
    
              //重置个数,只执行一次
              // Reset the idle handler count to 0 so we do not run them again.
              pendingIdleHandlerCount = 0;
    
              // While calling an idle handler, a new message could have been delivered
              // so go back and look again for a pending message without waiting.
              //执行完IdleHandler后,看看有没有消息需要执行
              nextPollTimeoutMillis = 0;
          }
      }
    
    //向Looper的消息队列发布同步障碍。
    //消息机制还是在执行,但是遇到屏障时,队列中的后续同步消息将被暂停(阻止执行),直到通过调用removeSyncBarrier并指定标识同步屏障的令牌来释放屏障
    public int postSyncBarrier() {
          return postSyncBarrier(SystemClock.uptimeMillis());
      }
    }
    
    
    

    MessageQueue是一个单项链表实现的队列。可以看到MessageQueue在消息入队的时候,会为Message选择一个合适的位置插入,延时时间越小,就排在越前面,当下一次被唤醒时。排在前面的就会先被取出执行。
    需要注意消息出队是会阻塞的。获取下一个消息,如果下一个消息是可以执行的,也就是now >=msg.when,就直接返回,如果是延时消息,则阻塞起来,到达延时时间就会被唤醒,从而继续取出消息执行。因为入队时经过排序,如果队头消息都需要等待,后面的消息肯定也是需要等待的。这也是延时消息的实现原理

    1. 解决前面的问题:为什么需要在子线程中调用Looper.prepare() ? 先来看看该方法做了什么
    //Looper.class
    
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    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");
          }
          sThreadLocal.set(new Looper(quitAllowed));
      }
    
        public static @Nullable Looper myLooper() {
          return sThreadLocal.get();
      }
    

    我们可以看到有个抛出异常,也就是说一个线程只能创建一个Looper,也就是只能调用一次Looper.prepare()。该方法实际上就是创建了一个Looper对象,并且保存到ThreadLocal中。利用ThreadLocal,实现Looper的线程隔离。默认情况下,非UI线程的Looper为空,所以在子线程中创建Handler时检测抛出异常,需要先调用Looper.prepare()方法

    private Looper(boolean quitAllowed) {
          mQueue = new MessageQueue(quitAllowed);
          mThread = Thread.currentThread();
      }
    

    在创建Looper的时候就会创建一个MessageQueue与其绑定起来。

    1. Looper和MessageQueue的关联
    /**
       * Run the message queue in this thread. Be sure to call
       * {@link #quit()} to end the loop.
       */
        public static void loop() {
          //返回sThreadLocal存储的Looper实例
          final Looper me = myLooper();
    
          //loop方法必须在prepare方法之后运行
          if (me == null) {
              throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
          }
    
          //获取Looper实例中的消息队列mQueue
          final MessageQueue queue = me.mQueue;
          ...
          for (;;) {
              //next()方法用于取出消息队列中的消息,如果没有符合的消息或者没有消息,则线程阻塞
              Message msg = queue.next(); // might block
              if (msg == null) {
                  // No message indicates that the message queue is quitting.
                  return;
              }
    
               //时间分发前打印时间
              // This must be in a local variable, in case a UI event sets the logger
              Printer logging = me.mLogging;
              if (logging != null) {
                  logging.println(">>>>> Dispatching to " + msg.target + " " +
                          msg.callback + ": " + msg.what);
              }
    
              //把消息派发给msg的target属性,然后用dispatchMessage方法去处理
              msg.target.dispatchMessage(msg);
    
              //消息执行完后打印时间
              if (logging != null) {
                  logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
              }
    
              msg.recycleUnchecked();
          }
      }
    

    可以看到loop()方法就是开启一个循环,一直从消息队列中取出消息,并交给Handler#dispatchMessage方法进行分发处理。在这额外说一下,在消息分发前后都调用了Looper 的Printer 打印了时间。而BlockCanary正是利用这一点监听卡顿的。下面看看Handler是如何分发消息的

  • Handler分发消息

    //Handler.class
     public void dispatchMessage(Message msg) {
          if (msg.callback != null) {
             //如果msg.callback不为空,实际上就是调用的是post方法,传入了一个Runnable对象,直接执行
              handleCallback(msg);
          } else {
              if (mCallback != null) { 
                 //如果创建Handler的时候传入了Callback ,则先调用Callback#handleMessage方法先处理消息
                  if (mCallback.handleMessage(msg)) {
                    //如果Callback#handleMessage方法返回true,则拦截
                      return;
                  }
              }
              //执行handleMessage方法
              handleMessage(msg);
          }
      }
    
    private static void handleCallback(Message message) {
         //执行Runnable的run方法
          message.callback.run();
      }
    

    Handler分发消息比较简单,先判断Message 的callback是不是为空,也就是判断是不是调用的post方法,注意post方法传入的Runnable也是包装成Message然后入队的,所以消息延时一样适用。如果是Runnable,直接执行。否则先判断Handler的Callback是否为空,先执行Handler#Callback#handleMessage方法,所以我们可以通过传入一个Callback对消息进行拦截,最后才执行Handler#handleMessage方法。

Handler消息机制流程

Handler机制流程.png
  • Looper.prepare()为每个线程创建了一个Looper,每个Looper会创建一个MessageQueue,用于存放Message
  • Looper.loop()开启一个循环,不断的从MessageQueue中取出Message并且交由Handler进行分发处理
  • new Handler()会从线程中取出与线程绑定的Looper,再从Looper中拿到MessageQueue
  • Handler.sendXXX()一系列发送消息的方法最终会通过enqueueMessage方法将Message插入MessageQueue中,此时Message的target属性绑定为发送它的Handler,最后消息出队时通过Message.target.dispatchMessage方法将Message交回给发送该Message的Handler进行处理,最终回到handleMessage.handleMessage方法

为什么非UI线程不能直接更新UI,只能通过Handler机制更新UI

  • 为什么非UI线程直接更新UI会报错

    //ViewRootImpl.class
     public ViewRootImpl(Context context, Display display) {
          ...
          mThread = Thread.currentThread();
     }
    
     @Override
      public void invalidateChild(View child, Rect dirty) {
          invalidateChildInParent(null, dirty);
      }
    
      @Override
      public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
          checkThread();
          ...
      }
    
     void checkThread() {
          if (mThread != Thread.currentThread()) {
              throw new CalledFromWrongThreadException(
                      "Only the original thread that created a view hierarchy can touch its views.");
          }
      }
    
    

    View的绘制都是通过ViewRootImpl操作的,关于View的绘制流程,可以关注博主,后续也会写一篇文章。ViewRootImpl是在UI线程创建的。我们可以看到每次更新UI都会检测是不是主线程/UI线程。当然在Activity#onCreate之前直接在子线程/非UI线程直接更新UI也是可以的,因为此时ViewRootImpl并没有被创建,它是在Activity#onResume中创建的。不过正常来说在这个时候更新UI并没有什么实际的作用。

  • 为什么只能通过Handler机制更新UI

    1. 是因为考虑到多线程并发更新UI的问题。不知道大家有没有想过这样一个问题:如果非UI线程能够直接更新UI,那么在有多个非UI线程同时更新UI会发生什么问题?
    2. 答案是会导致界面UI更新错乱。我们都知道,多线程的情况下会存在一个并发的问题,在这种情况下更新UI就可能造成预期之外的结果。那么怎么处理这个问题呢?
    3. 处理多线程问题,一般我们会考虑加锁,如果对更新UI的操作加锁,会带来性能上的问题。更新UI的操作在Android中是非常频繁的操作。如果加锁了,线程对锁的获取和释放、线程的切换都会带来资源的消耗,而对于更新UI的操作,一旦多花了点时间,都有可能导致界面的卡顿等不好的结果。Android 16.6ms内要完成一次刷新操作想必都有听说过。所以加锁是不适合的。那么应该如何处理呢?
    4. 答案就是通过Handler机制更新UI,非UI线程将所有更新UI的操作都放到主线程/UI线程中,这些更新UI的操作就会被放到队列中一个接一个被执行,即解决了多线程并发的问题,也避免了加锁带来的性能上的消耗。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容