Android - Handler原理

Handler的主要做用是收發消息和切線程

功能一:收發消息

簡單流程介紹

但願你看完這篇文章後也能夠把流程本身講出來,而且每一個環節還能夠講出不少細節java

他的消息機制離不開Looper、MessageQueueweb

  • 其中 Looper 每一個線程只能持有一個,主要負責循環查看 MessageQueue 裏面是否有 msg 須要處理,並將須要處理的消息取出,交給 Handler
  • MessageQueue 是負責存放消息的,數據結構是一個單鏈表,這樣就能夠方便地插入或刪除 msg

具體流程通常是:api

  1. Handler 發送一條msg => 本質是向MessageQueue裏插入一條msg,插入時候的依據是msg.when => SystemClock.uptimeMillis() + delayMillismarkdown

  2. 這條msgMessageQueue.next()返回並交給Handler去處理數據結構

next()會在有同步屏障(msg.target==null)的時候遍歷查找並返回最先的異步消息,並在移除屏障後,從頭取出並返回消息異步

  1. Handler.dispatchMessage(msg)會優先處理msg.callback,若是msg.callback爲空,就處理Handler.mCallback,而後處理是msg自己 msg.callback是在調用Handler.post(Runnable)時,裏面的Runnable(runOnUIThreadview.post(Runnable)也用的是Handler.post(Runnable)Runnable是同樣的)async

    這是在不新增Handler的狀況下,另外一種調用Handler的方式(以下)ide

class MyHandlerCallBack: Handler.Callback {
  override fun handleMessage(msg: Message?): Boolean {
    TODO("Not yet implemented")
  }
}
複製代碼

能夠看到他也有handleMessage這個方法oop

Looper是個死循環

(1)死循環的目的

目的就是讓主線程一直卡在這個死循環裏面post

由於Looper的做用就是在這個死循環裏面取出消息,而後交給Handler處理

Android的生命週期,你瞭解的onCreate,onStop,onStart...... 等等都是由Handler來處理的,都是在這個死循環裏面運行的

因此什麼Looper死循環卡死主線程怎麼辦???

必須給我卡住!!!不卡住的話,消息就無法整了!!!

看下Android啓動的時候的源碼 Activitythread.java >> main()

public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        ...
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        ...
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
複製代碼

想一想寫java的時候,main最後一行執行完了,不就完全玩完了嘛!!!

(2)死循環裏幹了啥

其實想都不用想,一直在看MessageQueue裏面有沒有消息唄,太簡單了!

咋看?

答: 調用MessageQueue.next()

看下源碼 Looper.java >> loop()

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);
                }
            }
            ...
        }
複製代碼

很簡單,next()返回Messagemsg.target.dispatchMessage() 處理Message

可是隊列裏沒消息就會返回null,這是錯誤的!!!具體往下看

MessageQueue是個單鏈表

1.插隊

Handler發消息的時候,目的就是對msg通過一系列操做,最終也只是調用enqueueMessage插入隊列而已

看下源碼 Handler>>enqueueMessage()

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
複製代碼

return直接調用Message的插入隊列方法

2.出隊

出隊就是next()方法,以前已經見過了

(1)時間順序

Message是按時間排序的,也就是msg.when => SystemClock.uptimeMillis() + delayMillis

msg.whenMessage指望被處理的時間

SystemClock.uptimeMillis()是開機到如今的時間,delayMills是延遲時間,這個在sendMessageDelayed方法裏直接能夠直接傳參

next()就是按照時間順序處理MessageQueue裏面的消息的

可是next()裏有個概念叫 同步屏障

(2)同步屏障

同步屏障,就是說,平時MessageQueue都是處理同步消息,也就是按順序來,一個個出隊

同步屏障就是阻擋同步消息的意思

就是msg.target == null 的時候,MessageQueue就會去找msg.isAsynchronous()返回truemsg

isAsynchronous,沒錯 ! 這是異步消息,就是優先級很高,須要馬上執行的消息,好比:更新View

(3)阻塞

值得注意的是,講Looper的時候,源碼next()後面官方給咱們註釋了 // might block可能阻塞,也就是說可能這個next()也許會執行很久

next()會阻塞?,何時阻塞?

now < msg.when也就是時間還沒到,指望時間大於如今的時間

(4)退出

另外看第一行,只有ptr == 0,纔會返回null

因此上面才說next()不會由於沒消息而返回null,原來返回null的時候在這呢!

看下源碼,MessageQueue.java >> next()

@UnsupportedAppUsage
    Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                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 {
                    ...
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
                ...
            }
            ...
        }
    }
複製代碼

代碼簡略了仍是有點多,彆着急,慢慢看

pre何時就是0了呢?

答:quit()了以後

看下源碼,Looper.java

public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }
複製代碼

能夠看到只是一個傳參不一樣而已,下面看看這個參數是幹嗎的

看下源碼,MessageQueue.java >> quit()

void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
複製代碼

能夠看到,safe == true,就移除將來的Message safe == false,就移除全部的Message

mQuiting變成了true,記住他咱們一下子會用到

而改變ptr的地方在這裏

next()裏面

裏面有個dispose,找不到能夠ctrl+F找一下

這裏只有在mQuiting == true的時候,纔會調用

這就是改mPtr的地方,而後下次next()的時候就會返回null

Handler流程

(1)post過來的msg

咱們已經知道了在Looper的死循環裏面,會將next()返回的msg交給Handler,調用dispatchMessage()

dispatchMessage()裏面會先判斷msg是否是被post過來的,由於post要執行的邏輯在msg.callback裏面,callback是一個Runnable,這可能不是很好理解

你能夠想一想runOnUIThread(Runnable),這裏的Runnable就是上面的callback, 他們都是調用了Handler.post(Runnable)

至於爲啥起個名叫callback,我也納悶兒

(2)send過來的msg

這些msg是會的邏輯是你重寫的handleMessage那裏的邏輯

若是實現了Handler.Callback這個Interface,就會處理mCallbackhandleMessage 而不是Handler本身的handleMessage

這是一個優先級策略,沒什麼好奇怪的

咱們看下源碼 => Handler.java >> dispatchMessage()

public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
複製代碼

這就是Handler的消息機制了

接下來咱們講講Handler的另外一個功能,切線程

功能二:切線程

Handler切線程使用的是ThreadLocal

(1)ThreadLocal

ThreadLocal是線程裏面的一個數據儲存類,用法相似mapkey就是thread

可是他沒有提供,根據key來找ThreadLocalValues的方法,因此暴露的api就只能讓你去get當前線程的ThreadLocalValues對象而已,就是key——你本身無法做爲參數傳進去,只能是currentThread

若是你沒用過ThreadLocal,我給你舉個例子

fun main() {
    val booleanThreadLocal = ThreadLocal<Boolean>()
    booleanThreadLocal.set(true)

    println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}")

    thread(name = "thread#001") {
        booleanThreadLocal.set(false)
        println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}")
    }

    thread(name = "thread#002") {
        println("in Thread[${Thread.currentThread().name}] booleanThreadLocal value = ${booleanThreadLocal.get()}")
    }
}
複製代碼

結果是這樣的:你能夠本身運行看看

in Thread[main] booleanThreadLocal value = true
in Thread[thread#001] booleanThreadLocal value = false
in Thread[thread#002] booleanThreadLocal value = null
複製代碼
(2)切線程的細節

話說回來,Handler怎麼經過ThreadLocal切線程的呢?

答案是:Looper是放在ThreadLocal裏的

回顧片頭的流程,Handler將消息插入MessageQueue,而後Looper取出來,再還給Handler,這種設計不止是爲了讓msg能夠按順序處理,還可讓外部接口只有Handler

最關鍵的是,LooperHandler的觸發關係只有Looper觸發HandlerHandler不會觸發Looper

所以Handler把消息放在MessageQueue以後,就在等着Looper來給本身派發任務(msg

舉個例子:

線程A調用主線程Handler發一個消息

Handler將這個消息插入MessageQueue,此時其實還在線程A

只有Loopernext()調用msg.target.dispatchMessage()時,就變成了主線程

僅僅是由於Looper主線程 而已

OVER

相關文章
相關標籤/搜索