解決工做線程更新UI的問題。
因爲在Android機制中,爲了保證UI操做是線程安全的,規定只容許主線程更新Activity的UI組件。但在實際開發中存在多個線程併發操做UI組件的狀況,會致使UI操做線程不安全。故採用Handler機制,當工做線程須要更新UI的時候,經過Handler通知主線程,從而在主線程中更新UI。安全
ps1:爲何不用鎖呢?用鎖會使UI的訪問邏輯變得複雜,鎖機制會下降UI訪問的效率,鎖會阻塞某些線程的執行。
UI線程就是APP啓動時,就會開啓一條ActivityThread線程,稱之爲主線程。網絡
工做線程,則是在操做過程當中,開啓的線程,如網絡請求線程等。併發
首先看一下該類的註釋,就能夠知道該類的功能和用處了。
Handler使你能夠發送和處理與線程MessageQueue相關聯的Message和Runnable。每一個實例都與一個該線程的MessageQueue向關聯。當建立Handler後,就會綁定MessageQueue。從綁定以後起,它就能夠將Runnable和Message傳入消息隊列中,並在讀取到對應消息時執行他們。less
從描述中,咱們能夠提取到幾個關鍵信息。Handler與線程綁定,與線程的MessageQueue綁定,大機率每一個線程就只能有一個MessageQueue。Handler能夠發送處理兩種類型的數據,Message和Runnable。(這些後面具體描述)異步
Looper是爲了爲線程提供消息循環。默認狀況下,線程沒有Looper,可使用prepare()獲取循環,並使用loop()方法開始處理信息,只到循環中止。
與信息循環的大部分交互的都是經過Handler類。async
包含Looper要發送的信息列表的低級類。消息不是直接添加到MessageQueue中,而是由與Looper關聯的Handler類添加的。函數
從描述中可知,MessageQueue從屬於Looper。oop
就是Handler處理的消息。其中有幾個比較重要的屬性:post
從前文可知,Handler須要綁定MessageQueue,而MessageQueue從屬於Looper,因此從建立looper開始。ui
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對象都會綁定當前線程與一個消息隊列
固然就是調用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); } } ...... } }
ps4: 如何退出循環。looper調用quit方法,或者quitSafely方法後,隊列就會放回msg == null,這樣就能夠結束循環。顧名思義,quit就是當即退出,而quitSafely則打上標誌,當消息處理徹底以後才結束循環。
ps5:爲何這裏阻塞了?會不會影響主線程,或者影響CPU呢?這也是一個值得關注的問題,容後面分析。
仍然仍是貼出關鍵代碼
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。
從前文得知Hanler應該能夠處理兩類數據?爲何這裏就只有Message呢?後文再說,咱們先看處理方法:
當message有回調時(實際上就是前文的Runnable的類型數據),就會調用這個回調的run方法。
message.callback.run();
當msg.callback爲空時,則此處會使用自定的hanlerMessage方法。
以上四步就是Handler機制處理消息的步驟了。那麼Handler機制是如何上傳Message的呢?
從前文三種處理方法中,咱們就能夠複寫出三種重寫Handler方法。
ps6: Handler要在處理該Handler(通常爲主線程)的線程中建立,而後在工做調用。
ps7:這裏的Runnable接口,只是做爲一種聲明,而不是像傳統的要開啓一個新的線程,切記。
ps8:子線程中能夠用MainLooper去建立Handler嗎? 子線程中Handler handler = new Handler(Looper.getMainLooper());,此時二者就不在一個線程中。
建立Message對象
Message msg = Message.obtain(); // 實例化消息對象 msg.what = 1; // 消息標識 msg.obj = "AA"; // 消息內容存放
*
ps9: 建立用了不常見的obtain,而不是new,是由於Message自帶緩衝池,避免每次都使用new從新分配內存,只有當線程池無對象時,纔會new新對象。
對應兩種類型數據,亦有兩條路徑:
post(Runnable r)
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)
步驟基本同上。
ps10:若是須要調用延遲的話,調用postDelayed方法,最後底層仍是和上面類似。
ps11:若是簡單更新UI則直接使用Runnable便可(可能致使線程耦合度高)。如果要傳遞數據,則使用Message。
致此,怎麼使用Handler機制已經講述完成了。可是Handler機制的使用仍是存在一些問題的,讓咱們繼續探究。
一、MessageQueue.next()在取出Msg時,若是發現消息A有延遲且時間沒到,會阻塞消息隊列。
二、若是此時有非延遲的新消息B,會將其加入消息隊列, 且處於消息A的前面,而且喚醒阻塞的消息隊列。
三、喚醒後會拿出隊列頭部的消息B,進行處理。而後會繼續由於消息A而阻塞。
四、若是達到了消息A延遲的時間,會取出消息A進行處理。
ActivityThread實質是隻是App的入口類,而不是真正的線程。
對於線程便是一段可執行的代碼,當可執行代碼執行完成後,線程生命週期便該終止了,線程退出。而對於主線程,咱們是毫不但願會被運行一段時間,本身就退出,那麼如何保證能一直存活呢?簡單作法就是可執行代碼是能一直執行下去的,死循環便能保證不會被退出。
至於消耗資源,涉及到epoll機制(到IO的時候在繼續講解吧),該機制會再有數據到達時,才喚醒主線程工做。不然就讓主線程休眠。
此點不夠清晰,之後詳細研究。
解決辦法:
隊列中的內容(不管Message仍是Runnable)能夠要求立刻執行,延遲必定時間執行或者指定某個時刻執行,若是將他們放置在隊列頭,則表示具備最高有限級別,當即執行。這些函數包括有:sendMessage(), sendMessageAtFrontOfQueue(), sendMessageAtTime(), sendMessageDelayed()以及用於在隊列中加入Runnable的post(), postAtFrontOfQueue(), postAtTime(),postDelay()。