Handler源碼分析

Handler

引言

Handler是爲了解決非UI線程中UI更新的問題,這裏會產生一個疑問。爲啥要在UI線程中更新,通常都知道會產生卡頓問題。html

基本概念

上張官方關係類圖,壓壓驚:

能夠看到他有四個子類,前面兩個是與異步數據庫操做相關的(contentProvider),後面兩個是與網絡請求(一個是處理http受權,另外一個是處理SSL錯誤請求)android

MessageQueue與Message,Looper,Handler數據庫

Handler做爲入口

Handler 主要功能是發送消息和處理接受消息。通常經常使用的 handler.sendEmptyMessage(int what)sendMessage(Message msg) 方法,下面就從 sendEmptyMessage(int what) 方法看看它是怎麼運做的?緩存

sendEmptyMessage 內部調用關係以下:網絡

sendEmptyMessage => sendEmptyMessageDelayed => sendMessageDelayed => enqueueMessageapp

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        //構造新的消息 使用sPool全局池保存回收的消息鏈表進行空消息緩存 
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }
    ...
   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);
    }

疑問:

  • obtain(奧普攤)方法是如何構造消息的?看了後發現recycle與之相關的。
  • 這裏發現產生的 uptimeMillis沒有被用到,那他是用來幹什麼的?(uptimeMillis是開機到如今的毫秒數,內部實現是一個native方法 )
  • 設置 msg.setAsynchronous 怎麼使用其值?

obtain方法如何構造消息的以及recycle方法的做用

看了obtain方法後發現,sPool默認是static,因此屬於Message類,共享的,沒有初始化,那它何時進行賦值。答案就在recycle方法,顧名思義其實做用至關於回收那些沒用的消息,那這個是怎麼工做的?異步

...
  //recycle內部調用的recycleUnchecked函數
  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) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

能夠本身紙上畫下,下面看圖說話:
ide

private 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;
            }
        }
        return new Message();
    }

直接上圖:
函數

  • 總結來講,其實生成新消息是取用了相似單鏈表的全局池,有個前提條件是已經有消息調用recycle方法,否則生成的消息都是new出來的。

MessageQueue 幹了啥

歸納:MessageQueue中使用一個單鏈表來維護消息隊列。經過Looper對象分發消息,可是不能直接把Message入隊,須要經過Looper關聯的Handler來發送消息。oop

關鍵看兩個方法:enqueueMessage 與 next(後續分析) 方法,前面一個是消息入隊,後面一個是消息出隊。

先來看看 enqueueMessage 方法,首先它有兩個參數:消息--msg 和觸發時間--when,簡單來講,若是p爲空的話直接賦值 mMessages = msg,若是p不爲空的話則須要根據觸發時間--when插入到鏈表合適的位置。

boolean enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
           ...
            //消息被標記使用
            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 {
               ...
                //關鍵代碼
                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;
    }
  • 先看下新消息須要放到隊頭的狀況:p == null || when == 0 || when < p.when。即隊列爲空,或者新消息須要當即處理,或者新消息處理的事件比隊頭消息更早被處理。這時只要讓新消息的next指向當前隊頭,讓mMessages指向新消息便可完成插入操做。
  • 除了上面狀況以外,須要遍歷鏈表並比較when的值,因爲這個鏈表根據when時間從小到大的,只要找到第一個鏈表節點的when比插入消息的when大或者沒找到,跳出循環,並插入鏈表。

Looper類

prepare()

Looper的構造函數修飾符是private的,那麼他是在哪裏被實例化?經過搜索,發現 prepare()prepareMainLooper()兩個方法。

  • prepare()方法官方說明;

    做爲looper初始化當前線程。提供一個機會來建立handler並使用looper。在使用以前,請在此方法以後調用loop(),並在結束時調用quit()

  • prepareMainLooper()方法官方說明;

    做爲looper初始化當前線程,並標記其爲application主線程的looper。在application主線程中的looper被Android系統建立,所以開發者請永遠不要手動調用這個方法。

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

看上面的prepareMainLooper()代碼,發現作了兩個事: prepare(false)myLooper()

  • prepare(false)方法中的sThreadLocal字段的類型是 ThreadLocal,用來幹什麼呢?實現一個線程本地存儲,一個讓每一個線程都有擁有value的變量。另外,能夠看看構造函數Looper幹了些什麼事情?
private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
  • 對於myLooper()來講,prepare()中提到了會將looper放到線程存儲ThreadLocal中,此處只須要從中取出並返回便可,所以代碼只有一行return sThreadLocal.get();

  • 疑問
    在activity當中實例的Handler,咱們並無調用loop方法?

查看prepareMainLooper()調用者能夠看到,在SystemServer.run()與ActivityThread.main()中都在調用Looper.prepareMainLooper()後不遠就調用了Looper.loop()。而這兩處能夠推斷一個是系統應用的主線程,一個是用戶應用的主線程,然後面這個就是答案。

loop()

  • loop主要是經過死循環來重複作某件事的方法。
public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                return;
            }
            msg.target.dispatchMessage(msg);
            msg.recycleUnchecked();
        }
    }

這裏就一目瞭然了:一個死循環,不斷從隊列中取消息並分發,若是取到null就說明消息隊列已經退出或被釋放,此時loop終止。msg.target.dispatchMessage(msg)中target即是在發送消息的handler對象,dispatchMessage()即是對消息的處理了。

總結

  • 上面幾個基本概念的關係以下圖:

參考資源

相關文章
相關標籤/搜索