1.Handler是Android中用來在線程中傳遞消息的工具,它提供了一種異步的回調機制,使在完成了一個相對耗時的操作後作出相應和通知。
2.Handler的使用,爲了能讓handler在線程間傳遞消息,還需要使用到Looper,messageQueue,message。
Looper是爲了指定的單一線程創建一個消息循環,與線程一一對應,在UI線程中會自動建立一個Looper,
而在子線程中則需要手動創建或者使用主線程的Looper。
在Looper與線程進行關聯時會同時產生一個messageQueue消息隊列,用來存放handler所發送的message,遵循先進先出原則。
message包含必要的描述和屬性數據,並且此對象可以被髮送給Handler處理,屬性字段:arg1、arg2、what、obj、replyTo等。
what是用來保存消息標示的;obj是Object類型的任意對象;replyTo是消息管理器,會關聯到一個handler,handler就是處理其中的消息。
通常Message可以直接new出來的,但推薦調用handler中的obtainMessage方法來直接獲得Message對象。
(從系統線程池中直接取出,可以避免message的創建和銷燬,從而節省資源。)
在主線程(UI)中使用handler很簡單,只需要創建一個handler對象,並實現他的handleMessage方法,在該方法中對接收到的消息作出相應處理。
3.handler發送消息的機制。在使用handler.sendEmptyMessage(0);發送一個消息對象後,message對象會被放入一個messageQueue隊列中,
而該隊列屬於某個Lopper對象,每個Looper對象通過ThreadLocal.set(new Looper())跟一個Thread進行綁定,Looper對象所屬的線程在Looper.Loop
方法中循環執行從messageQueue消息隊列中讀取message對象並把message對象交由handler處理,調用handler的dispatchMessage方法。
4.子線程中的handler,當在新建的子線程中建立handler時,程序會報RuntimeException異常,產生這個異常的原因是因爲子線程沒有建立Looper,
爲什麼主線程中不會報錯 呢?來看一下ActivityThread的源碼:
public static final void main(String[] args) {
SamplingProfilerIntegration.start();
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
Looper.loop();
if (Process.supportsProcesses()) {
throw new RuntimeException("Main thread loop unexpectedly exited");
}
thread.detach();
String name = (thread.mInitialApplication != null)
? thread.mInitialApplication.getPackageName()
: "<unknown>";
Slog.i(TAG, "Main thread of " + name + " is now exiting");
}
可以看到,在main函數中它已經做了這個事情了,調用 Looper.prepareMainLooper(); Looper.loop();在prepareMainLooper方法中建立了一個looper對象,並且與當前的進程進行綁定,在Looper.loop方法中,線程建立消息循環機制,循環從MessageQueue獲取Message對象,調用 msg.target.dispatchMessage(msg);進行處理msg.target在myThreadHandler.sendEmptyMessage(0)設置進去的,因爲一個Thead中可以建立多個Hander,通過msg.target保證MessageQueue中的每個msg交由發送message的handler進行處理,然而Handler是怎樣與Looper建立聯繫呢,打開Handler構造函數中會發現這樣一段代碼:
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
當新建Handler對象時需要設置mLooper成員,Looper.myLooper是從當前線程中獲取綁定的Looper對象:
public static final Looper myLooper() {
return (Looper)sThreadLocal.get();
}
若Looper對象沒有創建,就會拋出"Can't create handler inside thread that has not called Looper.prepare()"這樣一個異常。
所以想要在子線程中創建一個Handler對象就需要以下這種方式:
class MyThread extends Thread {
public void run() {
// 其它線程中新建一個handler
Log.i(TAG, MessageFormat.format("Thread run...", Thread.currentThread().getName()));
// 創建該線程的Looper對象,用於接收消息,在非主線程中是沒有looper的所以在創建handler前一定要使用prepare()創建一個Looper
Looper.prepare();
newThreadHandler = new Handler() {
public void handleMessage(Message msg) {
Log.d(Constant.TAG, MessageFormat.format(
"newThreadHandler run...", Thread.currentThread().getName()));
}
};
Looper.myLooper().loop();// 建立一個消息循環,該線程不會退出
}
}
在其它線程中Handler使用主線程的Looper,前面我說了在新線程中要新建一個Handler需要調用Looper.prepare();
也有另一種方法就是使用主線程中的Looper,那就不必新建Looper對象了:
getMainLoopHandler =new Handler(Looper.getMainLooper()){
public void handleMessage(android.os.Message msg) {
Log.i(TAG, MessageFormat.format("handleMessage run...", Thread
.currentThread().getName()));
}
//該handleMessage方法將在UI線程中執行
};
這時候注意不要在handleMessage做太多的操作,因爲它在主線程中執行,會影響主線程執行ui更新操作。使用Message.callback回調
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
從dispatchMessage定義可以看出,如果Message對象自帶callback對象,handler不會執行handleMessage方法而是執行message.callback中定義的run方法,當然callback還是在handler關聯的looper所綁定的線程中執行的。實際上Handler.post(Runnable r)方法就是把r添加到一個msg.callback的,也就是說,下面兩種寫法,沒有什麼區別:
1.使用Message.callback
Message msg = Message.obtain(newThreadHandler,new Runnable() {
@Override
public void run() {
Log.i(TAG, MessageFormat.format("newThreadHandler.Message.callback.run",
Thread.currentThread().getName()));
}
});
newThreadHandler.sendMessage(msg);
2.使用Handler.post
newThreadHandler.post(new Runnable() {
@Override
public void run() {
Log.i(TAG, MessageFormat.format("newThreadHandler.Message.callback.run",
Thread.currentThread().getName()));
}
});