Handler機(jī)制詳解

Handler是什么

  • 先來看官方文檔對(duì)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可以讓我們發(fā)送和處理和線程的MessageQueue關(guān)聯(lián)的MessageRunnable對(duì)象籽孙。每個(gè)Handler實(shí)例都與一個(gè)線程和該線程的消息隊(duì)列相關(guān)聯(lián)晓折。當(dāng)創(chuàng)建一個(gè)Handler時(shí)往扔,Handler就會(huì)被綁定到創(chuàng)建它的線程/消息隊(duì)列谆级。創(chuàng)建之后赞辩,Handler就可以發(fā)送消息(messages) 和任務(wù)(runnables)到該消息隊(duì)列昔案,并且在消息出隊(duì)的時(shí)候執(zhí)行它們烁挟。所以Handler其實(shí)是Android為我們提供的一套消息機(jī)制净蚤。

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主要有兩個(gè)作用:

    1. 讓消息(messages) 和任務(wù)(runnables)在將來某個(gè)時(shí)刻執(zhí)行
    2. 將要在不同于自己線程上執(zhí)行的操作(messages和runnables)插入隊(duì)列

    第一個(gè)作用很好理解,也就是說可以通過Handler延時(shí)執(zhí)行一些操作共屈;第二個(gè)怎么來理解呢绑谣?前面有提過,每個(gè)Handler都會(huì)和一個(gè)線程/消息隊(duì)列綁定起來拗引。那么通過該Handler發(fā)送消息和任務(wù)就會(huì)到該線程的消息隊(duì)列中并且執(zhí)行借宵。所以可以通過Handler將一些操作放到其它線程中去執(zhí)行。舉個(gè)例子矾削,Android是不允許在子線程中更新UI的壤玫,所以在子線程中可以通過Handler將更新UI的操作放到主線程/UI線程中執(zhí)行

  • 下面這一段描述其實(shí)就為第二個(gè)作用進(jìn)行了說明,并且我們知道了在主線程中會(huì)自動(dòng)維護(hù)一個(gè)消息隊(duì)列用于維護(hù)

    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.

    為應(yīng)用程序創(chuàng)建進(jìn)程的時(shí)候怔软,主線程也就是ActivityThread會(huì)專門啟動(dòng)一個(gè)消息隊(duì)列對(duì)于管理頂級(jí)的應(yīng)用程序?qū)ο罂严福热鏏ctivity、BroadcastReceiver等等以及它們創(chuàng)建的任何窗口挡逼。可以創(chuàng)建自己的線程括改,并通過Handler與主應(yīng)用程序線程進(jìn)行通信。

Handler的基本使用

  • Handler的最簡(jiǎn)單的使用方式如下:

    • 創(chuàng)建Handler
    //創(chuàng)建Handler
    private Handler mHandler = new Handler() {
          @Override
          public void handleMessage(Message msg) {
              switch (msg.what){
                  //do sth
              }
          }
      };
    
    • 發(fā)送empty消息
      int what = 1;
      mHandler.sendEmptyMessage(what);
    
    • 攜帶對(duì)象
    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);
    
    • 發(fā)送延時(shí)消息
    mHandler.sendMessageDelayed(message,1000);
    
    • 發(fā)送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);
    //移除所有消息和任務(wù)
    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
              }
          }
      };
    

    在創(chuàng)建的時(shí)候可以傳入一個(gè)Callback對(duì)消息進(jìn)行一個(gè)攔截,Callback中有一個(gè)具有返回值的handleMessage方法嘱能,返回true表示需要攔截該消息吝梅,攔截后下面返回值為voidhandleMessage方法不會(huì)再被調(diào)用

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

    關(guān)于Looper,會(huì)在后面進(jìn)行講解惹骂,在這我們先知道可以為Handler 指定Looper苏携,這也是在非UI線程中和UI線程交互的實(shí)現(xiàn)方式,將Looper指定為UI線程的Looper对粪,發(fā)送的消息就會(huì)到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,可以看到實(shí)際上它內(nèi)部也是使用Handler實(shí)現(xiàn)的,如果不是在UI 線程就交給Handler處理著拭,否則直接執(zhí)行
      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方式纱扭,在里面獲取控件的寬高等操作,內(nèi)部實(shí)際上也是利用Handler實(shí)現(xiàn)的儡遮,需要注意在Activity#onCreate時(shí)這里的AttachInfo是為空的乳蛾,所以走的是下面的getRunQueue().post(action);,它會(huì)先把Runnable用數(shù)組存起來,等到第一次測(cè)量完成后才執(zhí)行鄙币,所以我們可以用該方法獲取到寬高肃叶。關(guān)于View.post,想了解更多信息的話可以關(guān)注博主十嘿,之后會(huì)寫一篇文章進(jìn)行分析因惭。
  • 需要注意的點(diǎn)

    1. 對(duì)于Message的創(chuàng)建使用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方法會(huì)從一個(gè)公共緩存池返回一個(gè)Message實(shí)例,避免頻繁的分配對(duì)象绩衷。所以使用該方法可以對(duì)Message對(duì)象進(jìn)行復(fù)用筛欢,從而減少M(fèi)essage對(duì)象大量的創(chuàng)建。

    1. Handler的創(chuàng)建應(yīng)該改為靜態(tài)內(nèi)部類唇聘,在Activity關(guān)閉的時(shí)候應(yīng)該調(diào)用``清空消息和任務(wù),如果需要在Handler內(nèi)部使用Activity柱搜,采用弱引用方式迟郎,避免內(nèi)存泄漏
    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 中,非靜態(tài)的內(nèi)部類和匿名內(nèi)部類都會(huì)隱式地持有其外部類的引用聪蘸,靜態(tài)的內(nèi)部類不會(huì)持有外部類的引用宪肖。如果發(fā)送了一個(gè)延時(shí)Message,此時(shí)MessageQueue就會(huì)持有一個(gè)Message對(duì)象健爬。再來看發(fā)送消息是如何處理

    //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);
      }
    

    可以看到最終會(huì)調(diào)用enqueueMessage方法入隊(duì)控乾,此時(shí)MessageQueue 便會(huì)持有一個(gè)Message對(duì)象,關(guān)注這行代碼:msg.target = this;,將Handler對(duì)象賦值給Message的target變量娜遵,所以Message會(huì)持有Handler對(duì)象蜕衡,如果Handler是非靜態(tài)內(nèi)部類的話,它又持有Activity,最終就會(huì)形成MessageQueue ->Message->Handler->Activity這樣一條引用鏈设拟,當(dāng)Activity退出時(shí)還有消息沒有被執(zhí)行就會(huì)導(dǎo)致Activity不會(huì)被回收慨仿,最終導(dǎo)致Activity泄漏久脯。

    1. 在子線程中創(chuàng)建Handler時(shí)必須指定Looper或者調(diào)用Looper.prepare()Looper.loop()為線程創(chuàng)建一個(gè)Looper并啟動(dòng)Looper
    new Thread(new Runnable() {
              //指定Looper
              private Handler mHandler = new Handler(Looper.getMainLooper());
              
              @Override
              public void run() {
                  //先為線程創(chuàng)建一個(gè)Looper,并啟動(dòng)
                  Looper.prepare();
                  Looper.loop();
                  Handler mHandler1 = new Handler();
              }
          }).start();
    

    從下面的源碼中可以看到在子線程中創(chuàng)建Handler,會(huì)對(duì)Looper進(jìn)行一個(gè)檢測(cè)镰吆,如果為空的話帘撰,會(huì)報(bào)錯(cuò)。至于為什么需要調(diào)用Looper.prepare() 万皿,在了解了Handler摧找、Looper、MessageQueue牢硅、Message之間的關(guān)系之后就可以解釋了蹬耘。

     public Handler() {
          this(null, false);
      }
    
     public Handler(Callback callback, boolean async) {
          //建議創(chuàng)建成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());
              }
          }
    
          //檢測(cè)有沒有設(shè)置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之間的關(guān)系

  • Handler與Message之間的關(guān)系
    首先Message是一個(gè)Parcelable對(duì)象,作為消息的載體佳励。主要注意target休里、callbacknext赃承。
    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;
      }
       
       //設(shè)置消息是否是異步的妙黍,異步則意味著它不受 Looper同步障礙影響。
        public void setAsynchronous(boolean async) {
          if (async) {
              flags |= FLAG_ASYNCHRONOUS;
          } else {
              flags &= ~FLAG_ASYNCHRONOUS;
          }
      }
    }
    
    1.Handler target
    注意到它是Handler類型瞧剖,還記得前面Handler是如何發(fā)送一個(gè)消息的嗎拭嫁?最終會(huì)調(diào)用
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
          msg.target = this;
          if (mAsynchronous) {
              msg.setAsynchronous(true);
          }
          return queue.enqueueMessage(msg, uptimeMillis);
      }
    
    關(guān)注msg.target = this;,也就是說當(dāng)Handler發(fā)送一個(gè)Message的時(shí)候抓于,Message會(huì)將發(fā)送它的Handler記錄下來做粤。這也就是它們之間的關(guān)系:Message會(huì)持有發(fā)送它的Handler對(duì)象。最終是為了在該Handler的handleMessage處理該消息
    2.Runnable callback
    callback是一個(gè)Runnable對(duì)象捉撮,還記得Handler可以直接發(fā)送一個(gè)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;
      }
    
    實(shí)際上也是將Runnable對(duì)象包裝成一個(gè)Message,而Message的callback就是發(fā)送的Runnable對(duì)象。先記住這一點(diǎn)巾遭,在消息分發(fā)的時(shí)候會(huì)用到肉康。
    1. Message next
      next也是一個(gè)Message類型,相信大家都有見到過這種寫法——鏈表灼舍。實(shí)際上MessageQueue是一個(gè)單鏈表形式的隊(duì)列
  • ThreadLocal簡(jiǎn)介
    為了便于理解線程與Looper以及MessageQueue之間的關(guān)系吼和,在這里對(duì)ThreadLocal做一個(gè)簡(jiǎn)要的介紹。ThreadLocal可以提供一個(gè)線程的局部變量骑素。每一個(gè)線程通過ThreadLocal#set保存和ThreadLocal#get訪問的數(shù)據(jù)都有它們自己的副本炫乓。也就是說通過ThreadLocal保存的數(shù)據(jù)是線程隔離的。舉個(gè)例子

    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();
    

    事例非常簡(jiǎn)單。在每個(gè)線程中獲取的都會(huì)是該線程本身保存的值厢岂。

  • 接下來看一下ThreadLocal#setThreadLocal#get方法光督,幫助我們理解這個(gè)過程

    //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的一個(gè)內(nèi)部類,在這里我們不深入講它是如何插入和獲取值的塔粒。就當(dāng)它是一個(gè)容器结借。比如HashMap之類的,可以用來保存插入的內(nèi)容卒茬。我們可以看到整個(gè)流程其實(shí)非常的清晰

    1. 調(diào)用ThreadLocal.set實(shí)際上會(huì)為當(dāng)前線程創(chuàng)建一個(gè)容器(ThreadLocalMap)船老,也就是t.threadLocals,最終保存的內(nèi)容是放在這個(gè)容器當(dāng)中的
    2. 調(diào)用ThreadLocal.get實(shí)際上是從當(dāng)前線程取出之前創(chuàng)建的容器(ThreadLocalMap),然后從這個(gè)容器中獲取對(duì)應(yīng)的內(nèi)容圃酵。
    3. 也就是說實(shí)際上每一個(gè)線程都會(huì)有一個(gè)自己的容器(ThreadLocalMap)柳畔,那么保存在里面的內(nèi)容肯定是它自己獨(dú)享的,和其它線程之間不會(huì)互相干擾郭赐。需要注意在線程退出(exit)的時(shí)候會(huì)自動(dòng)釋放該容器薪韩。
  • Looper、MessageQueue之間的關(guān)系

    1. MessageQueue 簡(jiǎn)介
    public final class MessageQueue {
      //Native消息機(jī)制中MessageQueue的地址
      private long mPtr; // used by native code
    
      //處理的消息
      Message mMessages;
      //可以添加一個(gè)IdleHandler捌锭,用于在每次空閑(沒有消息處理)時(shí)回調(diào)俘陷,可以利用該特性進(jìn)行一些優(yōu)化處理一些需要在主線程執(zhí)行又比較耗時(shí)操作,在系統(tǒng)空閑時(shí)才執(zhí)行
      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()方法是否因?yàn)檎{(diào)用pollOnce() 在阻塞
      private boolean mBlocked; 
    
     private native static long nativeInit();
     //阻塞調(diào)用該方法的線程观谦,類似于Object.wait拉盾,不過會(huì)在阻塞timeoutMillis之后喚醒線程
     private native void nativePollOnce(long ptr, int timeoutMillis);
     //喚醒線程以繼續(xù)執(zhí)行
     private native static void nativeWake(long ptr);
    
      MessageQueue(boolean quitAllowed) {
          //是否可以停止
          mQuitAllowed = quitAllowed;
          mPtr = nativeInit();
      }
    
     //消息入隊(duì)
     boolean enqueueMessage(Message msg, long when) {
     
          synchronized (this) {
              msg.when = when;
              Message p = mMessages;
              boolean needWake;
              if (p == null || when == 0 || when < p.when) {
                  // 新消息入隊(duì),隊(duì)列中沒有消息或者新消息不需要延時(shí)豁状,或者新消息的執(zhí)行時(shí)間比當(dāng)前隊(duì)列中最早執(zhí)行的消息執(zhí)行時(shí)間還要早捉偏,插入頭部
                  msg.next = p;
                  mMessages = msg;
                  //如果當(dāng)前正在阻塞即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.
                  //判斷喚醒條件泻红,當(dāng)前消息隊(duì)列頭部消息是屏障消息(也就是正在阻塞)夭禽,且當(dāng)前插入的消息為異步消息,并且當(dāng)前沒有需要處理的消息
                  needWake = mBlocked && p.target == null && msg.isAsynchronous();
                  Message prev;
                  //循環(huán)找到需要插入的地方插入該消息谊路,延時(shí)越長(zhǎng)排在越后面
                  for (;;) {
                      prev = p;
                      p = p.next;
                      if (p == null || when < p.when) {
                          break;
                      }
                      if (needWake && p.isAsynchronous()) {
                          needWake = false;
                      }
                  }
                 //入隊(duì)
                  msg.next = p; // invariant: p == prev.next
                  prev.next = msg;
              }
    
              // We can assume mPtr != 0 because mQuitting is false.
              if (needWake) {
                  //喚醒驻粟,結(jié)束等待,繼續(xù)執(zhí)行
                  nativeWake(mPtr);
              }
          }
          return true;
      }
    
       //消息出隊(duì)
      Message next() {
         
          final long ptr = mPtr;
          if (ptr == 0) {
              return null;
          }
    
          ...
          int nextPollTimeoutMillis = 0;
          for (;;) {
        
              //阻塞nextPollTimeoutMillis時(shí)間凶异,第一次是0,所以會(huì)往下執(zhí)行
              nativePollOnce(ptr, nextPollTimeoutMillis);
    
              synchronized (this) {
                  //獲取系統(tǒng)開機(jī)到現(xiàn)在的時(shí)間
                  final long now = SystemClock.uptimeMillis();
                  Message prevMsg = null;
                  Message msg = mMessages;
                  //msg.target == null表示是屏障信息挤巡,則需要找到隊(duì)列中下一個(gè)異步消息執(zhí)行
                  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) {
                      //比較時(shí)間剩彬,還沒有到要執(zhí)行的時(shí)間,需要等待
                      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.
                          // 不需要等待或者等待時(shí)間到了矿卑,直接返回該消息
                          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.
                  //結(jié)束直接返回空
                  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.
                  //沒有消息的第一時(shí)間,獲取IdleHandler的個(gè)數(shù)
                  if (pendingIdleHandlerCount < 0
                          && (mMessages == null || now < mMessages.when)) {
                      pendingIdleHandlerCount = mIdleHandlers.size();
                  }
                 //沒有設(shè)置IdleHandler,繼續(xù)阻塞
                  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
                  //執(zhí)行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);
                      }
                  }
              }
    
              //重置個(gè)數(shù)轻黑,只執(zhí)行一次
              // 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.
              //執(zhí)行完IdleHandler后糊肤,看看有沒有消息需要執(zhí)行
              nextPollTimeoutMillis = 0;
          }
      }
    
    //向Looper的消息隊(duì)列發(fā)布同步障礙。
    //消息機(jī)制還是在執(zhí)行氓鄙,但是遇到屏障時(shí)馆揉,隊(duì)列中的后續(xù)同步消息將被暫停(阻止執(zhí)行),直到通過調(diào)用removeSyncBarrier并指定標(biāo)識(shí)同步屏障的令牌來釋放屏障
    public int postSyncBarrier() {
          return postSyncBarrier(SystemClock.uptimeMillis());
      }
    }
    
    
    

    MessageQueue是一個(gè)單項(xiàng)鏈表實(shí)現(xiàn)的隊(duì)列抖拦∩ǎ可以看到MessageQueue在消息入隊(duì)的時(shí)候,會(huì)為Message選擇一個(gè)合適的位置插入态罪,延時(shí)時(shí)間越小噩茄,就排在越前面,當(dāng)下一次被喚醒時(shí)复颈。排在前面的就會(huì)先被取出執(zhí)行绩聘。
    需要注意消息出隊(duì)是會(huì)阻塞的。獲取下一個(gè)消息耗啦,如果下一個(gè)消息是可以執(zhí)行的凿菩,也就是now >=msg.when,就直接返回芹彬,如果是延時(shí)消息蓄髓,則阻塞起來,到達(dá)延時(shí)時(shí)間就會(huì)被喚醒舒帮,從而繼續(xù)取出消息執(zhí)行会喝。因?yàn)槿腙?duì)時(shí)經(jīng)過排序,如果隊(duì)頭消息都需要等待玩郊,后面的消息肯定也是需要等待的肢执。這也是延時(shí)消息的實(shí)現(xiàn)原理

    1. 解決前面的問題:為什么需要在子線程中調(diào)用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();
      }
    

    我們可以看到有個(gè)拋出異常预茄,也就是說一個(gè)線程只能創(chuàng)建一個(gè)Looper,也就是只能調(diào)用一次Looper.prepare()。該方法實(shí)際上就是創(chuàng)建了一個(gè)Looper對(duì)象侦厚,并且保存到ThreadLocal中耻陕。利用ThreadLocal,實(shí)現(xiàn)Looper的線程隔離刨沦。默認(rèn)情況下诗宣,非UI線程的Looper為空,所以在子線程中創(chuàng)建Handler時(shí)檢測(cè)拋出異常想诅,需要先調(diào)用Looper.prepare()方法

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

    在創(chuàng)建Looper的時(shí)候就會(huì)創(chuàng)建一個(gè)MessageQueue與其綁定起來召庞。

    1. Looper和MessageQueue的關(guān)聯(lián)
    /**
       * Run the message queue in this thread. Be sure to call
       * {@link #quit()} to end the loop.
       */
        public static void loop() {
          //返回sThreadLocal存儲(chǔ)的Looper實(shí)例
          final Looper me = myLooper();
    
          //loop方法必須在prepare方法之后運(yùn)行
          if (me == null) {
              throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
          }
    
          //獲取Looper實(shí)例中的消息隊(duì)列mQueue
          final MessageQueue queue = me.mQueue;
          ...
          for (;;) {
              //next()方法用于取出消息隊(duì)列中的消息岛心,如果沒有符合的消息或者沒有消息,則線程阻塞
              Message msg = queue.next(); // might block
              if (msg == null) {
                  // No message indicates that the message queue is quitting.
                  return;
              }
    
               //時(shí)間分發(fā)前打印時(shí)間
              // 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);
              }
    
              //把消息派發(fā)給msg的target屬性篮灼,然后用dispatchMessage方法去處理
              msg.target.dispatchMessage(msg);
    
              //消息執(zhí)行完后打印時(shí)間
              if (logging != null) {
                  logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
              }
    
              msg.recycleUnchecked();
          }
      }
    

    可以看到loop()方法就是開啟一個(gè)循環(huán)忘古,一直從消息隊(duì)列中取出消息,并交給Handler#dispatchMessage方法進(jìn)行分發(fā)處理诅诱。在這額外說一下髓堪,在消息分發(fā)前后都調(diào)用了Looper 的Printer 打印了時(shí)間。而BlockCanary正是利用這一點(diǎn)監(jiān)聽卡頓的逢艘。下面看看Handler是如何分發(fā)消息的

  • Handler分發(fā)消息

    //Handler.class
     public void dispatchMessage(Message msg) {
          if (msg.callback != null) {
             //如果msg.callback不為空旦袋,實(shí)際上就是調(diào)用的是post方法,傳入了一個(gè)Runnable對(duì)象它改,直接執(zhí)行
              handleCallback(msg);
          } else {
              if (mCallback != null) { 
                 //如果創(chuàng)建Handler的時(shí)候傳入了Callback 疤孕,則先調(diào)用Callback#handleMessage方法先處理消息
                  if (mCallback.handleMessage(msg)) {
                    //如果Callback#handleMessage方法返回true,則攔截
                      return;
                  }
              }
              //執(zhí)行handleMessage方法
              handleMessage(msg);
          }
      }
    
    private static void handleCallback(Message message) {
         //執(zhí)行Runnable的run方法
          message.callback.run();
      }
    

    Handler分發(fā)消息比較簡(jiǎn)單央拖,先判斷Message 的callback是不是為空祭阀,也就是判斷是不是調(diào)用的post方法,注意post方法傳入的Runnable也是包裝成Message然后入隊(duì)的鲜戒,所以消息延時(shí)一樣適用专控。如果是Runnable,直接執(zhí)行遏餐。否則先判斷Handler的Callback是否為空伦腐,先執(zhí)行Handler#Callback#handleMessage方法,所以我們可以通過傳入一個(gè)Callback對(duì)消息進(jìn)行攔截失都,最后才執(zhí)行Handler#handleMessage方法柏蘑。

Handler消息機(jī)制流程

Handler機(jī)制流程.png
  • Looper.prepare()為每個(gè)線程創(chuàng)建了一個(gè)Looper,每個(gè)Looper會(huì)創(chuàng)建一個(gè)MessageQueue,用于存放Message
  • Looper.loop()開啟一個(gè)循環(huán)粹庞,不斷的從MessageQueue中取出Message并且交由Handler進(jìn)行分發(fā)處理
  • new Handler()會(huì)從線程中取出與線程綁定的Looper咳焚,再?gòu)腖ooper中拿到MessageQueue
  • Handler.sendXXX()一系列發(fā)送消息的方法最終會(huì)通過enqueueMessage方法將Message插入MessageQueue中,此時(shí)Message的target屬性綁定為發(fā)送它的Handler庞溜,最后消息出隊(duì)時(shí)通過Message.target.dispatchMessage方法將Message交回給發(fā)送該Message的Handler進(jìn)行處理革半,最終回到handleMessage.handleMessage方法

為什么非UI線程不能直接更新UI,只能通過Handler機(jī)制更新UI

  • 為什么非UI線程直接更新UI會(huì)報(bào)錯(cuò)

    //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操作的流码,關(guān)于View的繪制流程又官,可以關(guān)注博主,后續(xù)也會(huì)寫一篇文章漫试。ViewRootImpl是在UI線程創(chuàng)建的六敬。我們可以看到每次更新UI都會(huì)檢測(cè)是不是主線程/UI線程。當(dāng)然在Activity#onCreate之前直接在子線程/非UI線程直接更新UI也是可以的商虐,因?yàn)榇藭r(shí)ViewRootImpl并沒有被創(chuàng)建觉阅,它是在Activity#onResume中創(chuàng)建的。不過正常來說在這個(gè)時(shí)候更新UI并沒有什么實(shí)際的作用秘车。

  • 為什么只能通過Handler機(jī)制更新UI

    1. 是因?yàn)榭紤]到多線程并發(fā)更新UI的問題典勇。不知道大家有沒有想過這樣一個(gè)問題:如果非UI線程能夠直接更新UI,那么在有多個(gè)非UI線程同時(shí)更新UI會(huì)發(fā)生什么問題叮趴?
    2. 答案是會(huì)導(dǎo)致界面UI更新錯(cuò)亂割笙。我們都知道,多線程的情況下會(huì)存在一個(gè)并發(fā)的問題眯亦,在這種情況下更新UI就可能造成預(yù)期之外的結(jié)果伤溉。那么怎么處理這個(gè)問題呢?
    3. 處理多線程問題妻率,一般我們會(huì)考慮加鎖乱顾,如果對(duì)更新UI的操作加鎖,會(huì)帶來性能上的問題宫静。更新UI的操作在Android中是非常頻繁的操作走净。如果加鎖了,線程對(duì)鎖的獲取和釋放孤里、線程的切換都會(huì)帶來資源的消耗伏伯,而對(duì)于更新UI的操作,一旦多花了點(diǎn)時(shí)間捌袜,都有可能導(dǎo)致界面的卡頓等不好的結(jié)果说搅。Android 16.6ms內(nèi)要完成一次刷新操作想必都有聽說過。所以加鎖是不適合的虏等。那么應(yīng)該如何處理呢弄唧?
    4. 答案就是通過Handler機(jī)制更新UI,非UI線程將所有更新UI的操作都放到主線程/UI線程中博其,這些更新UI的操作就會(huì)被放到隊(duì)列中一個(gè)接一個(gè)被執(zhí)行套才,即解決了多線程并發(fā)的問題,也避免了加鎖帶來的性能上的消耗慕淡。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末背伴,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子峰髓,更是在濱河造成了極大的恐慌傻寂,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件携兵,死亡現(xiàn)場(chǎng)離奇詭異疾掰,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)徐紧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門静檬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炭懊,“玉大人,你說我怎么就攤上這事拂檩∥旮梗” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵稻励,是天一觀的道長(zhǎng)父阻。 經(jīng)常有香客問我,道長(zhǎng)望抽,這世上最難降的妖魔是什么加矛? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮煤篙,結(jié)果婚禮上斟览,老公的妹妹穿的比我還像新娘。我一直安慰自己舰蟆,他們只是感情好趣惠,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著身害,像睡著了一般味悄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上塌鸯,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天侍瑟,我揣著相機(jī)與錄音,去河邊找鬼丙猬。 笑死涨颜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的茧球。 我是一名探鬼主播庭瑰,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼抢埋!你這毒婦竟也來了弹灭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤揪垄,失蹤者是張志新(化名)和其女友劉穎穷吮,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饥努,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捡鱼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了酷愧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驾诈。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缠诅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出乍迄,到底是詐尸還是另有隱情滴铅,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布就乓,位于F島的核電站,受9級(jí)特大地震影響拱烁,放射性物質(zhì)發(fā)生泄漏生蚁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一戏自、第九天 我趴在偏房一處隱蔽的房頂上張望邦投。 院中可真熱鬧,春花似錦擅笔、人聲如沸志衣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至窖维,卻和暖如春弯淘,著一層夾襖步出監(jiān)牢的瞬間绿店,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工庐橙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留假勿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓态鳖,卻偏偏與公主長(zhǎng)得像转培,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子浆竭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容