原文鏈接:https://segmentfault.com/a/1190000005926314
Handler:是一個消息分發(fā)對象,進行發(fā)送和處理消息,并且其 Runnable 對象與一個線程的 MessageQueue 關聯(lián)。
作用:調度消息,將一個任務切換到某個指定的線程中去執(zhí)行。
為什么需要 Handler?
子線程不允許訪問 UI
假若子線程允許訪問 UI,則在多線程并發(fā)訪問情況下,會使得 UI 控件處于不可預期的狀態(tài)。
傳統(tǒng)解決辦法:加鎖,但會使得UI訪問邏輯變的復雜,其次降低 UI 訪問的效率。
引入 Handler
采用單線程模型處理 UI 操作,通過 Handler 切換到 UI 線程,解決子線程中無法訪問 UI 的問題。
Handler 使用
方式一: post(Runnable)
創(chuàng)建一個工作線程,實現(xiàn) Runnable 接口,實現(xiàn) run 方法,處理耗時操作
創(chuàng)建一個 handler,通過 handler.post/postDelay,投遞創(chuàng)建的 Runnable,在 run 方法中進行更新 UI 操作。
new Thread(new Runnable() {
@Override
public void run() {
/**
耗時操作
*/
handler.post(new Runnable() {
@Override
public void run() {
/**
更新UI
*/
}
});
}
}).start();
方式二: sendMessage(Message)
創(chuàng)建一個工作線程,繼承 Thread,重新 run 方法,處理耗時操作
創(chuàng)建一個 Message 對象,設置 what 標志及數(shù)據(jù)
通過 sendMessage 進行投遞消息
創(chuàng)建一個handler,重寫 handleMessage 方法,根據(jù) msg.what 信息判斷,接收對應的信息,再在這里更新 UI。
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) { //判斷標志位
case 1:
/**
獲取數(shù)據(jù),更新UI
*/
break;
}
}
};
public class WorkThread extends Thread {
@Override
public void run() {
super.run();
/**
耗時操作
*/
//從全局池中返回一個message實例,避免多次創(chuàng)建message(如new Message)
Message msg =Message.obtain();
msg.obj = data;
msg.what=1; //標志消息的標志
handler.sendMessage(msg);
}
}
new WorkThread().start();
Handler 存在的問題
內存方面
Handler 被作為 Activity 引用,如果為非靜態(tài)內部類,則會引用外部類對象。當 Activity finish 時,Handler可能并未執(zhí)行完,從而引起 Activity 的內存泄漏。故而在所有調用 Handler 的地方,都用靜態(tài)內部類。
異常方面
當 Activity finish 時,在 onDestroy 方法中釋放了一些資源。此時 Handler 執(zhí)行到 handlerMessage 方法,但相關資源已經(jīng)被釋放,從而引起空指針的異常。
避免
如果是使用 handlerMessage,則在方法中加try catch。
如果是用 post 方法,則在Runnable方法中加try catch。
Handler 的改進
內存方面:使用靜態(tài)內部類創(chuàng)建 handler 對象,且對 Activity 持有弱引用
異常方面:不加 try catch,而是在 onDestory 中把消息隊列 MessageQueue 中的消息給 remove 掉。
則使用如下方式創(chuàng)建 handler 對象:
/**
* 為避免handler造成的內存泄漏
* 1、使用靜態(tài)的handler,對外部類不保持對象的引用
* 2、但Handler需要與Activity通信,所以需要增加一個對Activity的弱引用
*/
private static class MyHandler extends Handler {
private final WeakReference<Activity> mActivityReference;
MyHandler(Activity activity) {
this.mActivityReference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity = (MainActivity) mActivityReference.get(); //獲取弱引用隊列中的activity
switch (msg.what) { //獲取消息,更新UI
case 1:
byte[] data = (byte[]) msg.obj;
activity.threadIv.setImageBitmap(activity.getBitmap(data));
break;
}
}
}
并在 onDesotry 中銷毀:
@Override
protected void onDestroy() {
super.onDestroy();
//避免activity銷毀時,messageQueue中的消息未處理完;故此時應把對應的message給清除出隊列
handler.removeCallbacks(postRunnable); //清除runnable對應的message
//handler.removeMessage(what) 清除what對應的message
}
Handler 通信機制
- 創(chuàng)建Handler,并采用當前線程的Looper創(chuàng)建消息循環(huán)系統(tǒng);
- Handler通過sendMessage(Message)或Post(Runnable)發(fā)送消息,調用enqueueMessage把消息插入到消息鏈表中;
- Looper循環(huán)檢測消息隊列中的消息,若有消息則取出該消息,并調用該消息持有的handler的dispatchMessage方法,回調到創(chuàng)建Handler線程中重寫的handleMessage里執(zhí)行。
Handler 如何關聯(lián) Looper、MessageQueue
1、Handler 發(fā)送消息
上一段很熟悉的代碼:
Message msg =Message.obtain();
msg.obj = data;
msg.what=1; //標志消息的標志
handler.sendMessage(msg);
從sendMessageQueue開始追蹤,函數(shù)調用關系:sendMessage -> sendMessageDelayed ->sendMessageAtTime,在sendMessageAtTime中,攜帶者傳來的message與Handler的mQueue一起通過enqueueMessage進入隊列了。
對于postRunnable而言,通過post投遞該runnable,調用getPostMessage,通過該runnable構造一個message,再通過 sendMessageDelayed投遞,接下來和sendMessage的流程一樣了。
2、消息入隊列
在enqueueMessage中,通過MessageQueue入隊列,并為該message的target賦值為當前的handler對象,記住msg.target很重要,之后Looper取出該消息時,還需要由msg.target.dispatchMessage回調到該handler中處理消息。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
在MessageQueue中,由Message的消息鏈表進行入隊列
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
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;
}
3、Looper 處理消息
再說處理消息之前,先看Looper是如何構建與獲取的:
- 構造Looper時,構建消息循環(huán)隊列,并獲取當前線程
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
- 但該函數(shù)是私有的,外界不能直接構造一個Looper,而是通過Looper.prepare來構造的:
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));
}
- 這里創(chuàng)建Looper,并把Looper對象保存在sThreadLocal中,那sThreadLocal是什么呢?
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
它是一個保存Looper的TheadLocal實例,而ThreadLocal是線程私有的數(shù)據(jù)存儲類,可以來保存線程的Looper對象,這樣Handler就可以通過ThreadLocal來保存于獲取Looper對象了。
- TheadLocal 如何保存與獲取Looper?
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
public T get() {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
在 set 中都是通過 values.put 保存當前線程的 Looper 實例,通過 values.getAfterMiss(this)獲取,其中put和getAfterMiss都有key和value,都是由Value對象的table數(shù)組保存的,那么在table數(shù)組里怎么存的呢?
table[index] = key.reference;
table[index + 1] = value;
很顯然在數(shù)組中,前一個保存著ThreadLocal對象引用的索引,后一個存儲傳入的Looper實例。
- 接下來看Looper在loop中如何處理消息
在loop中,一個循環(huán),通過next取出MessageQueue中的消息
若取出的消息為null,則結束循環(huán),返回。
設置消息為空,可以通過MessageQueue的quit和quitSafely方法通知消息隊列退出。
若取出的消息不為空,則通過msg.target.dispatchMessage回調到handler中去。
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;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
Long.toHexString(ident) + " to 0x"
Long.toHexString(newIdent) + " while dispatching to "
msg.target.getClass().getName() + " "
msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
4、handler處理消息
Looper把消息回調到handler的dispatchMessage中進行消息處理:
若該消息有callback,即通過Post(Runnable)的方式投遞消息,因為在投遞runnable時,把runnable對象賦值給了message的callback。
若handler的mCallback不為空,則交由通過callback創(chuàng)建handler方式去處理。
否則,由最常見創(chuàng)建handler對象的方式,在重寫handlerMessage中處理。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}