<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
用於為執行緒執行訊息迴圈的類。預設情況下,執行緒沒有與之關聯的訊息迴圈。要建立一個,在要執行迴圈的執行緒中呼叫 prepare(),然後呼叫loop()讓它處理訊息,直到迴圈停止為止。與訊息迴圈的大多數互動是通過 Handler類進行的。
意思大概就是讓執行緒有處理訊息的能力,並且這種能力是無限迴圈的,直到被停止為止。
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,"收到傳送過來的訊息:"+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); }
建立一個具有訊息迴圈的執行緒,該執行緒中建立一個和該looper繫結的handler物件,然後點選事件中不斷的去傳送訊息給looper迴圈,看下最後的效果如下:
18:17:45.459 12495-12538/com.example.myapplication E/[MainActivity]: 收到傳送過來的訊息:點選事件訊息時間戳:5458
18:17:45.690 12495-12538/com.example.myapplication E/[MainActivity]: 收到傳送過來的訊息:點選事件訊息時間戳:5690
18:17:45.887 12495-12538/com.example.myapplication E/[MainActivity]: 收到傳送過來的訊息:點選事件訊息時間戳:5886
...省略
18:18:40.010 12495-12538/com.example.myapplication E/[MainActivity]: 收到傳送過來的訊息:點選事件訊息時間戳:9
18:18:40.840 12495-12538/com.example.myapplication E/[MainActivity]: 收到傳送過來的訊息:點選事件訊息時間戳:839
18:18:41.559 12495-12538/com.example.myapplication E/[MainActivity]: 收到傳送過來的訊息:點選事件訊息時間戳:1558
可以看到我一直點選,一直有訊息可以被處理,那麼說明我建立的執行緒是一直執行的,並沒有結束。那麼looper具體是怎麼實現的這樣的功能的呢?
在分析原始碼之前,先看下整體的類圖關係:
我們從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); } ...省略
可以看到呼叫了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的泛型引數是Looper,那麼知道Looper儲存在了執行緒所持有的map容器中,首先就是判斷sThreadLocal.get()
是否為空,這個方法在上一章說過,是根據當前執行緒來獲取的,如果這個prepare方法在ui執行緒中呼叫那麼返回的就是ui執行緒中的Looper,如果呼叫的是子執行緒中,那麼返回的就是子執行緒的Looper了,如果不為空,丟擲異常,意思就是一個執行緒只能持有一個Looper物件;如果為空的話,那麼呼叫sThreadLocal的set方法將建立的Looper物件存放到對應執行緒的map容器中。
接著呼叫了loop函數:
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(); } }
大概是這樣的,其中去掉了一些和業務無關的程式碼。
第一步呼叫myLooper()方法:
final Looper me = myLooper(); final MessageQueue queue = me.mQueue; public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
獲取當前執行緒的sThreadLocal中的Looper物件。從Looper物件獲取佇列。
第二步開始for迴圈,Message msg = queue.next(); // might block
在迴圈中不斷的從queue中取Message訊息,
獲取msg判斷是否為空,空的話直接返回,不為空的話,呼叫msg的Target的dispatchMessage方法。最後msg使用完畢之後就回收msg物件。
首先來看下
Message msg = queue.next(); // might block
呼叫的是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; } ...省略 } ...省略 } }
首先呼叫nativePollOnce(ptr, nextPollTimeoutMillis);
這個方法是呼叫的native方法,意思就是阻塞當前執行緒,在延遲nextPollTimeoutMillis時長後喚醒當前執行緒。
接著呼叫:
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就是同步屏障訊息,用於攔截同步訊息的,讓非同步訊息有優先處理權。如果當前是同步屏障的話,那麼while迴圈,一直向後遍歷msg節點,條件是這個msg非空和非非同步訊息,所以這裡能夠跳出迴圈的情況就是msg到了尾部為空了,要麼就是向後遍歷發現了非同步訊息。接著往下看:
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迴圈結束,回到起點的位置,又開始執行nativePollOnce(ptr, nextPollTimeoutMillis);
延遲時間是-1那麼執行緒就會阻塞下去,直到被喚醒,不會執行for迴圈了(msg在進入佇列的時候會去喚醒執行緒的,所以這裡不會一直阻塞的)。
(2)如果msg不為空的話,假設訊息設定的時間點大於現在的時間點,那麼設定nextPollTimeoutMillis 為時間差和整數最大值中的最小值。這樣的話,執行緒在下次迴圈中的開頭就會阻塞到可以執行該訊息的when時間節點再次執行(執行緒在阻塞的時候不會去輪轉cpu時間片所以可以節約cpu資源,同樣的,如果阻塞期間有訊息進來可以馬上執行,那麼還是會被喚醒的);假設訊息設定的時間點小於現在的時間點,那麼從msg訊息鏈中把該訊息摘取出來,msg標記為使用中,將msg返回。
思考:佇列中頭部msg是同步屏障的話,那麼優先從前往後去查詢非同步訊息進行處理,所以在同步屏障訊息之後的同步訊息不會被執行,直到被移除為止。佇列頭部是普通的訊息的時候,是根據when時間節點來判斷,是直接返回msg,還是等待when-now時間差在去迴圈一遍查詢頭結點msg。
handler = new Handler(Looper.myLooper(),new Handler.Callback() { @Override public boolean handleMessage(Message msg) { Log.e(TAG,"收到傳送過來的訊息:"+msg.obj.toString()); return false; } });
handler在建立的引數是Looper和Callback,接著再來看下dispatchMessage是如何實現的:
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的話,直接呼叫callbakc的run方法,這裡不存在我們傳遞msg沒有設定callback,那麼走下面的那個邏輯,我們給handler設定了mCallback,那麼就直接回撥handler的mCallback.handleMessage的方法:
@Override public boolean handleMessage(Message msg) { Log.e(TAG,"收到傳送過來的訊息:"+msg.obj.toString()); return false; }
這樣也就出現了我們開頭demo中的列印訊息了。
我們通過上面的next方法分析瞭如何從佇列中獲取訊息,那麼我們還沒有分析訊息是如何入隊的,接下來我們來分析下handler的幾個關鍵的問題,(1)handler的訊息一個分為幾種;(2)handler傳送訊息到哪去了。
我們從handler的建構函式入手:
handler = new Handler(Looper.myLooper(),new Handler.Callback() { @Override public boolean handleMessage(Message msg) { Log.e(TAG,"收到傳送過來的訊息:"+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一共持有四個關鍵變數,Looper迴圈(和looper關聯,handler傳送的訊息只會發到這個佇列中),mQueue 持有Looper的佇列,mCallback 用於處理訊息的回撥函數,mAsynchronous 標誌這個handler傳送的訊息是同步的還是非同步的。
我們再來看一下訊息是怎麼傳送的:
Message message = Message.obtain(); message.obj = "點選事件訊息時間戳:"+System.currentTimeMillis()%10000; handler.sendMessage(message);
首先從Message中獲取一個message,這個Message其實裡面儲存著msg的連結串列,遍歷連結串列,返回的是回收的msg,其中flags整數變數標誌著msg是否正在使用中,是否是非同步訊息等等狀態。
handler.sendMessage(message);
然後使用handler去傳送一個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初始狀態下是同步訊息,sendMessage方法傳送出去的訊息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,然後根據handler是否是非同步的,設定msg是否是非同步的,然後呼叫佇列的入隊函數,將訊息入隊。
這裡先回答第二個問題,如何入隊的:
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時間是否比佇列中的頭結點的when時間節點靠前,靠前的話,就將入隊的msg加入到佇列的頭部,並且呼叫nativeWake(mPtr);
方法喚醒looper所在的執行緒,那麼next()開始執行了,可以馬上遍歷佇列,消耗msg訊息。如果當前訊息msg的時間節點when大於頭部節點,首先設定needWake標誌, 是否需要喚醒分為:如果佇列頭部是同步屏障,並且入隊訊息msg是非同步訊息,那麼就需要喚醒執行緒,其他情況不需要喚醒;接著執行for迴圈,迴圈裡面尋找佇列中第一個節點時間是大於msg訊息的時間節點的(這意味著佇列中訊息是按照時間節點排序的),迴圈結束後,將入隊的msg插入到佇列中,最後根據需要是否喚醒執行緒。
同步屏障功能是讓佇列中的同步訊息暫時不執行,直到同步屏障被移除,非同步訊息可以不受影響的被執行,相當於排隊買票的佇列中頭部有個人一直卡著不走,只有vip的人才能正常在視窗中買票,其他普通人買不了票,如果那個頭部卡著的那個人不走的話。這個同步屏障非常有用,用於優先執行某些任務。
同步屏障我們使用的比較少,但是安卓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); } } }
向佇列中傳送一個同步屏障getQueue().postSyncBarrier();
看下原始碼如何實現的:
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; } }
同步屏障的時間節點是當前時間,還可以知道同步屏障訊息的Target是空的,成員變數arg1儲存的是同步屏障的自增值。接下來就是找到佇列中第一個時間節點比自己大的節點位置,然後插入到佇列中,所以屏障也是按照時間來排列的,沒有特殊待遇。
接著使用handler向Looper中傳送了一個非同步訊息:
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); msg.arg1 = callbackType; msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime);
可以看到非同步訊息需要設定msg.setAsynchronous(true);
執行ui的任務使用非同步訊息去執行,為啥要用非同步,因為在5.0以上的安卓系統中已經開始使用了垂直同步技術了,所以重繪頁面的操作需要按照螢幕重新整理率來執行,假如一個16ms裡面有多次重繪請求,最終也只會拋棄掉,只保留一個重繪訊息,所以,為了保證重繪操作能夠在收到同步訊號的時間節點馬上執行,必須使用同步屏障,這樣前面排隊的同步訊息暫時不執行,優先執行我們的重繪介面的非同步訊息,這樣可以保證我們的介面儘量能夠及時重新整理,避免丟幀。、
再來看下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函數中也知道,如果msg如果有自己的callback 就會呼叫這個回撥處理訊息,不會使用handler自己的callback 來處理訊息。
根據以上所說的關係,畫一張圖:
結論:
handler的訊息分為:同步訊息,非同步訊息,屏障訊息。
handler的訊息傳送:訊息都傳送到了和它繫結的Looper的佇列中去了。
那麼queue一對一looper,looper一對多handler,looper物件儲存在所線上程的ThreadLocal中。
到此這篇關於Android Loop機制中Looper與handler詳細分析的文章就介紹到這了,更多相關Android Looper與handler內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45