Handler原理分析

Handler的原理分析這個標題,不少文章都寫過,最近認真將源碼逐行一字一句研究,特此也簡單總結一遍。java

首先是Handler整個Android消息機制的簡單歸納:android

分三部分對消息機制的整個流程進行闡述:git

  • Handler的建立,包括LooperMessageQueue的建立;
  • Handler發送消息,Message是如何進入消息隊列MessageQueue的(入列);
  • Looper輪詢消息,Message出列,Handler處理消息。

1、Handler建立流程分析

1.Handler如何被建立的

// 最簡單的建立方式
public Handler() {
    this(null, false);
}

// ....還有不少種方式,但這些方式最終都執行這個構造方法
public Handler(Callback callback, boolean async) {
  // 1.檢查內存泄漏
  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());
      }
  }

  // 2.經過Looper.myLooper()獲取當前線程的Looper對象
  mLooper = Looper.myLooper();
  // 3.若是Looper爲空,拋出異常
  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的內存泄漏是一個很是常見的面試題,其實Handler的源碼中已經將答案很是清晰告知給了開發者,即讓Handler的導出類保證爲static的,若是須要,將Context做爲弱引用的依賴注入進來。github

同時,在Handler建立的同時,會嘗試獲取當前線程惟一的Looper對象:面試

public final class Looper {

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

  public static @Nullable Looper myLooper() {
      return sThreadLocal.get();
  }
}
複製代碼

關於ThreadLocal,我在上一篇文章中已經進行了分析,如今咱們知道了ThreadLocal保證了當前線程內有且僅有惟一的一個Looper數據結構

2.Looper是如何保證線程單例的

那就是須要調用Looper.prepare()方法:app

public final class 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));
   }

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

這也就說明了,爲何當前線程沒有Looper的實例時,會拋出一個異常並提示開發者須要調用Looper.prepare()方法了。async

也正如上述代碼片斷所描述的,若是當前線程已經有了Looper的實例,也會拋出一個異常,提示用戶每一個線程只能有一個Looperthrow new RuntimeException("Only one Looper may be created per thread");)。ide

此外,在Looper實例化的同時,也建立了對應的MessageQueue,這也就說明,一個線程有且僅有一個Looper,也僅有一個MessageQueueoop

2、發送消息流程分析

1.sendMessage()分析

sendMessage()流程以下:

// 1.發送即時消息
public final boolean sendMessage(Message msg) {
    return sendMessageDelayed(msg, 0);
}

// 2.其實是發射一個延時爲0的Message
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
   if (delayMillis < 0) {
       delayMillis = 0;
   }
   return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

// 3.將消息和延時的時間進行入列(消息隊列)
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);
}

// 4.內部實際上仍是執行了MessageQueue的enqueueMessage()方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼

注意第四步實際上將Handler對象最爲target,附着在了Message之上;接下來看MessageQueue類內部是如何對Message進行入列的。

2.MessageQueue消息入列

boolean enqueueMessage(Message msg, long when) {
    //... 省略部分代碼
    synchronized (this) {
        msg.markInUse();
        msg.when = when;
        // 得到鏈表頭的Message
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // 如有如下情景之一,將Message置於鏈表頭
            // 1.頭部Message爲空,鏈表爲空
            // 2.消息爲即時Message
            // 3.頭部Message的時間戳大於最新Message的時間戳
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // 反之,將Message插入到鏈表對應的位置
            Message prev;
            // for循環就是找到合適的位置,並將最新的Message插入鏈表
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
複製代碼

MessageQueue的數據結構自己是一個單向鏈表

3、接收消息分析

Handler建立好後,若在此以前調用了Looper.prepare()初始化Looper,還須要調用Looper.loop()開始該線程內的消息輪詢。

1.Looper.loop()

public static void loop() {
    // ...省略部分代碼
    // 1. 獲取Looper對象
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // 2.獲取messageQueue
    final MessageQueue queue = me.mQueue;
    // 3. 輪詢消息,這裏是一個死循環
    for (;;) {
        // 4.從消息隊列中取出消息,若消息隊列爲空,則阻塞線程
        Message msg = queue.next();
        if (msg == null) {
            return;
        }

        // 5.派發消息到對應的Handler
        msg.target.dispatchMessage(msg);
        // ...
    }
}
複製代碼

比較簡單,須要注意的一點是MessageQueue.next()是一個可能會阻塞線程的方法,當有消息時會輪詢處理消息,但若是消息隊列中沒有消息,則會阻塞線程。

2.MessageQueue.next()

private native void nativePollOnce(long ptr, int timeoutMillis);


Message next() {

    // ...省略部分代碼
    int nextPollTimeoutMillis = 0;

    for (;;) {
      // ...

    // native方法
    nativePollOnce(ptr, nextPollTimeoutMillis);

    synchronized (this) {

        final long now = SystemClock.uptimeMillis();
        Message prevMsg = null;
        Message msg = mMessages;

        // 從消息隊列中取出消息
        if (msg != null) {
            // 當時間小於message的時間戳時,獲取時間差
            if (now < msg.when) {
                // 該值將會致使在下次循環中阻塞對應時間
                nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
            } else {
                // 取出消息並返回
                mBlocked = false;
                if (prevMsg != null) {
                    prevMsg.next = msg.next;
                } else {
                    mMessages = msg.next;
                }
                msg.next = null;
                msg.markInUse();
                return msg;
            }
        }
        // ...
   }
}
複製代碼

注意代碼片斷最上方的native方法——循環體內首先調用nativePollOnce(ptr, nextPollTimeoutMillis),這是一個native方法,實際做用就是經過Native層的MessageQueue阻塞nextPollTimeoutMillis毫秒的時間:

  • 1.若是nextPollTimeoutMillis=-1,一直阻塞不會超時。
  • 2.若是nextPollTimeoutMillis=0,不會阻塞,當即返回。
  • 3.若是nextPollTimeoutMillis>0,最長阻塞nextPollTimeoutMillis毫秒(超時),若是期間有程序喚醒會當即返回。

搞清楚這一點,其它就都好理解了。

3.最終將消息發送給Handler

正如上文所說的,msg.target.dispatchMessage(msg)實際上就是調用Handler.dispatchMessage(msg),內部最終也是執行了Handler.handleMessage()回調:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        // 若是消息沒有定義callBack,或者不是經過
        // Handler(Callback)的方式實例化Handler,
        // 最終會走到這裏
        handleMessage(msg);
    }
}
複製代碼

參考&感謝


關於我

Hello,我是卻把清梅嗅,若是您以爲文章對您有價值,歡迎 ❤️,也歡迎關注個人博客或者Github

若是您以爲文章還差了那麼點東西,也請經過關注督促我寫出更好的文章——萬一哪天我進步了呢?

相關文章
相關標籤/搜索