Android Handler機制

爲何要有Handler機制?

解決工做線程更新UI的問題。
因爲在Android機制中,爲了保證UI操做是線程安全的,規定只容許主線程更新Activity的UI組件。但在實際開發中存在多個線程併發操做UI組件的狀況,會致使UI操做線程不安全。故採用Handler機制,當工做線程須要更新UI的時候,經過Handler通知主線程,從而在主線程中更新UI。安全

ps1:爲何不用鎖呢?用鎖會使UI的訪問邏輯變得複雜,鎖機制會下降UI訪問的效率,鎖會阻塞某些線程的執行。

Handler機制包含了什麼?

重要概念

  • MainThread,UI線程,子線程(工做線程)
  • Handler 處理者
  • Looper 循環器
  • Message Queue 消息隊列
  • Message 消息

UI線程與工做線程

UI線程就是APP啓動時,就會開啓一條ActivityThread線程,稱之爲主線程。網絡

工做線程,則是在操做過程當中,開啓的線程,如網絡請求線程等。併發

Handler

image.png

首先看一下該類的註釋,就能夠知道該類的功能和用處了。
Handler使你能夠發送和處理與線程MessageQueue相關聯的Message和Runnable。每一個實例都與一個該線程的MessageQueue向關聯。當建立Handler後,就會綁定MessageQueue。從綁定以後起,它就能夠將Runnable和Message傳入消息隊列中,並在讀取到對應消息時執行他們。less

從描述中,咱們能夠提取到幾個關鍵信息。Handler與線程綁定,與線程的MessageQueue綁定,大機率每一個線程就只能有一個MessageQueue。Handler能夠發送處理兩種類型的數據,Message和Runnable。(這些後面具體描述)異步

Looper

image.png

Looper是爲了爲線程提供消息循環。默認狀況下,線程沒有Looper,可使用prepare()獲取循環,並使用loop()方法開始處理信息,只到循環中止。
與信息循環的大部分交互的都是經過Handler類。async

MessageQueue

image.png
包含Looper要發送的信息列表的低級類。消息不是直接添加到MessageQueue中,而是由與Looper關聯的Handler類添加的。函數

從描述中可知,MessageQueue從屬於Looper。oop

Message

就是Handler處理的消息。其中有幾個比較重要的屬性:post

  • target 發送和處理這個Message的Handler對象
  • callback 在主線程執行的回調
  • when 信息的傳遞時間
  • next 指向下一條Message(鏈表結構)

怎麼使用Handler機制?

從前文可知,Handler須要綁定MessageQueue,而MessageQueue從屬於Looper,因此從建立looper開始。ui

消息處理

1、建立Looper對象

  • 主線程 Looper.prepareMainLooper()
    主線程建立的時候會調用prepareMainLooper方法建立一個looper方法,因此在咱們本身寫代碼時,無需在主線程建立looper。
public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

咱們能夠發現,這裏也調用了prepare方法,咱們看一下prepare的方法。

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

這裏出現了sThreadLocal字段,這個字段被static final修飾,說明是類共享的常量。(關於ThreadLocal具體講解能夠看上一篇文章)該常量,爲每一個線程都提供類Looper的副本。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
ps2 : 如何保證一個線程中只有一個Looper? Looper的構造方法是private,只能在prepare中調用。而且若是一個ThreadLocal獲取到相應的value,說明已經建立過。會拋出異常。
ps3:prepare的方法中攜帶一個布爾類型參數,用於判斷是否能夠退出循環。子線程的都爲true,意味着能夠退出;主線程的爲false,意味着不能夠退出。

接着來看看構造函數,每一個looper對象都會綁定當前線程與一個消息隊列
image.png

2、使Looper開始工做

固然就是調用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;

        ......

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            ......
            try {
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
        ......
        }
    }
  • 首先得到當前的Looper的消息隊列 MessageQueue queue = me.mQueue;
  • 而後開啓死循環 for(::)
  • 經過消息隊列獲取MessageQueue獲取信息 Message msg = queue.next();(可能形成阻塞)
  • 而後處理消息 msg.target.dispatchMessage(msg) 前文已知Message的target的就是發送和處理該對象的Handler。
ps4: 如何退出循環。looper調用quit方法,或者quitSafely方法後,隊列就會放回msg == null,這樣就能夠結束循環。顧名思義,quit就是當即退出,而quitSafely則打上標誌,當消息處理徹底以後才結束循環。
ps5:爲何這裏阻塞了?會不會影響主線程,或者影響CPU呢?這也是一個值得關注的問題,容後面分析。

3、MessageQueue讀取數據

仍然仍是貼出關鍵代碼

Message next() {
        ......
        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.
                        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;
                }

                ......
            }

           ......
        }
    }

不難發現nextPollTimeoutMillis是很重要的參數。正常循環時,此參數爲0。當有延遲消息,消息沒準備好時,則會設置一個延遲時間,讓下一次循環執行。當message爲空時,此參數會設置成-1。

nativePollOnce方法是一個本地方法,也是阻塞這個消息隊列的方法。當前面參數爲-1時,就會使消息隊列陷入等待狀態。

注意這裏有個同步方法,鎖住類MessageQueue對象。因爲處理的消息,是由其餘線程傳入的,爲了保證線程安全,就得Synchronized。

4、Handler.dispatchMessage(msg)

image.png

從前文得知Hanler應該能夠處理兩類數據?爲何這裏就只有Message呢?後文再說,咱們先看處理方法:
當message有回調時(實際上就是前文的Runnable的類型數據),就會調用這個回調的run方法。

message.callback.run();

當msg.callback爲空時,則此處會使用自定的hanlerMessage方法。

以上四步就是Handler機制處理消息的步驟了。那麼Handler機制是如何上傳Message的呢?

上傳消息

1、重寫Handler

從前文三種處理方法中,咱們就能夠複寫出三種重寫Handler方法。

  • 新建Handler子類,實現handlerMessage處理方法。(對應最後一種處理)
  • 匿名內部類,經過Callback實現handlerMessag。(對應倒數第二種處理)
  • 在post方法中傳入Runnable(對應第三種處理)
ps6: Handler要在處理該Handler(通常爲主線程)的線程中建立,而後在工做調用。
ps7:這裏的Runnable接口,只是做爲一種聲明,而不是像傳統的要開啓一個新的線程,切記。
ps8:子線程中能夠用MainLooper去建立Handler嗎? 子線程中Handler handler = new Handler(Looper.getMainLooper());,此時二者就不在一個線程中。

2、建立消息對象

  • 建立Runnable對象,能夠直接實現一個類,也能夠直接在post方法內使用匿名內部類。
  • 建立Message對象

    • 建立舉例
Message msg = Message.obtain(); // 實例化消息對象
   msg.what = 1; // 消息標識
   msg.obj = "AA"; // 消息內容存放
*
ps9: 建立用了不常見的obtain,而不是new,是由於Message自帶緩衝池,避免每次都使用new從新分配內存,只有當線程池無對象時,纔會new新對象。

3、發送信息到隊列

對應兩種類型數據,亦有兩條路徑:

  • post(Runnable r)

    • sendMessageDelayed(getPostMessage(r), 0); getPostMessage方法中,將Runnable接口封裝成了message對象,並將r設置成類msg.callback。
    • sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    • enqueueMessage(queue, msg, uptimeMillis); 將msg與當前的Handler綁定,並將其插入隊列中
    • queue.enqueueMessage(msg, uptimeMillis);這步較爲關鍵,貼一下源碼。這裏使用的是單鏈表的增添方法,鏈表的好處是增刪便捷,可是查詢不便利,這邊也不多查詢的方法,故使用鏈表也較爲合適。
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 {
                // 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;
                // 噹噹前隊列不爲空,則根據時間插入,到適當的位置
                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;
    }
  • sendMessage(Runnable r)

    • endMessageDelayed(msg, 0);
    • sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    • enqueueMessage(queue, msg, uptimeMillis);
    • queue.enqueueMessage(msg, uptimeMillis);

步驟基本同上。

ps10:若是須要調用延遲的話,調用postDelayed方法,最後底層仍是和上面類似。
ps11:若是簡單更新UI則直接使用Runnable便可(可能致使線程耦合度高)。如果要傳遞數據,則使用Message。

致此,怎麼使用Handler機制已經講述完成了。可是Handler機制的使用仍是存在一些問題的,讓咱們繼續探究。

Handler使用疑問

1、處理延遲消息:

一、MessageQueue.next()在取出Msg時,若是發現消息A有延遲且時間沒到,會阻塞消息隊列。
二、若是此時有非延遲的新消息B,會將其加入消息隊列, 且處於消息A的前面,而且喚醒阻塞的消息隊列。
三、喚醒後會拿出隊列頭部的消息B,進行處理。而後會繼續由於消息A而阻塞。
四、若是達到了消息A延遲的時間,會取出消息A進行處理。

2、Looper 死循環爲何不會致使應用卡死?會消耗愈來愈多的資源嘛?

ActivityThread實質是隻是App的入口類,而不是真正的線程。

對於線程便是一段可執行的代碼,當可執行代碼執行完成後,線程生命週期便該終止了,線程退出。而對於主線程,咱們是毫不但願會被運行一段時間,本身就退出,那麼如何保證能一直存活呢?簡單作法就是可執行代碼是能一直執行下去的,死循環便能保證不會被退出。

至於消耗資源,涉及到epoll機制(到IO的時候在繼續講解吧),該機制會再有數據到達時,才喚醒主線程工做。不然就讓主線程休眠。

3、主線程的消息循環機制是什麼?

此點不夠清晰,之後詳細研究。

4、Handler的內存泄漏問題。

  • 當Handler是非靜態內部類或匿名Handler內部類時,會持有外部應用,這樣會致使一個問題。當要銷燬當前Activity時,可能有消息未處理徹底,Message指向了Handler,而當前Handler又持有Activity,因此當前的Activity不能被回收,可能形成內存泄漏。
  • 解決辦法:

    • 一、存在「未被處理 / 正處理的消息 -> Handler實例 -> 外部類」 的引用關係
      首先將Handler設置爲靜態內部類,而後持有Activity的弱引用實例。保證消息隊列中全部消息都能執行。
    • 二、Handler的生命週期 > 外部類的生命週期
      在OnDestory方法中,清空Handle隊列,並直接終止handler,mHandler.removeCallbacksAndMessages(null);

5、補充

隊列中的內容(不管Message仍是Runnable)能夠要求立刻執行,延遲必定時間執行或者指定某個時刻執行,若是將他們放置在隊列頭,則表示具備最高有限級別,當即執行。這些函數包括有:sendMessage(), sendMessageAtFrontOfQueue(), sendMessageAtTime(), sendMessageDelayed()以及用於在隊列中加入Runnable的post(), postAtFrontOfQueue(), postAtTime(),postDelay()。

相關文章
相關標籤/搜索