Android入門教程 | Handler,Looper與MessageQueue使用與分析
從原始碼角度分析Handler。有利於使用 Handler 和分析 Handler 的相關問題。認識Looper與Handler的關係。
Handler 簡介
一個 Handler 允許傳送和處理 Message,透過關聯執行緒的 MessageQueue 執行 Runnable 物件。 每個 Handler 例項都和一個單獨的執行緒及其訊息佇列繫結。 可以將一個任務切換到 Handler 所在的執行緒中去執行。一個用法就是子執行緒透過 Handler 更新 UI。
Handler主要有2種用法:
- 做出計劃,在未來某個時間點執行訊息和Runnable
- 執行緒排程,在其他執行緒規劃並執行任務
要使用好 Handler,需要了解與其相關的
MessageQueue
,
Message
和
Looper
;不能孤立的看Handler。 Handler就像一個操作者(或者像一個對開發者開放的視窗),利用
MessageQueue
和
Looper
來實現任務排程和處理。
Handler持有 Looper 的例項,直接持有looper的訊息佇列。
屬性與構造器
Handler 類中持有的例項,持有 looper,messageQueue 等等。
final Looper mLooper; // Handler持有Looper例項 final MessageQueue mQueue; // Handler持有訊息佇列 final Callback mCallback;
在 Handler 的構造器中,我們可以看到 Handler 獲取了 Looper 的訊息佇列。
public Handler(Callback callback, boolean async) { // rustfisher 處理異常 mLooper = Looper.myLooper(); // rustfisher 處理特殊情況... mQueue = mLooper.mQueue; // 獲取的是Looper的訊息佇列 } public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; // 獲取的是Looper的訊息佇列 mCallback = callback; mAsynchronous = async; }` </pre>
Handler 使用方法
Handler 傳送和處理訊息的幾個方法
- void handleMessage( Message msg): 處理訊息的方法,該方法通常被重寫。
- final boolean hasMessage(int what): 檢查訊息佇列中是否包含有what屬性為指定值的訊息
- final boolean hasMessage(int what ,Object object) : 檢查訊息佇列中是否包含有what好object屬性指定值的訊息
- sendEmptyMessage(int what): 傳送空訊息
- final Boolean send EmptyMessageDelayed(int what ,long delayMillis): 指定多少毫秒傳送空訊息
- final boolean sendMessage(Message msg): 立即傳送訊息
- final boolean sendMessageDelayed(Message msg,long delayMillis): 多少秒之後傳送訊息
Handler.sendEmptyMessage(int what) 流程解析
獲取一個Message 例項,並立即將 Message 例項新增到訊息佇列中去。 簡要流程如下:
// 立刻傳送一個empty訊息 sendEmptyMessage(int what) // 傳送延遲為0的empty訊息 這個方法裡透過Message.obtain()獲取一個Message例項 sendEmptyMessageDelayed(what, 0) // 計算訊息的計劃執行時間,進入下一階段 sendMessageDelayed(Message msg, long delayMillis) // 在這裡判斷佇列是否為null 若為null則直接返回false sendMessageAtTime(Message msg, long uptimeMillis) // 將訊息新增到佇列中 enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) // 接下來是MessageQueue新增訊息 // MessageQueue.java boolean enqueueMessage(Message msg, long when)
可以看到,最後是把message新增到了messageQueue中。
Handler 取消任務
要取消任務時,呼叫下面這個方法
removeCallbacksAndMessages(Object token)
public final void removeCallbacksAndMessages(Object token) { mQueue.removeCallbacksAndMessages(this, token); }
透過呼叫
Message.recycleUnchecked()
方法,取消掉與此 Handler 相關聯的Message。
相關的訊息佇列會執行取消指令
void removeCallbacksAndMessages(Handler h, Object object)
訊息驅動與 Handler
Android是訊息驅動的,實現訊息驅動有幾個要素
- 訊息的表示:Message
- 訊息佇列:MessageQueue
- 訊息迴圈,用於迴圈取出訊息進行處理:Looper
- 訊息處理,訊息迴圈從訊息佇列中取出訊息後要對訊息進行處理:Handler
初始化訊息佇列
在Looper構造器中即建立了一個MessageQueue,Looper持有訊息佇列的例項。
傳送訊息
透過 Looper.prepare 初始化好訊息佇列後就可以呼叫 Looper.loop 進入訊息迴圈了,然後我們就可以向訊息佇列傳送訊息, 訊息迴圈就會取出訊息進行處理,在看訊息處理之前,先看一下訊息是怎麼被新增到訊息佇列的。
訊息迴圈
Java 層的訊息都儲存在了 Java 層 MessageQueue 的成員 mMessages 中,Native 層的訊息都儲存在了 Native Looper 的 mMessageEnvelopes 中,這就可以說有兩個訊息佇列,而且都是按時間排列的。
Message 和 MessageQueue
與 Handler 工作的幾個元件 Looper、MessageQueue 各自的作用:
- Handler:它把訊息傳送給 Looper 管理的 MessageQueue ,並負責處理Looper分給它的訊息
- MessageQueue:管理 Message,由 Looper 管理
- Looper:每個執行緒只有一個Looper,比如UI執行緒中,系統會預設的初始化一個Looper物件,它負責管理 MessageQueue,不斷的從MessageQueue中取訊息,並將相對應的訊息分給Handler處理。
Message
Message 屬於被傳遞,被使用的角色。Message 是包含描述和任意資料物件的“訊息”,能被髮送給
Handler
。Message 包含 2 個 int 屬性和一個額外的物件。 雖然構造器是公開的,但獲取例項最好的辦法是呼叫
Message.obtain()
或
Handler.obtainMessage()
。這樣可以從他們的可回收物件池中獲取到訊息例項。一般來說,每個Message例項持有一個Handler。
Message部分屬性值
/*package*/ Handler target; // 指定的Handler /*package*/ Runnable callback; // 可以組成連結串列 // sometimes we store linked lists of these things /*package*/ Message next;
從這裡也不難看出,每個 Message 都持有 Handler 例項。如果 Handler 持有Activity的引用,Activity onDestroy 後 Message 卻仍然在佇列中,因為 Handler 與Activity的強關聯,會造成 Activity 無法被 GC 回收,導致記憶體洩露。 因此在Activity onDestroy 時,與Activity關聯的Handler應清除它的佇列由Activity產生的任務,避免記憶體洩露。
重置自身的方法,將屬性全部重置
public void recycle() void recycleUnchecked()
獲取 Message 例項的常用方法,得到的例項與傳入的 Handler 繫結
/** * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned. * @param h Handler to assign to the returned Message object's <em>target</em> member. * @return A Message object from the global pool. */ public static Message obtain(Handler h) { Message m = obtain(); m.target = h; return m; }
將訊息傳送給 Handler
/** * Sends this Message to the Handler specified by {@link #getTarget}. * Throws a null pointer exception if this field has not been set. */ public void sendToTarget() { target.sendMessage(this); // target 就是與訊息繫結的Handler }
呼叫這個方法後,Handler 會將訊息新增進它的訊息佇列
MessageQueue
中。
MessageQueue
持有一列可以被 Looper 分發的 Message。一般來說由 Handler 將 Message 新增到 MessageQueue 中。 獲取當前執行緒的 MessageQueue 方法是
Looper.myQueue()
。透過
Looper.getMainLooper()
獲取到主執行緒的 looper。
Looper 簡介
Looper 與 MessageQueue 緊密關聯。在一個執行緒中執行的訊息迴圈。執行緒預設情況下是沒有與之管理的訊息迴圈的。 要建立一個訊息迴圈,線上程中呼叫 prepare,然後呼叫 loop。即開始處理訊息,直到迴圈停止。大多數情況下透過Handler來與訊息迴圈互動。
Handler 與 Looper 線上程中互動的典型例子
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); // 為當前執行緒準備一個Looper // 建立Handler例項,Handler會獲取當前執行緒的Looper // 如果例項化Handler時當前執行緒沒有Looper,會報異常 RuntimeException mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); // Looper開始執行 } }
呼叫了
Looper.loop()
之後,looper 開始執行。當 looper 的 messageQueue 中沒有訊息時,相關的執行緒處於什麼狀態呢? 檢視looper的原始碼,看到loop方法裡面有一個死迴圈。
queue.next()
方法是可能會阻塞執行緒的。如果從queue中獲取到null,則表明此訊息佇列正在退出。此時looper的死迴圈也會被返回。
for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; }
呼叫 looper 的 quit 方法,實際上呼叫了
mQueue.quit(false)
。訊息佇列退出後,looper 的 loop 死迴圈也被退出了。
進入 MessageQueue 的
next
方法去看,發現裡面也有一個死迴圈。沒有訊息時,這個死迴圈會阻塞在
nativePollOnce
這個方法。
Message next() { // ... for (;;) { // ... nativePollOnce(ptr, nextPollTimeoutMillis); // 處理message物件
我們知道 Thread 有 New(新建,未執行),RUNNABLE(可執行),BLOCKED,WAITING(執行緒擁有了某個鎖之後, 呼叫了他的wait方法, 等待其他執行緒/鎖擁有者呼叫 notify / notifyAll),TIMED_WAITING,TERMINATED(已經執行完畢)這幾種狀態。
訊息佇列中沒有訊息,在
nativePollOnce
方法中“等待”。
nativePollOnce
效果上大致等同於
Object.wait()
,但它們的實現完全不同。
nativePollOnce
使用
epoll
, 而
Object.wait
使用
futex
。
“等待”時,相關執行緒則處於
WAITING
狀態。
Looper中的屬性
Looper 持有 MessageQueue;唯一的主執行緒 Looper
sMainLooper
;Looper 當前執行緒
mThread
; 儲存 Looper 的
sThreadLocal
// sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper; // guarded by Looper.class final MessageQueue mQueue; // Handler會獲取這個訊息佇列例項(參考Handler構造器) final Thread mThread; // Looper當前執行緒
ThreadLocal並不是執行緒,它的作用是可以在每個執行緒中儲存資料。
Looper方法
準備方法,將當前執行緒初始化為 Looper。退出時要呼叫 quit
public static void prepare() { prepare(true); } 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)); // Looper例項存入了sThreadLocal }
prepare
方法新建 Looper 並存入 sThreadLocal
sThreadLocal.set(new Looper(quitAllowed))
ThreadLocal<T>
類
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
當要獲取 Looper 物件時,從
sThreadLocal
獲取
// 獲取與當前執行緒關聯的Looper,返回可以為null public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
在當前執行緒執行一個訊息佇列。結束後要呼叫退出方法
quit()
public static void loop()
準備主執行緒 Looper。Android 環境會建立主執行緒 Looper,開發者不應該自己呼叫這個方法。 UI執行緒,它就是 ActivityThread,ActivityThread 被建立時就會初始化 Looper,這也是在主執行緒中預設可以使用 Handler 的原因。
public static void prepareMainLooper() { prepare(false); // 這裡表示了主執行緒Looper不能由開發者來退出 synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
獲取主執行緒的 Looper。我們開發者想操作主執行緒時,可呼叫此方法
public static Looper getMainLooper()
同一個 Thread 的不同 Handler
與 UI 執行緒對應的 MainLooper,可以關聯多個 Handler。 多個 Handler 之間的計劃任務不會互相影響。比如有 2 個關聯了 UI 執行緒的 handler。
Handler mMainHandler1; Handler mMainHandler2; private void initUtils() { mMainHandler1 = new Handler(Looper.getMainLooper()); mMainHandler2 = new Handler(Looper.getMainLooper()); Log.d(TAG, "mMainHandler1 post 任務"); mMainHandler1.postDelayed(new Runnable() { @Override public void run() { Log.d(TAG, "mMainHandler1的演示任務已執行 rustfisher"); } }, 1500); mMainHandler2.removeCallbacksAndMessages(null); }
mMainHandler2 取消它的任務並不會影響 mMainHandler1。
Handler 相關面試題
1. ⼦執行緒⼀定不能更新 UI 嗎?
答:不⼀定。
- Activity存在⼀種審計機制,這個機制會在Activity完全顯示之後⼯作,如果⼦執行緒在Activity完全顯示
之前更新UI是可⾏的;
- SurfaceView:多媒體影片播放,也可以在⼦執行緒中更新UI
- Progress:進度相關控制元件,也可以在⼦執行緒中更新UI
2. 給我說說 Handler 的原理
3. Handler 導致的記憶體洩露你是如何解決的?
4. 如何使⽤Handler讓⼦執行緒和⼦執行緒通訊?
- 傳送訊息的⼦執行緒
package com.cdc.handler; import android.os.Handler; import android.os.Message; import android.os.SystemClock; //傳送訊息的⼦執行緒 public class Thread1 extends Thread { private Handler handler; public Thread1(Handler handler){ super.setName("Thread1"); this.handler=handler; } @Override public void run() { Message msg = Message.obtain(); msg.what = 1; msg.obj = System.currentTimeMillis()+""; handler.sendMessage(msg); System.out.println((Thread.currentThread().getName() + "----傳送了消 息!" + msg.obj)); SystemClock.sleep(1000); } }
- 接收訊息的⼦執行緒
package com.cdc.handler; import android.os.Handler; import android.os.Looper; //接收訊息的⼦執行緒 public class Thread2 extends Thread{ private Handler handler2; public Handler getHandler(){//注意哦,在run執⾏之前,返回的是null return handler2; } public Thread2(){ super.setName("Thread2"); } @Override public void run() { //在⼦執行緒⾥⾯新建Handler的例項,需要先調⽤Looper.prepare();否則會報 錯:Can't create handler inside thread that has not called Looper.prepare() Looper.prepare(); handler2 = new Handler(){ public void handleMessage(android.os.Message msg) { //這⾥處理訊息 System.out.println(("收到訊息了:" + Thread.currentThread().getName() + "----" + msg.obj)); }; }; Looper.loop(); } }
- 調⽤
private Handler myHandler=null; private Thread2 thread1; private Thread1 thread2; @OnClick(R.id.handler3) public void handler3(){ thread1=new Thread2(); thread1.start(); myHandler=thread1.getHandler(); while(myHandler==null){ SystemClock.sleep(100); myHandler=thread1.getHandler(); } thread2=new Thread1(myHandler); thread2.start(); }
5. HandlerThread是什麼 & 原理 & 使⽤場景?
6. IdleHandler是什麼?
7. ⼀個執行緒能否建立多個Handler,Handler和Looper之間的對應關係?
8 為什麼Android系統不建議⼦執行緒訪問UI? ⾸先,UI控制元件不是執行緒安全的,如果多執行緒併發訪問UI控制元件可能會出現不可預期的狀態 那為什麼系統不對UI控制元件的訪問加上鎖機制呢? 缺點有兩個:
- 加上鎖機制會讓UI訪問的邏輯變得複雜;
- 鎖機制會降低UI訪問的效率,因為鎖機制會阻塞某些執行緒的執⾏
鑑於這兩個缺點,最簡單且⾼效的⽅法就是採⽤單執行緒模型來處理UI操作,所以原始碼ViewRootImpl中會有對執行緒的⼀個判斷,程式碼如下: frameworks/base/core/java/android/view/ViewRootImpl.java
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
對於開發者來說也不是很麻煩,只是透過 handler 切換⼀下 UI 訪問的執⾏執行緒即可
9. Looper 死迴圈為什麼不會導致應⽤卡死?
10. 使⽤ Handler 的 postDealy 後訊息佇列有什麼變化?
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70008155/viewspace-2844177/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android Handler機制之Handler 、MessageQueue 、LooperAndroidOOP
- 從源分析Handler、MessageQueue、LooperOOP
- Android多執行緒之Handler、Looper與MessageQueue原始碼解析Android執行緒OOP原始碼
- Android Handler MessageQueue Looper 訊息機制原理AndroidOOP
- Android 訊息機制:Handler、MessageQueue 和 LooperAndroidOOP
- Android訊息機制全面解析(Handler,MessageQueue,Looper,Threadlocal)AndroidOOPthread
- Handler,Looper,MessageQueue,Message直接聯絡OOP
- 三劍客 Handler、Looper 和 MessageQueueOOP
- Android Handler與Looper原理簡析AndroidOOP
- Android入門教程 | RecyclerView使用入門AndroidView
- Android入門教程 | Fragment (載入方法與通訊)AndroidFragment
- Android入門教程 | DialogFragment 的使用AndroidFragment
- Android入門教程 | RecyclerView實際使用AndroidView
- Android入門教程 | AsyncTask 使用介紹Android
- android 非同步通訊機制Handler的分析與運用Android非同步
- Android入門教程 | EditText 使用者輸入Android
- Metricbeat入門與使用
- Android入門教程 |res資源目錄簡介與shape的繪製和使用Android
- AsyncTask與Thread+Handler簡要分析thread
- Android入門教程 | Kotlin協程入門AndroidKotlin
- 【Android開發入門教程】二.Android應用程式結構分析Android
- Git與GitHub入門簡明教程Github
- Java入門教程十二(集合與泛型)Java泛型
- NodeJs安裝與使用入門NodeJS
- JDBC入門與簡單使用JDBC
- Android測試工具 UIAutomator入門與介紹AndroidUI
- 【Android開發入門教程】三.Activity入門指南!Android
- R語言入門與資料分析R語言
- Android入門教程 | SharedPreferences 簡介Android
- Android Handler機制之Message的傳送與取出Android
- Android WorkManager使用入門Android
- Android入門教程 | 使用 ConstraintLayout 構建自適應介面AndroidAI
- 《Django入門與實踐教程》完整版Django
- Python安裝與Pycharm使用入門PythonPyCharm
- Spring原理與原始碼分析系列(六)- Spring AOP入門與概述Spring原始碼
- Android 原始碼分析(二)handler 機制Android原始碼
- Android入門教程 | 多執行緒Android執行緒
- Android入門教程 | Fragment 基礎概念AndroidFragment