Android Loop機制中Looper與handler詳細分析
Looper是什么
用于為線程運行消息循環(huán)的類。默認情況下,線程沒有與之關(guān)聯(lián)的消息循環(huán)。要創(chuàng)建一個,在要運行循環(huán)的線程中調(diào)用 prepare(),然后調(diào)用loop()讓它處理消息,直到循環(huán)停止為止。與消息循環(huán)的大多數(shù)交互是通過 Handler類進行的。
意思大概就是讓線程有處理消息的能力,并且這種能力是無限循環(huán)的,直到被停止為止。
簡單使用
public Handler handler; public void looperThread(){ new Thread(new Runnable() { @Override public void run() { Looper.prepare(); handler = new Handler(Looper.myLooper(),new Handler.Callback() { @Override public boolean handleMessage(Message msg) { Log.e(TAG,"收到發(fā)送過來的消息:"+msg.obj.toString()); return false; } }); Looper.loop(); } }).start(); } @Override public void onClick(View view) { Message message = Message.obtain(); message.obj = "點擊事件消息時間戳:"+System.currentTimeMillis()%10000; handler.sendMessage(message); }
創(chuàng)建一個具有消息循環(huán)的線程,該線程中創(chuàng)建一個和該looper綁定的handler對象,然后點擊事件中不斷的去發(fā)送消息給looper循環(huán),看下最后的效果如下:
18:17:45.459 12495-12538/com.example.myapplication E/[MainActivity]: 收到發(fā)送過來的消息:點擊事件消息時間戳:5458
18:17:45.690 12495-12538/com.example.myapplication E/[MainActivity]: 收到發(fā)送過來的消息:點擊事件消息時間戳:5690
18:17:45.887 12495-12538/com.example.myapplication E/[MainActivity]: 收到發(fā)送過來的消息:點擊事件消息時間戳:5886
...省略
18:18:40.010 12495-12538/com.example.myapplication E/[MainActivity]: 收到發(fā)送過來的消息:點擊事件消息時間戳:9
18:18:40.840 12495-12538/com.example.myapplication E/[MainActivity]: 收到發(fā)送過來的消息:點擊事件消息時間戳:839
18:18:41.559 12495-12538/com.example.myapplication E/[MainActivity]: 收到發(fā)送過來的消息:點擊事件消息時間戳:1558
可以看到我一直點擊,一直有消息可以被處理,那么說明我創(chuàng)建的線程是一直運行的,并沒有結(jié)束。那么looper具體是怎么實現(xiàn)的這樣的功能的呢?
從源碼了解loop原理
在分析源碼之前,先看下整體的類圖關(guān)系:
loop分析
我們從Looper.prepare();
這句代碼開始分析:
Looper.prepare();
public final class Looper { static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper; // guarded by Looper.class final MessageQueue mQueue; final Thread mThread; ...省略 public static void prepare() { prepare(true); } ...省略
可以看到調(diào)用了prepare()
方法后,接著調(diào)用了有參函數(shù)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的泛型參數(shù)是Looper,那么知道Looper保存在了線程所持有的map容器中,首先就是判斷sThreadLocal.get()
是否為空,這個方法在上一章說過,是根據(jù)當前線程來獲取的,如果這個prepare方法在ui線程中調(diào)用那么返回的就是ui線程中的Looper,如果調(diào)用的是子線程中,那么返回的就是子線程的Looper了,如果不為空,拋出異常,意思就是一個線程只能持有一個Looper對象;如果為空的話,那么調(diào)用sThreadLocal的set方法將創(chuàng)建的Looper對象存放到對應線程的map容器中。
接著調(diào)用了loop函數(shù):
Looper.loop();
public static void loop() { final Looper me = myLooper(); ...省略 final MessageQueue queue = me.mQueue; for (;;) { Message msg = queue.next(); // might block if (msg == null) { return; } ...省略 try { msg.target.dispatchMessage(msg); } finally { ...省略 } ...省略 msg.recycleUnchecked(); } }
大概是這樣的,其中去掉了一些和業(yè)務無關(guān)的代碼。
myLooper()
第一步調(diào)用myLooper()方法:
final Looper me = myLooper(); final MessageQueue queue = me.mQueue; public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
獲取當前線程的sThreadLocal中的Looper對象。從Looper對象獲取隊列。
第二步開始for循環(huán),Message msg = queue.next(); // might block
在循環(huán)中不斷的從queue中取Message消息,
獲取msg判斷是否為空,空的話直接返回,不為空的話,調(diào)用msg的Target的dispatchMessage方法。最后msg使用完畢之后就回收msg對象。
首先來看下
Message msg = queue.next(); // might block
next()
調(diào)用的是MessageQueue的next方法,代碼如下:
Message next() { ...省略 int pendingIdleHandlerCount = -1; // -1 only during first iteration 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; } ...省略 } ...省略 } }
首先調(diào)用nativePollOnce(ptr, nextPollTimeoutMillis);
這個方法是調(diào)用的native方法,意思就是阻塞當前線程,在延遲nextPollTimeoutMillis時長后喚醒當前線程。
接著調(diào)用:
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()); }
其中的判斷是msg.target == null這個條件,這個條件說明當前的msg是沒有設置Target的,msg的Target一般是handler,如果這里是空的話,那么這個msg就是同步屏障消息,用于攔截同步消息的,讓異步消息有優(yōu)先處理權(quán)。如果當前是同步屏障的話,那么while循環(huán),一直向后遍歷msg節(jié)點,條件是這個msg非空和非異步消息,所以這里能夠跳出循環(huán)的情況就是msg到了尾部為空了,要么就是向后遍歷發(fā)現(xiàn)了異步消息。接著往下看:
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; }
分為兩種情況:
(1)如果msg為空的話,先設置延遲時長nextPollTimeoutMillis = -1;
接著這趟for循環(huán)結(jié)束,回到起點的位置,又開始執(zhí)行nativePollOnce(ptr, nextPollTimeoutMillis);
延遲時間是-1那么線程就會阻塞下去,直到被喚醒,不會執(zhí)行for循環(huán)了(msg在進入隊列的時候會去喚醒線程的,所以這里不會一直阻塞的)。
(2)如果msg不為空的話,假設消息設置的時間點大于現(xiàn)在的時間點,那么設置nextPollTimeoutMillis 為時間差和整數(shù)最大值中的最小值。這樣的話,線程在下次循環(huán)中的開頭就會阻塞到可以執(zhí)行該消息的when時間節(jié)點再次運行(線程在阻塞的時候不會去輪轉(zhuǎn)cpu時間片所以可以節(jié)約cpu資源,同樣的,如果阻塞期間有消息進來可以馬上運行,那么還是會被喚醒的);假設消息設置的時間點小于現(xiàn)在的時間點,那么從msg消息鏈中把該消息摘取出來,msg標記為使用中,將msg返回。
思考:隊列中頭部msg是同步屏障的話,那么優(yōu)先從前往后去查找異步消息進行處理,所以在同步屏障消息之后的同步消息不會被執(zhí)行,直到被移除為止。隊列頭部是普通的消息的時候,是根據(jù)when時間節(jié)點來判斷,是直接返回msg,還是等待when-now時間差在去循環(huán)一遍查找頭結(jié)點msg。
handler.dispatchMessage
handler = new Handler(Looper.myLooper(),new Handler.Callback() { @Override public boolean handleMessage(Message msg) { Log.e(TAG,"收到發(fā)送過來的消息:"+msg.obj.toString()); return false; } });
handler在創(chuàng)建的參數(shù)是Looper和Callback,接著再來看下dispatchMessage是如何實現(xiàn)的:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); }
如果msg存在callback的話,直接調(diào)用callbakc的run方法,這里不存在我們傳遞msg沒有設置callback,那么走下面的那個邏輯,我們給handler設置了mCallback,那么就直接回調(diào)handler的mCallback.handleMessage的方法:
@Override public boolean handleMessage(Message msg) { Log.e(TAG,"收到發(fā)送過來的消息:"+msg.obj.toString()); return false; }
這樣也就出現(xiàn)了我們開頭demo中的打印消息了。
handler分析
我們通過上面的next方法分析了如何從隊列中獲取消息,那么我們還沒有分析消息是如何入隊的,接下來我們來分析下handler的幾個關(guān)鍵的問題,(1)handler的消息一個分為幾種;(2)handler發(fā)送消息到哪去了。
我們從handler的構(gòu)造函數(shù)入手:
handler = new Handler(Looper.myLooper(),new Handler.Callback() { @Override public boolean handleMessage(Message msg) { Log.e(TAG,"收到發(fā)送過來的消息:"+msg.obj.toString()); return false; } }); public Handler(Looper looper, Callback callback) { this(looper, callback, false); } public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
我們可以看到,handler一共持有四個關(guān)鍵變量,Looper循環(huán)(和looper關(guān)聯(lián),handler發(fā)送的消息只會發(fā)到這個隊列中),mQueue 持有Looper的隊列,mCallback 用于處理消息的回調(diào)函數(shù),mAsynchronous 標志這個handler發(fā)送的消息是同步的還是異步的。
我們再來看一下消息是怎么發(fā)送的:
Message message = Message.obtain(); message.obj = "點擊事件消息時間戳:"+System.currentTimeMillis()%10000; handler.sendMessage(message);
首先從Message中獲取一個message,這個Message其實里面保存著msg的鏈表,遍歷鏈表,返回的是回收的msg,其中flags整數(shù)變量標志著msg是否正在使用中,是否是異步消息等等狀態(tài)。
handler.sendMessage(message);
然后使用handler去發(fā)送一個msg對象、接著進去看下:
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } 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); }
msg初始狀態(tài)下是同步消息,sendMessage方法發(fā)送出去的消息delayMillis 延遲時間是0;
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
在入隊列之前,將msg的Target設置為當前handler,然后根據(jù)handler是否是異步的,設置msg是否是異步的,然后調(diào)用隊列的入隊函數(shù),將消息入隊。
這里先回答第二個問題,如何入隊的:
消息入隊
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) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { 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; } if (needWake) { nativeWake(mPtr); } } return true; }
首先判斷當前入隊msg的when時間是否比隊列中的頭結(jié)點的when時間節(jié)點靠前,靠前的話,就將入隊的msg加入到隊列的頭部,并且調(diào)用nativeWake(mPtr);
方法喚醒looper所在的線程,那么next()開始執(zhí)行了,可以馬上遍歷隊列,消耗msg消息。如果當前消息msg的時間節(jié)點when大于頭部節(jié)點,首先設置needWake標志, 是否需要喚醒分為:如果隊列頭部是同步屏障,并且入隊消息msg是異步消息,那么就需要喚醒線程,其他情況不需要喚醒;接著執(zhí)行for循環(huán),循環(huán)里面尋找隊列中第一個節(jié)點時間是大于msg消息的時間節(jié)點的(這意味著隊列中消息是按照時間節(jié)點排序的),循環(huán)結(jié)束后,將入隊的msg插入到隊列中,最后根據(jù)需要是否喚醒線程。
同步屏障
同步屏障功能是讓隊列中的同步消息暫時不執(zhí)行,直到同步屏障被移除,異步消息可以不受影響的被執(zhí)行,相當于排隊買票的隊列中頭部有個人一直卡著不走,只有vip的人才能正常在窗口中買票,其他普通人買不了票,如果那個頭部卡著的那個人不走的話。這個同步屏障非常有用,用于優(yōu)先執(zhí)行某些任務。
同步屏障我們使用的比較少,但是安卓frame層代碼有使用這個同步屏障的功能,例如ViewRootImp中:
ViewRootImp中: void scheduleTraversals() { ...省略 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); ...省略 } Choreographer中: private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) { ...省略 mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); if (dueTime <= now) { scheduleFrameLocked(now); } else { Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); } } }
向隊列中發(fā)送一個同步屏障getQueue().postSyncBarrier();
看下源碼如何實現(xiàn)的:
public int postSyncBarrier() { return postSyncBarrier(SystemClock.uptimeMillis()); } private int postSyncBarrier(long when) { // Enqueue a new sync barrier token. // We don't need to wake the queue because the purpose of a barrier is to stall it. synchronized (this) { final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when != 0) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } }
同步屏障的時間節(jié)點是當前時間,還可以知道同步屏障消息的Target是空的,成員變量arg1保存的是同步屏障的自增值。接下來就是找到隊列中第一個時間節(jié)點比自己大的節(jié)點位置,然后插入到隊列中,所以屏障也是按照時間來排列的,沒有特殊待遇。
接著使用handler向Looper中發(fā)送了一個異步消息:
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime);
可以看到異步消息需要設置msg.setAsynchronous(true);
執(zhí)行ui的任務使用異步消息去執(zhí)行,為啥要用異步,因為在5.0以上的安卓系統(tǒng)中已經(jīng)開始使用了垂直同步技術(shù)了,所以重繪頁面的操作需要按照屏幕刷新率來執(zhí)行,假如一個16ms里面有多次重繪請求,最終也只會拋棄掉,只保留一個重繪消息,所以,為了保證重繪操作能夠在收到同步信號的時間節(jié)點馬上執(zhí)行,必須使用同步屏障,這樣前面排隊的同步消息暫時不執(zhí)行,優(yōu)先執(zhí)行我們的重繪界面的異步消息,這樣可以保證我們的界面盡量能夠及時刷新,避免丟幀。、
再來看下handler.post()方法:
public final boolean post(Runnable r){ return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
可以看到,其實也是封裝了一個msg對象,將callback傳遞給它,我們在dispatchMessge函數(shù)中也知道,如果msg如果有自己的callback 就會調(diào)用這個回調(diào)處理消息,不會使用handler自己的callback 來處理消息。
總結(jié)
根據(jù)以上所說的關(guān)系,畫一張圖:
結(jié)論:
handler的消息分為:同步消息,異步消息,屏障消息。
handler的消息發(fā)送:消息都發(fā)送到了和它綁定的Looper的隊列中去了。
那么queue一對一looper,looper一對多handler,looper對象保存在所在線程的ThreadLocal中。
到此這篇關(guān)于Android Loop機制中Looper與handler詳細分析的文章就介紹到這了,更多相關(guān)Android Looper與handler內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決NDK開發(fā)中Eclipse報錯Unresolved inclusion jni.h的最終解決方法(已測)
這篇文章主要介紹了解決NDK開發(fā)中Eclipse報錯Unresolved inclusion jni.h的最終方法,需要的朋友可以參考下2016-12-12Android開發(fā)系列二之窗口Activity的生命周期
這篇文章主要介紹了Android學習系列二之窗口Activity的生命周期的相關(guān)資料,需要的朋友可以參考下2016-05-05Android實現(xiàn)基于滑動的SQLite數(shù)據(jù)分頁加載技術(shù)(附demo源碼下載)
這篇文章主要介紹了Android實現(xiàn)基于滑動的SQLite數(shù)據(jù)分頁加載技術(shù),涉及Android針對SQLite數(shù)據(jù)的讀取及查詢結(jié)果的分頁顯示功能相關(guān)實現(xiàn)技巧,末尾還附帶demo源碼供讀者下載參考,需要的朋友可以參考下2016-07-07Android Studio Kotlin代碼和java代碼相互轉(zhuǎn)化實例
這篇文章主要介紹了Android Studio Kotlin代碼和java代碼相互轉(zhuǎn)化實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03