Android Handler 機制
Handler 簡介
一個Handler允許傳送和處理Message,通過關聯執行緒的 MessageQueue 執行 Runnable 物件。
每個Handler例項都和一個單獨的執行緒及其訊息佇列繫結。
可以將一個任務切換到Handler所在的執行緒中去執行。一個用法就是子執行緒通過Handler更新UI。
主要有2種用法:
-
- 做出計劃,在未來某個時間點執行訊息和Runnable
-
- 在其他執行緒規劃並執行任務
要使用好Handler,需要了解與其相關的 MessageQueue
, Message
和Looper
;不能孤立的看Handler
Handler就像一個操作者(或者像一個對開發者開放的視窗),利用MessageQueue
Looper
來實現任務排程和處理
// 這個回撥允許你使用Handler時不新建一個Handler的子類
public interface Callback {
publicbooleanhandleMessage(Message msg);
}
final Looper mLooper; // Handler持有 Looper 的例項
final MessageQueue mQueue; // 持有訊息佇列
final Callback mCallback;
在Handler的構造器中,我們可以看到訊息佇列是相關的Looper管理的
public Handler(Callback callback, boolean async) {
// 處理異常
mLooper = Looper.myLooper();
// 處理特殊情況...
mQueue = mLooper.mQueue; // 獲取的是Looper的訊息佇列
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue ; // 獲取的是Looper的訊息佇列
mCallback = callback;
mAsynchronous = async;
}
Android是訊息驅動的,實現訊息驅動有幾個要素:
- 訊息的表示:Message
- 訊息佇列:MessageQueue
- 訊息迴圈,用於迴圈取出訊息進行處理:Looper
- 訊息處理,訊息迴圈從訊息佇列中取出訊息後要對訊息進行處理:Handler
初始化訊息佇列
在Looper構造器中即建立了一個MessageQueue
傳送訊息
通過Looper.prepare初始化好訊息佇列後就可以呼叫Looper.loop進入訊息迴圈了,然後我們就可以向訊息佇列傳送訊息,
訊息迴圈就會取出訊息進行處理,在看訊息處理之前,先看一下訊息是怎麼被新增到訊息佇列的。
訊息迴圈
Java層的訊息都儲存在了Java層MessageQueue的成員mMessages中,Native層的訊息都儲存在了Native Looper的
mMessageEnvelopes中,這就可以說有兩個訊息佇列,而且都是按時間排列的。
為什麼要用Handler這樣的一個機制
因為在Android系統中UI操作並不是執行緒安全的,如果多個執行緒併發的去操作同一個元件,可能導致執行緒安全問題。
為了解決這一個問題,android制定了一條規則:只允許UI執行緒來修改UI元件的屬性等,也就是說必須單執行緒模型,
這樣導致如果在UI介面進行一個耗時較長的資料更新等就會形成程式假死現象 也就是ANR異常,如果20秒中沒有完成
程式就會強制關閉。所以比如另一個執行緒要修改UI元件的時候,就需要藉助Handler訊息機制了。
Handler傳送和處理訊息的幾個方法
1.void handleMessage( Message msg):處理訊息的方法,該方法通常被重寫。
2.final boolean hasMessage(int what):檢查訊息佇列中是否包含有what屬性為指定值的訊息
3.final boolean hasMessage(int what ,Object object) :檢查訊息佇列中是否包含有what好object屬性指定值的訊息
4.sendEmptyMessage(int what):傳送空訊息
5.final Boolean send EmptyMessageDelayed(int what ,long delayMillis):指定多少毫秒傳送空訊息
6.final boolean sendMessage(Message msg):立即傳送訊息
7.final boolean sendMessageDelayed(Message msg,long delayMillis):多少秒之後傳送訊息
與Handler工作的幾個元件Looper、MessageQueue各自的作用:
- 1.Handler:它把訊息傳送給Looper管理的MessageQueue,並負責處理Looper分給它的訊息
- 2.MessageQueue:管理Message,由Looper管理
- 3.Looper:每個執行緒只有一個Looper,比如UI執行緒中,系統會預設的初始化一個Looper物件,它負責管理MessageQueue,
不斷的從MessageQueue中取訊息,並將相對應的訊息分給Handler處理
Handler.java
(frameworks/base/core/java/android/os)
// 將訊息新增到佇列前,先判斷佇列是否為null
publicbooleansendMessageAtTime(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);
}
// ......
// 將訊息新增到佇列中
privatebooleanenqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; // 將自己指定為Message的Handler
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
從這裡也不難看出,每個Message都持有Handler。如果Handler持有Activity的引用,Activity onDestroy後Message卻仍然在佇列中,
因為Handler與Activity的強關聯,會造成Activity無法被GC回收,導致記憶體洩露。
因此在Activity onDestroy 時,與Activity關聯的Handler應清除它的佇列由Activity產生的任務,避免記憶體洩露。
訊息佇列 MessageQueue.java
(frameworks/base/core/java/android/os)
// 新增訊息
booleanenqueueMessage(Message msg, long when) {
// 判斷並新增訊息...
return true;
}
Handler.sendEmptyMessage(int what) 流程解析
獲取一個Message例項,並立即將Message例項新增到訊息佇列中去。
簡要流程如下
// Handler.java
// 立刻傳送一個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
booleanenqueueMessage(Message msg, long when)
部分原始碼如下
publicfinalbooleansendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
publicfinalbooleansendEmptyMessage(int what)
{
return sendEmptyMessageDelayed(what, 0);
}
publicfinalbooleansendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
publicbooleansendMessageAtTime(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);
}
privatebooleanenqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
Handler 取消任務 removeCallbacksAndMessages
要取消任務時,呼叫下面這個方法
publicfinalvoidremoveCallbacksAndMessages(Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
通過呼叫Message.recycleUnchecked()
方法,取消掉與此Handler相關聯的Message。
相關的訊息佇列會執行取消指令
voidremoveCallbacksAndMessages(Handler h, Object object)
Message 和 MessageQueue 簡介
Message
Message 屬於被傳遞,被使用的角色
Message 是包含描述和任意資料物件的“訊息”,能被髮送給Handler
。
包含2個int屬性和一個額外的物件
雖然構造器是公開的,但獲取例項最好的辦法是呼叫Message.obtain()
或Handler.obtainMessage()
。
這樣可以從他們的可回收物件池中獲取到訊息例項
一般來說,每個Message例項握有一個Handler
部分屬性值
/*package*/ Handler target; // 指定的Handler
/*package*/ Runnable callback;
// 可以組成連結串列
// sometimes we store linked lists of these things
/*package*/ Message next;
重置自身的方法,將屬性全部重置
publicvoidrecycle()
voidrecycleUnchecked()
獲取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. */
publicstatic 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. */
publicvoidsendToTarget() {
target.sendMessage(this); // target 就是與訊息繫結的Handler
}
呼叫這個方法後,Handler會將訊息新增進它的訊息佇列MessageQueue
中
MessageQueue
持有一列可以被Looper分發的Message。
一般來說由Handler將Message新增到MessageQueue中。
獲取當前執行緒的MessageQueue方法是Looper.myQueue()
Looper 簡介
Looper與MessageQueue緊密關聯
在一個執行緒中執行的訊息迴圈。執行緒預設情況下是沒有與之管理的訊息迴圈的。
要建立一個訊息迴圈,線上程中呼叫prepare,然後呼叫loop。即開始處理訊息,直到迴圈停止。
大多數情況下通過Handler來與訊息迴圈互動。
Handler與Looper線上程中互動的典型例子
class LooperThread extends Thread {
public Handler mHandler;
publicvoidrun() {
Looper.prepare(); // 為當前執行緒準備一個Looper
// 建立Handler例項,Handler會獲取當前執行緒的Looper
// 如果例項化Handler時當前執行緒沒有Looper,會報異常 RuntimeException
mHandler = new Handler() {
publicvoidhandleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop(); // Looper開始執行
}
}
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
publicstaticvoidprepare() {
prepare(true);
}
privatestaticvoidprepare(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>
類
publicvoidset(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()
publicstaticvoidloop()
準備主執行緒Looper。Android環境會建立主執行緒Looper,開發者不應該自己呼叫這個方法。
UI執行緒,它就是ActivityThread,ActivityThread被建立時就會初始化Looper,這也是在主執行緒中預設可以使用Handler的原因。
publicstaticvoidprepareMainLooper() {
prepare(false); // 這裡表示了主執行緒Looper不能由開發者來退出
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
獲取主執行緒的Looper。我們開發者想操作主執行緒時,可呼叫此方法
publicstatic Looper getMainLooper()