Android消息機制Handler解讀

本文基於Android源碼8.1java

概述

在Android系統中使用了不少種通訊的方式,好比進程間通訊使用的Socket,Binder機制等,可是在相同進程不一樣線程之間通訊再使用這些方式就顯得殺雞用牛了,因而Android使用了一種新的Handler消息機制,有了它咱們能夠很方便的進行不一樣線程之間的通訊,固然了Handler消息機制也只能限定在同一個進程中。可是,Android系統中關於Handler消息機制的使用卻不限於此,好比:android的四大組件,事件機制等都和Handler消息機制密切相關。咱們所說的Handler消息機制是由Looper、MessageQueue、Message、Handler等類共同組成的,接下來就經過源碼研究一下handler消息機制的原理。話很少說,先上張圖,下圖雖然簡單,可是它體現了handler消息機制最核心的運行流程,在接下來枯燥而乏味的源碼解讀中,你們能夠結合這張圖去看,思路可能會更清晰些。android

Handler實例

下面先看一個咱們平時使用handler的例子,經過這個這個例子,咱們一步一步去探究handler機制的整個運行的流程。這個例子就是怎麼在一個線程中建立Handler,能夠簡單歸納爲下面的步驟:git

  1. 調用Looper.prepare()方法
  2. 建立Handler對象
  3. 調用Looper.loop()方法
  4. 調用Looper的quit方法結束loop
private void handlerTest(){

       mLooperThread = new LooperThread("xray");
       mLooperThread.start();
       findViewById(R.id.btn_send_msg).setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
               mLooperThread.mHandler.sendEmptyMessage(10);
           }
       });


   }

   class LooperThread extends Thread{

       public Handler mHandler;

       public LooperThread(String name) {
           super(name);
       }

       @Override
       public void run() {
           super.run();

           Looper.prepare();

           mHandler = new Handler(){
               @Override
               public void handleMessage(Message msg) {
                   super.handleMessage(msg);
                   Log.d(TAG, "looperThread thread id = " + Thread.currentThread().getId());
               }
           };
           Looper.loop();
       }
   }

   @Override
   protected void onDestroy() {

       if(mLooperThread != null){
           mLooperThread.mHandler.getLooper().quit();
       }
       super.onDestroy();
   }


複製代碼

Looper

經過上面的例子,咱們在一個線程中建立handler的時候,首先得調用Looper.prepare()方法。github

//Looper.java

/** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    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));
    }

複製代碼

除了prepare()方法,還有一個同名的帶參數的方法,這個參數判斷咱們是否能夠主動退出loop()循環,等一會咱們講到 loop()方法的時候會對這個參數有更深的理解。而後prepare方法建立了Looper對象,並將其實例保存在了sThreadLocal這個成員變量中。關於ThreadLocal我會單獨寫篇文章介紹,這裏只要知道ThreadLocal會保存當前線程中,而且多個線程之間不會互相干擾。緩存

Looper的構造方法:bash

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

複製代碼

咱們發如今Looper的構造方法中,建立了MessageQueue對象,俗稱消息隊列,這個是handler消息機制的另一個主人公。咱們稍後會着重對它進行介紹。總結一下Looper.prepare()所作的工做:數據結構

  1. 建立Looper對象,並將其保存在ThreadLocal中
  2. 在Looper中建立了MessageQueue對象

如今咱們回到上面的例子,先無論Handler的建立,看Looper.loop()方法.app

//Looper.java

/**
    * Run the message queue in this thread. Be sure to call
    * {@link #quit()} to end the loop.
    */
   public static void loop() {
       final Looper me = myLooper();
       if (me == null) {
           throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
       }
       final MessageQueue queue = me.mQueue;

       // Make sure the identity of this thread is that of the local process,
       // and keep track of what that identity token actually is.
       Binder.clearCallingIdentity();
       final long ident = Binder.clearCallingIdentity();

       for (;;) {
           Message msg = queue.next(); // 可能會阻塞在這裏
           if (msg == null) {
               // No message indicates that the message queue is quitting.
               //若是沒有message說明消息隊列正在退出,好比調用了quit方法時
               return;
           }

           // This must be in a local variable, in case a UI event sets the logger
           final Printer logging = me.mLogging;
           if (logging != null) {
               logging.println(">>>>> Dispatching to " + msg.target + " " +
                       msg.callback + ": " + msg.what);
           }

           final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;

           final long traceTag = me.mTraceTag;
           if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
               Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
           }
           final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
           final long end;
           try {
              //從消息隊列中獲得消息後,要將消息進行分發處理,這個target大多數就是handler
               msg.target.dispatchMessage(msg);
               end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
           } finally {
               if (traceTag != 0) {
                   Trace.traceEnd(traceTag);
               }
           }
           if (slowDispatchThresholdMs > 0) {
               final long time = end - start;
               if (time > slowDispatchThresholdMs) {
                   Slog.w(TAG, "Dispatch took " + time + "ms on "
                           + Thread.currentThread().getName() + ", h=" +
                           msg.target + " cb=" + msg.callback + " msg=" + msg.what);
               }
           }

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

           // Make sure that during the course of dispatching the
           // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } //消息進行回收 msg.recycleUnchecked();//todo,介紹消息回收的方式 } } 複製代碼

代碼比較長,重要的地方我作了註釋,這個方法被調用後Looper就啓動了,歸納一下該方法作的主要工做:less

  1. 有個for循環,在循環中不斷的從消息隊列中獲取消息,而且獲取的方法可能會被阻塞。
  2. 將獲取到的Message分發出去, msg.target.dispatchMessage(msg),這個target大多數狀況就是Handler.
  3. 將Message進行回收,以即可以複用,下文會詳細介紹。

mylooper()

從ThreadLocal中獲取當前線程的Looper對象async

/**
    * Return the Looper object associated with the current thread.  Returns
    * null if the calling thread is not associated with a Looper.
    */
   public static @Nullable Looper myLooper() {
       return sThreadLocal.get();
   }

複製代碼

quit()和quitSafely()

退出loop循環的方法,其實他真正的實如今MessageQueue中。說一下二者的區別,quit方法是將MessageQueue中全部的消息所有清除,而後退出loop, quitSafely方法是將此時此刻,尚未到執行時間的消息清除,可是已經達到執行時間了,可是還沒來得及執行的消息會保留,等執行完了再退出loop.

/**

   public void quit() {
       mQueue.quit(false);
   }


   public void quitSafely() {
       mQueue.quit(true);
   }

複製代碼

MessageQueue

在上面講Handler的時候,咱們屢次提到了MessageQueue,下面就介紹一下MessageQueue的原理。

next()

next方法的做用就是從消息隊列中取出Message,固然具體不是一句話這麼簡單,下面看看其內部的實現。

//MessageQueue.java

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 pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            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;
                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.
                        //若是下一條message被延時,設置一個延時,等時間到了再去返回該Message
                        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.
                    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.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    //若是沒有idle handlers須要執行,Loop將輸入等待狀態,也就是,next方法處於阻塞的狀態,此處執行調到下一次循環,
                    //直到有新的消息,或者loop被終止,或則有idle handlers 須要執行
                    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

                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.
            //重置idle handler的個數爲0, 須要等下次再沒有可執行的Message執行時,idle handler才能繼續執行
            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.
            //須要重置這個過時時間,由於有可能有新的message須要執行,因此須要的檢查
            nextPollTimeoutMillis = 0;
        }
    }


複製代碼

經過上面的代碼,咱們知道MessageQueue中維護了一個鏈表,在從隊列中獲取消息時,是根據消息真正的執行時間來取出的,若是這段時間空閒,也就是獲取Message處於阻塞狀態的時候,會回調IdleHandler,假使咱們設置了它,若是當前的Message的執行時間沒到,又沒有IdleHandler須要處理,那麼程序就會阻塞在這裏。看到這裏若是你們夠細心的話,必定能推測出MessageQueue中的Message必定是按時間排好序的,不然Message的分發順序就會有問題,排序的邏輯就在enqueueMessage方法中。

enqueueMessage

enqueueMessage方法的做用是往消息隊列中添加消息,而且在插入的時候會以消息執行的時間進行排序。下面咱們看看具體的代碼實現,其實仍是對鏈表的操做。

//MessageQueue.java

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    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();//回收message todo
            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; //按時間去排序,將message插入到隊列相應的位置 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執行的時間進行對比,對於延時的消息,MessageQueue會遍歷整個鏈表,直到找到合適的插入的位置。

Message

Message顧名思義是handler消息機制中的那個消息,Handler發送和處理的實體就是這個它.

obtain()

當咱們平時須要Message實例時,能夠直接new Message(),也能夠調用Message.obtain()方法,可是更推薦 使用後者,由於Message中有個Message的緩存池,這個緩存池的大小是50(從MAX_POOL_SIZE這個常量值能夠獲得), 而obtain()方法會先從緩存池中獲取,這個緩存池也是用鏈表實現的。若是obtain()獲取不到Message實例,纔會從新new

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
              //message pool也是用鏈表實現的
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }


複製代碼

recycle()和recycleUnchecked()

這兩個方法的做用是將使用完的Message對象進行回收,從新放入都Message緩存池中,以便下次使用,其實代碼很簡單, 仍是對鏈表的操做,有沒有發現鏈表這種數據結構真的使用的不少。

/**
     * Return a Message instance to the global pool.
     * <p>
     * You MUST NOT touch the Message after calling this function because it has
     * effectively been freed.  It is an error to recycle a message that is currently
     * enqueued or that is in the process of being delivered to a Handler.
     * </p>
     */
    public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

    /**
     * 該方法可能回收還在使用的Message
     */
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {//Message緩存池大小爲50
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

複製代碼

Handler

咱們講Handler消息機制,如今終於輪到Handler了,它在整個流程中就是對Message進行發送和處理。

Handler構造方法

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();//從當前的線程中獲取Looper, todo
      if (mLooper == null) {//在線程中建立handle時,須要先調用Looper.prepare()
          throw new RuntimeException(
              "Can't create handler inside thread that has not called Looper.prepare()");
      }
      mQueue = mLooper.mQueue; //Looper中建立的消息隊列
      mCallback = callback;//處理message的回調
      mAsynchronous = async;
  }


複製代碼

發送Message

發送消息其實最終就是將根據Message的執行時間,將其插入到MessageQueue中。

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

複製代碼

分發消息

Looper在調用loop()方法的時候,當遇到符合條件的Message,就會調用Handler的dispatchMessage方法, 用來分發Message,這樣咱們就能夠在Handler中處理Message了。

/**
   * Handle system messages here.
   */
  public void dispatchMessage(Message msg) {
      if (msg.callback != null) {
          handleCallback(msg);
      } else {
          if (mCallback != null) {
              if (mCallback.handleMessage(msg)) {
                  return;
              }
          }
          handleMessage(msg);
      }
  }

複製代碼

IdleHandler

前面在講MessageQueue的next的方法的時候見到過IdleHandler,當咱們取消息處於阻塞狀態的時候,若是添加了IdleHandler,就會處理它,因此咱們能夠把一些不那麼重要的操做放到IdleHandler中執行,這樣能夠顯著的提升性能。好比著名的內存泄漏檢測庫leakarary中關於內存泄漏檢測的操做就放到了IdleHandler中執行。

/**
    * Callback interface for discovering when a thread is going to block
    * waiting for more messages.
    */
   public static interface IdleHandler {

      /**
        * 在該方法中執行咱們須要執行的任務,若是該任務是一次性的則返回false,若是該任務須要屢次
        * 執行則返回true
        */
       boolean queueIdle();
   }


複製代碼

總結

到這裏Java層的Handler機制就講完了,限於篇幅的緣由和做者的水平,有些地方沒有很深刻的講解,在此說聲抱歉,可是大體的流程應該是有的,建議讀者去仔細的讀一下這塊的源碼,相信收穫會不小。前面說了這只是Java層面的Handler消息機制,其實在Native層,也有一套C++的實現,有興趣的小夥伴也能夠看看這個的內容。

相關文章
相關標籤/搜索