帶你真正攻克Handler

先來一個本身畫的Handler機制總體流程圖,本文不會帶着你走一遍源碼,只會對重點須要注意的地方以及一些細節的處理作出解釋,讓你更好的瞭解Handler機制總體的運做bash

在這裏插入圖片描述

  • Handler經過sendMessage()發送Message到MessageQueue隊列;
  • Looper經過loop(),不斷提取出達到觸發條件的Message,並將Message交給target來處理;
  • 通過dispatchMessage()後,交回給Handler的handleMessage()來進行相應地處理。
  • 將Message加入MessageQueue時,處往管道寫入字符,能夠會喚醒loop線程;
  • 若是MessageQueue中沒有Message,並處於Idle狀態,則會執行IdelHandler接口中的方法,每每用於作一些清理性地工做。

下邊放幾個須要注意的Handler知識點:

  1. Handler 的背後有 Looper、MessageQueue 支撐,Looper 負責消息分發,MessageQueue 負責消息管理;
  2. 在建立 Handler 以前必定須要先建立 Looper;
  3. Looper 有退出的功能,可是主線程的 Looper 不容許退出;
  4. 異步線程的 Looper 須要本身調用 Looper.quit(); 退出;
  5. Runnable 被封裝進了 Message,能夠說是一個特殊的 Message;
  6. Handler.handleMessage() 所在的線程是 Looper.loop() 方法被調用的線程,也能夠說成 Looper 所在的線程,並非建立 Handler 的線程,Handler新建時持有的Looper在哪一個線程,最後Handler.handleMessage()就在哪一個線程執行
  7. 使用內部類的方式使用 Handler 可能會致使內存泄露,即使在 Activity.onDestroy 裏移除延時消息,必需要寫成靜態內部類;

爲何須要使用Handler

由於Android系統不容許在非UI線程更新UI,由於若是多個線程同時改變View的狀態會形成最終View狀態的不肯定性,若是給每一個View的操做都上鎖的話那麼勢必會形成性能的損耗,因此乾脆規定只能在UI線程去更新UI,而Handler就是用來進行線程切換操做的。 使用方法框架

class LooperThread extends Thread {
    public Handler mHandler;


    public void run() {
        Looper.prepare();  
        mHandler = new Handler() {  
            public void handleMessage(Message msg) {
                //TODO 定義消息處理邏輯.
            }
        };
        Looper.loop();  
    }
}
複製代碼

主線程中可使用Handler的緣由是在ActivityThread中程序的入口main方法中調用了Looper.prepare();和Looper.loop();異步

Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
    sMainThreadHandler = thread.getHandler();
}
if (false) {
    Looper.myLooper().setMessageLogging(new
            LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
複製代碼

Looper

  • Looper.prepare() Looper.prepare()在每一個線程只容許執行一次,該方法給當前線程經過TL綁定一個線程所屬的惟一一個實例。
private static void prepare(boolean quitAllowed) {
    //看當前線程是否已經過TL綁定對應的實例,有的話拋異常,因此prepare方法只容許調用一次
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //建立Looper對象,並經過TL創建與線程的綁定關係
    sThreadLocal.set(new Looper(quitAllowed));
}
複製代碼
  • ThreadLocal 咱們看一下ThreadLocal.set方法
public void set(T value) {
   Thread t = Thread.currentThread();//獲取當前線程
   ThreadLocalMap map = getMap(t);//獲取當前線程所屬的ThreadLocalMap實例,鍵值對結構
   if (map != null)
       map.set(this, value); //以當前ThreadLocal做爲鍵,Looper做爲值創建綁定關係
   else
       createMap(t, value);
   }
}
複製代碼

ThreadLocal.get方法async

public T get() {
    Thread t = Thread.currentThread();//獲取當前線程
    ThreadLocalMap map = getMap(t);//獲取當前線程所屬的ThreadLocalMap實例,鍵值對結構
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);//經過當前ThreadLocal做爲鍵取出對應的值
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
複製代碼

爲何要選擇ThreadLocal創建綁定關係?

由於咱們是要讓每個線程都有且只有一個惟一的Looper實例,這時就可使用ThreadLocal給每一個線程綁定一個惟一實例的特性很方便的創建綁定關係。若是不採用ThreadLocal去實現,那麼只能使用一個LooperManager管理類而後經過其中的Map去統一管理,那麼這樣無疑是很麻煩的。ThreadLocal只是做爲主鍵,若是是Thread做爲主鍵,那麼很顯然一個線程只能與一個對應的對象創建綁定關係,這顯然是很是不合理的。ide

  • Looper.loop() loop()進入循環模式,主要進行了以下幾點:
    1. 獲取當前線程的Looper實例
    2. 經過Looper獲取MessageQueue實例
    3. 開啓死循環並在其中調用MessageQueue的next方法不斷輪詢MessageQueue的頭結點
public static void loop() {
    final Looper me = myLooper();  //獲取TLS存儲的Looper對象 -->sThreadLocal.get()
    final MessageQueue queue = me.mQueue;  //獲取Looper對象中的消息隊列


    Binder.clearCallingIdentity();
    //確保在權限檢查時基於本地進程,而不是調用進程。
    final long ident = Binder.clearCallingIdentity();


    for (;;) { //進入loop的主循環方法
        Message msg = queue.next(); //可能會阻塞 
        if (msg == null) { //沒有消息則退出循環,調用Looper.quit()方法後返回空的message,隨即退出
            return;
        }

        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        msg.target.dispatchMessage(msg); //用於分發Message 
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        final long newIdent = Binder.clearCallingIdentity();
        msg.recycleUnchecked(); 
    }
}
複製代碼
  • Looper.quit() 用於終止loop循環,主線程中不容許調用(能調用的話至關於主程序退出了,應用就直接掛掉了),子線程中要退出時須要主動調用,不然會形成子線程中一直處於死循環狀態沒法退出。調用後會改變MessageQueue中的mQuitting標誌位,next方法中若是檢測到mQuitting爲true則直接返回null,loop方法中檢測到message是null則直接return終止死循環從而結束邏輯使得線程能夠退出。
public void quit() {
    mQueue.quit(false); //所有消息移除
}


public void quitSafely() {
    mQueue.quit(true); //只移除沒有執行的消息
}
複製代碼

如何作到延遲發送消息

  • 會根據Message發送時的時間戳肯定Message在MessageQueue中的位置。函數

    1. 放入Message時會根據msg.when這個時間戳進行順序的排序,若是非延遲消息則msg.when爲系統當前時間,延遲消息則爲系統當前時間+延遲時間(如延遲發送3秒則爲SystemClock.uptimeMillis() + 3000)
    2. 將Message放入MessageQueue時會以msg.when對msg進行排序確認當前msg處於單鏈表中的位置,分爲幾種狀況: (1)頭結點爲null(表明MessageQueue沒有消息),Message直接放入頭結點。 (2) 頭結點不爲null時開啓死循環遍歷全部節點,退出死循環的條件是: 1.遍歷出的節點的next節點爲null(說明當前鏈表已經遍歷到了末尾,將放入的Message放入next節點). 2.遍歷出的節點的when大於放入message的when(說明當前message是一個比放入message延遲更久的消息,將放入的Message放入當前遍歷的Message節點以前).
  • 當咱們發送消息的時候其實最後都會調用到sendMessageAtTime這個方法,這個方法其實最終會把你的Handler對象賦值給Message實體,咱們最終發送消息都是發送的Message實體,而後調用MessageQueue的enqueueMessage方法oop

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);
}
複製代碼
  • MessageQueue 是一個單鏈表結構,其中的Message節點是以Message放入MessageQueue的時間去進行順序肯定的(小的在前大的再後),這樣就完成了消息的延遲發送
boolean enqueueMessage(Message msg, long when) {
    // 每個普通Message必須有一個target
    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) {  //正在退出時,回收msg,加入到消息池
            msg.recycle();
            return false;
        }
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) { //p爲null(表明MessageQueue沒有消息) 或者msg的觸發時間是隊列中最先的, 則進入該分支並將加入的message放入頭結點
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked; //當阻塞時須要喚醒
        } else {
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {                             //開啓死循環遍歷message
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {  //退出條件爲當前message的下一個節點爲null或者當前節點的message執行時間大於你放入message的執行時間
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p;                           //進行賦值
            prev.next = msg;
        }
        //消息沒有退出,咱們認爲此時mPtr != 0
        if (needWake) {                        
            nativeWake(mPtr);                        //往管道中寫數據,喚醒阻塞,nativePollOnce方法阻塞解除
        }
    }
    return true;
}
複製代碼

MessageQueue的機制

  • Message的入列和出列實際上是一個很典型的**生產者-消費者模型**,其中使用了epoll機制,當沒有消息的時候會進行阻塞釋放CPU時間片避免死循環形成性能的浪費。雖然是不斷循環取出頭結點的Message進行分發處理可是若是沒有消息時它是阻塞在 nativePollOnce這個native方法中的當咱們enqueue插入Message時會觸發nativeWake這個方法去喚醒,從而nativePollOnce阻塞解除繼續遍歷MessageQueue取出頭結點去處理。
  • Looper.loop()在一個線程中調用next()不斷的取出消息,另一個線程則經過enqueueMessage向隊列中插入消息,因此在這兩個方法中使用了synchronized (this) {}同步機制,其中this爲MessageQueue對象,無論在哪一個線程,這個對象都是同一個,由於Handler中的mQueue指向的是Looper中的mQueue,這樣防止了多個線程對同一個隊列的同時操做(如增長的同時正在輪詢獲取Message,有可能形成MessageQueue中最終結果的不肯定性)。
Message next() {
    final long ptr = mPtr;
    if (ptr == 0) { //當消息循環已經退出,則直接返回
        return null;
    }
    int pendingIdleHandlerCount = -1; // 循環迭代的首次爲-1
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //阻塞操做,當等待nextPollTimeoutMillis時長,或者消息隊列被喚醒,都會返回
        //nextPollTimeoutMillis 爲-1,一直阻塞,在調用nativeWake(enqueue Message或Looper.quit()退出Looper)時會被喚醒解除阻塞
        //nextPollTimeoutMillis 爲0,不阻塞
        //nextPollTimeoutMillis 爲>0,阻塞到對應時間後解除,如爲10000則阻塞十秒後解除,用於處理延遲消息
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                //當消息Handler爲空時,查詢MessageQueue中的下一條異步消息msg,則退出循環。
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                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;
                    //設置消息的使用狀態,即flags |= FLAG_IN_USE
                    msg.markInUse();
                    return msg;   //成功地獲取MessageQueue中的下一條即將要執行的消息
                }
            } else {
                //沒有消息,阻塞
                nextPollTimeoutMillis = -1;
            }
            //消息正在退出,返回null
            if (mQuitting) {
                dispose();
                return null;
            }
            //當消息
複製代碼

Message分發的三個的優先級

當遍歷出Message後Message會獲取其中的Handler並調用Handler的dispatchMessage進行分發,這時也會有三個優先級。post

  1. Message的回調方法:message.callback.run(),優先級最高; 對應handler.post(new Runnable)的方式發送消息
  2. Handler的回調方法:Handler.mCallback.handleMessage(msg),優先級僅次於1; 對應新建Handler時傳進CallBack接口 Handler handler=new Handler(new Handler.Callback()....(一般咱們能夠利用 Callback 這個攔截機制來攔截 Handler 的消息,場景如:Hook ActivityThread.mH,在 ActivityThread 中有個成員變量 mH ,它是個 Handler,又是個極其重要的類,幾乎全部的插件化框架都使用了這個方法。)
  3. Handler的默認方法:Handler.handleMessage(msg),優先級最低。 對應新建Handler並複寫handleMessage方法
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {             //callback是一個runnable對象
        handleCallback(msg);
    } else {
        if (mCallback != null) {            //mCallback是新建Handler時傳進去的Callback接口
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);                //默認空實現,通常咱們會本身複寫實現這個方法
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}
複製代碼
  1. 將 Runnable post 到主線程執行(不少第三方框架都使用的這種方式方便的完成主線程的切換,這也是爲何有handler.post(new Runnable)這種方式去發送消息)。
  2. 利用 Looper 判斷當前線程是不是主線程。
public final class MainThread {
    private MainThread() {
    }

    private static final Handler HANDLER = new Handler(Looper.getMainLooper());

    public static void run(@NonNull Runnable runnable) {
        if (isMainThread()) {
           runnable.run();
        }else{
            HANDLER.post(runnable);
        }
    }

    public static boolean isMainThread() {
        return Looper.myLooper() == Looper.getMainLooper();
    }

}
複製代碼

Handler線程調度的實質

Handler的實質其實就是共享內存,咱們看一個例子。性能

public class Demo {
    List mList= new ArrayList()<Message>;
    public static void main(String[] args) {
        //子線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000l);
                    mList.add(new Message());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }
        }).start();
        //主線程開啓死循環不斷遍歷list取頭結點
        for (;;) {
            //主線程中處理
            Message message = mList.get(0);
            if (message != null) {
                //處理
            }
        }

    }
}
複製代碼

咱們爲了將數據最終從子線程切換到主線程中去其實只要拿到mList這個實例便可,這個mList對應的其實就是MessageQueue,而咱們要獲取MessageQueue只要獲取對應的Looper便可,當咱們Handler新建的時會根據Handler所在線程獲取到其線程正在輪詢消息的Looper對象,Handler中的mQueue指向的是其所在線程的Looper中的mQueue(固然也能夠手動指定一個其餘線程的Looper,不指定的話默認爲當前線程的Looper),由此即可在發送Message時將任務放到Looper所在線程中處理。ui

public Handler(Callback callback, boolean async) {
    ...
    mLooper = Looper.myLooper(); //threadLocal.get獲取線程對應的Looper
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;     //經過Looper獲取MessageQueue
    mCallback = callback;
    mAsynchronous = async;
}
複製代碼

Looper.loop是一個死循環,拿不到須要處理的Message就會阻塞,那在UI線程中爲何不會致使ANR?

首先咱們來看形成ANR的緣由:

  1. 當前的事件沒有機會獲得處理(即主線程正在處理前一個事件,沒有及時的完成或者looper被某種緣由阻塞住了)。
  2. 當前的事件正在處理,但沒有及時完成。

咱們再來看一下APP的入口ActivityThread的main方法:

public static void main(String[] args) {
  
        ...

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
複製代碼

咱們知道Android 的是由事件驅動的,looper.loop() 不斷地接收事件、處理事件,每個點擊觸摸或者說Activity的生命週期都是運行在 Looper的控制之下,若是它中止了,應用也就中止了。真正的阻塞是由於輪詢出message後在處理message消息的時候因爲執行了耗時操做致使了ANR,而不是死循環致使的阻塞,沒有消息處理的時候消息隊列是阻塞在nativePollOnce方法中的,**這個方法使用的是epoll管道機制,Linux底層執行後會釋放CPU避免不斷死循環形成的CPU浪費。**

Handler 引發的內存泄露緣由以及最佳解決方案

當咱們用Handler發送延時消息時,若是在延時期間用戶關閉了 Activity,那麼該 Activity 會泄露。這個泄露是由於 Message 會持有 Handler,而又由於 Java 的特性,內部類會持有外部類,使得 Activity 會被 Handler 持有,這樣最終就致使 Activity 泄露。 解決該問題的最有效的方法是:將 Handler 定義成靜態的內部類(靜態內部類不會持有外部類引用,可是靜態內部類調用不到外部類的非靜態屬性和方法,因此咱們須要在內部類中使用弱引用持有Activity,使用弱引用調用到Activity中的方法),並及時移除全部消息。 泄漏時的引用鏈 Activity->Handler->Message->MessageQueue ,延遲消息會一直在MessageQueue中等待處理,在等待的過程當中有可能會形成內存泄漏。 示例代碼以下:

private static class SafeHandler extends Handler {

    private WeakReference<HandlerActivity> ref;

    public SafeHandler(HandlerActivity activity) {
        this.ref = new WeakReference(activity);
    }

    @Override
    public void handleMessage(final Message msg) {
        HandlerActivity activity = ref.get();
        if (activity != null) {
            activity.handleMessage(msg);
        }
    }
}
複製代碼

而且再在 Activity.onDestroy() 前移除消息,加一層保障:

@Override
protected void onDestroy() {
  safeHandler.removeCallbacksAndMessages(null);
  super.onDestroy();
}
複製代碼

這樣雙重保障,就能徹底避免內存泄露了。 注意:單純的在 onDestroy 移除消息並不保險,由於 onDestroy 並不必定執行(如報異常)。

IdleHandler 是什麼

從下邊MessageQueue的源碼可知道,IdleHandler即在MessageQueue應該被阻塞以前去調用(固然前提是你要講自定義的IdleHandler加入到集合中)IdleHandler接口表示當MessageQueue發現當前沒有更多消息能夠處理的時候, 則順便乾點別的事情的callback函數(即若是發現idle了, 那就找點別的事幹). callback函數有個boolean的返回值, 表示是否keep. 若是返回false, 則它會在調用完畢以後從mIdleHandlers中移除. IdleHandler 能夠用來提高提高性能,主要用在咱們但願可以在當前線程消息隊列空閒時作些事情(譬如UI線程在顯示完成後,若是線程空閒咱們就能夠提早準備其餘內容)的狀況下,不過最好不要作耗時操做

Message next() {
    final long ptr = mPtr;
    if (ptr == 0) { //當消息循環已經退出,則直接返回
        return null;
    }
    int pendingIdleHandlerCount = -1; // 循環迭代的首次爲-1
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //阻塞操做,當等待nextPollTimeoutMillis時長,或者消息隊列被喚醒,都會返回
        //nextPollTimeoutMillis 爲-1,一直阻塞,在調用nativeWake(enqueue Message或Looper.quit()退出Looper)時會被喚醒解除阻塞
        //nextPollTimeoutMillis 爲0,不阻塞
        //nextPollTimeoutMillis 爲>0,阻塞到對應時間後解除,如爲10000則阻塞十秒後解除,用於處理延遲消息
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                //當消息Handler爲空時,查詢MessageQueue中的下一條異步消息msg,則退出循環。
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                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;
                    //設置消息的使用狀態,即flags |= FLAG_IN_USE
                    msg.markInUse();
                    return msg;   //成功地獲取MessageQueue中的下一條即將要執行的消息
                }
            } else {
                //沒有消息,阻塞
                nextPollTimeoutMillis = -1;
            }
            //消息正在退出,返回null
            if (mQuitting) {
                dispose();
                return null;
            }
            //若是當前MessageQueue頭結點爲空(沒有消息要處理了)或者當前系統時間<消息觸發時間
            if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
            //看是否加入了idleHandler
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                //沒有idle handlers 須要運行,則循環並等待。
                mBlocked = true;
                continue;
            }
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        //只有第一次循環時,會運行idle handlers,執行完成後,重置pendingIdleHandlerCount爲0.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; //去掉handler的引用
            boolean keep = false;
            try {
                //若是queueIdle()返回false則當前idlehandler只能運行一次
                keep = idler.queueIdle();  //idle時執行的方法
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                synchronized (this) {
                    //queueIdle返回false,移除idlehandler
                    mIdleHandlers.remove(idler);
                }
            }
        }
        //重置idle handler個數爲0,以保證不會再次重複運行
        pendingIdleHandlerCount = 0;
        //當調用一個空閒handler時,一個新message可以被分發,所以無需等待能夠直接查詢pending message.
        nextPollTimeoutMillis = 0;
    }
}
複製代碼

同步屏障

同步屏障是由系統發送,通常用於刷新UI(如16ms刷新一次界面)。當設置了同步屏障以後,next函數將會忽略全部的同步消息,返回異步消息。換句話說就是,設置了同步屏障以後,Handler只會處理異步消息。再換句話說,同步屏障爲Handler消息機制增長了一種簡單的優先級機制,異步消息的優先級要高於同步消息。

  • 如何判斷是否爲同步屏障消息? 當Message的target爲null時(Message不持有Handler)則當前消息爲異步消息,也就是同步屏障。

Android應用框架中爲了更快的響應UI刷新事件在ViewRootImpl.scheduleTraversals中使用了同步屏障

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //設置同步障礙,確保mTraversalRunnable優先被執行
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //內部經過Handler發送了一個異步消息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}
複製代碼
相關文章
相關標籤/搜索