對Handler,Looper,messagequeue,HandlerThread的深入理解,Handler常見面試題

歡迎轉載,請標明出處:http://blog.csdn.net/a_long_/article/details/51635378 ,本文出自【阿隆 a_long_】

本文目錄:

1.Handler的兩個作用
2.爲什麼創建Handler之前必須要調用Looper的prepare()方法?
3.爲什麼所以每個線程對應一個looper對象,同樣對應一個messageQueue?
4.爲什麼子線程不更新UI,也會需要用到Looper?
5.如果子線程需要用Looper,怎麼使用?
6.爲什麼主線程不需要prepare()?
7.Handler的構造函數裏還有一個async是幹什麼的?
8.Handler是在哪個線程執行的post和handleMessage?

9.Handler裏面的callback又是什麼鬼?

10.HandlerThread的run方法已經被寫死了,那還怎麼在子線程中幹活:

11.    MessageQueue如何根據規定的時間對信息執行調度,如何在無信息的時候阻塞,有信息的時候喚醒:





1.Handler的兩個作用

1:調度消息:在未來執行某操作(放到Looper裏有順序的執行)。

2:在別的線程(而不是自己的線程)執行某操作。(子線程耗時操作後利用Hadnler在主線程更新UI)

源碼中的解釋:

There are two main uses for a Handler: (1) to schedule messages and
 runnables to be executed as some point in the future; and (2) to enqueue
 an action to be performed on a different thread than your own.


2.爲什麼創建Handler之前必須要調用Looper的prepare()方法?

由於:

原因1:Handler的構造函數有很多種,但最終都會直接去拿當前線程的sThreadLocal裏面的Looper實例(sThreadLocal.get();),如果沒有實例就報異常。

  if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }

原因2:Looper的構造函數是private,所以不能直接創建Looper對象,只能通過prepare()方法。
所以導致:創建Handler之前必須要調用Looper的prepare()方法。




3.爲什麼所以每個線程對應一個looper對象,同樣對應一個messageQueue?

Looper在創建實例的時候,會和當前線程綁定且創造出一個對應的MessageQueue,構造函數如下:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
由於Looper對象只能創建一次,原因如下:

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));
    }
由於每個線程都有自己獨立的ThreadLocal,所以每個線程對應一個looper對象,同樣只對應一個messageQueue。


4.爲什麼子線程不更新UI,也會需要用到Looper?


應用實例:大量圖片的加載工具:比如GridView實現手機的相冊功能,一般會用到LruCache,線程池,任務隊列等;那麼異步消息處理可以用哪呢?

1、用於UI線程當Bitmap加載完成後更新ImageView

2、在圖片加載類初始化時,我們會在一個子線程中維護一個Loop實例,當然子線程中也就有了MessageQueue,Looper會一直在那loop停着等待消息的到達,當有消息到達時,從任務隊列按照隊列調度的方式(FIFO,LIFO等),取出一個任務放入線程池中進行處理。


簡易的一個流程:當需要加載一張圖片,首先把加載圖片加入任務隊列,然後使用loop線程(子線程)中的hander發送一個消息,提示有任務到達,loop()(子線程)中會接着取出一個任務,去加載圖片,當圖片加載完成,會使用UI線程的handler發送一個消息去更新UI界面。

(此例摘自鴻洋老師)


總結:子線程的Looper是維護消息隊列、調度用的,使得線程池有序運作。如果要更新UI,還是需要主線程的Handler。




5.如果子線程需要用Looper,怎麼使用?

方法一:Looper類裏面的描述,非常清楚:

  * Class used to run a message loop for a thread.  Threads by default do
  * not have a message loop associated with them; to create one, call
  * {@link #prepare} in the thread that is to run the loop, and then
  * {@link #loop} to have it process messages until the loop is stopped.
  *
  * <p>Most interaction with a message loop is through the
  * {@link Handler} class.
  *
  * <p>This is a typical example of the implementation of a Looper thread,
  * using the separation of {@link #prepare} and {@link #loop} to create an
  * initial Handler to communicate with the Looper.
  
   <pre>
    class LooperThread extends Thread {
        public Handler mHandler;
  
        public void run() {
            Looper.prepare();
  
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    // process incoming messages here
                }
            };
  
            Looper.loop();
        }
  }</pre>


方法二:使用HandlerThread,在創建Handler的時候給一個參數Handler handler = new Handler(handlerThread.getLooper())。

原理和方法一是一樣的,只是HandlerThread幫忙封裝了prepare(), Looper.loop();方法,所以不用自己來調用。

源碼如下:

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }


6.爲什麼主線程不需要prepare()?

因爲在創建ActivityThread的時候調用了prepareMainLooper(),這時已經創建了,無需自己再調用。

源碼如下:

/**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }


7.Handler的構造函數裏還有一個async是幹什麼的?

這個參數默認是false,也就是默認Handler是非異步(同步)的,也就是Looper一個一個往外拿Message,這才能保證UI線程不報異常。

但是如果一個Looper的MessageQueue裏都可以接受同步,那麼把這個參數設成true可以提高效率。

不過實際上這樣的構造函數都被@hide了,所以實際上所有的Handler都是同步的。


Handler類對async參數的解釋:

     * Handlers are synchronous by default unless this constructor is used to make
     * one that is strictly asynchronous.
     *
     * Asynchronous messages represent interrupts or events that do not require global ordering
     * with respect to synchronous messages.  Asynchronous messages are not subject to
     * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
     *
     * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
     * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.



8.Handler是在哪個線程執行的post和handleMessage?

都是在Handler所在的線程。比如Handler一般在主線程創建,那麼post的runnable對象以及handleMessage都是在主線程工作的,容易造成ANR。應該建立子線程,在run函數中通過主線程handler的引用來sendMessage,這樣就不會耗時。或者在子線程的Handler中的handleMessage中循環調用給自己發消息並且利用主線程的Handler更新UI,也可以實現子線程做耗時任務最終UI線程更新UI的目的。

handler的發送message和task只是往某一個looper的MessageQueue注入一項。
Looper是和thread結合的,就是一個事件循環。當該線程回到其事件循環時,之前注入的項就會得到執行。
至於在哪個線程裏執行,要看handler發送到哪個線程的looper了,創建handler時都會綁定一個looper(ui線程是自動綁定的),handler發送的message等都會在該looper的線程得到處理。

如果是想在HandlerThread執行,那麼在創建Handler的時候new Hadnler(handlerThread.getLooper())即可。


9.Handler裏面的callback接口又是什麼鬼?


根據源碼裏面的說明:Callback interface you can use when instantiating a Handler to avoid having to implement your own subclass of Handler.

可以用callback實例化Handler而不必自己實現Handler的子類。

所以和自己實現Handler的子類的效果是相同的,只是方便一些。


10.HandlerThread的run方法已經被寫死了,那還怎麼在子線程中幹活:

用子線程的Handler的方法,有sandMessage、sendMessageAtTime等,比如可以在handleMessage裏面加入sendMessageAtTime(message,1000),那麼子線程就會每秒鐘去幹活。


11.MessageQueue如何根據規定的時間對信息執行調度,如何在無信息的時候阻塞,有信息的時候喚醒:

這不是隊列,而是一個鏈表,放入鏈表中合適的位置用enqueueMessage函數:

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) {
                //p爲null(代表MessageQueue沒有消息) 或者msg的觸發時間是隊列中最早的, 則進入該該分支
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked; //當阻塞時需要喚醒
            } else {
                //將消息按時間順序插入到MessageQueue。一般地,不需要喚醒事件隊列,除非
                //消息隊頭存在barrier,並且同時Message是隊列中最早的異步消息。
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;



            //會從隊列頭開始遍歷



                for (;;) {
                    prev = p;
                    p = p.next;     



//直到找到消息應該插入的合適位置,以保證所有消息的時間順序。



                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p;
                prev.next = msg;
 }



MessageQueue是按照Message觸發時間的先後順序排列的,隊頭的消息是將要最早觸發的消息。當有消息需要加入消息隊列時,會從隊列頭開始遍歷,直到找到消息應該插入的合適位置,以保證所有消息的時間順序。

 

讀取下一條messageMessageQueuenext函數:

@Override
Message next() {
    final long ptr = mPtr;
    int pendingIdleHandlerCount = -1; // 循環迭代的首次爲-1
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        //阻塞操作,當等待nextPollTimeoutMillis時長,或者消息隊列被喚醒,都會返回
        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;
            }
            //當消息隊列爲空,或者是消息隊列的第一個消息時
            if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                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);
        }
        }
        //當調用一個空閒handler時,一個新message能夠被分發,因此無需等待可以直接查詢pending message.
        nextPollTimeoutMillis = 0;
    }
 }



 















參考:

鴻洋大神的分析

guolin大神的Handler分析

廣泛的網上對Handler、Looper的源碼分析

Mars老師的教程

Handler詳細使用方法實例









發佈了27 篇原創文章 · 獲贊 52 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章