Handler最常見(jiàn)的使用場(chǎng)景就是下載回調(diào),為了不影響用戶體驗(yàn)Android不支持在主線程中進(jìn)行耗時(shí)時(shí)操作侧到,長(zhǎng)時(shí)間的耗時(shí)操作會(huì)產(chǎn)生ANR異常奶浦,而下載無(wú)疑是耗時(shí)操作,所以我們會(huì)在子線程中進(jìn)行下載捎迫。但晃酒,下載完畢進(jìn)行UI操作卻會(huì)發(fā)生異常,原來(lái)谷歌為了不讓UI的操作出現(xiàn)沖突(線程的不可確定性)窄绒,所以規(guī)定只能在主線程中進(jìn)行UI操作贝次,可這就尷尬了...即不讓在主線程中進(jìn)行聯(lián)網(wǎng)操作,又不讓在子線程中進(jìn)行UI操作彰导,我們?nèi)绾螌⒏嬖V主線程我們已經(jīng)下載完畢了呢蛔翅?這時(shí)就要用到Handler了.
Handler的簡(jiǎn)單使用
下面是最簡(jiǎn)單的使用.
// 1.創(chuàng)建Handler對(duì)象, 重寫(xiě)方法.
mTestHandler = new TestHandler(this);
-----------------------------點(diǎn)擊模擬下載---------------------------------
public void download(View view) {
text.setText("開(kāi)始下載...");
new Thread(){
@Override
public void run() {
// 2.創(chuàng)建消息對(duì)象
Message msg = mTestHandler.obtainMessage();
msg.obj = "下載完畢";
msg.arg1 = 1;
// 3.發(fā)送延時(shí)消息
mTestHandler.sendMessageDelayed(msg, 2000);
}
}.start();
}
---------------------------創(chuàng)建一個(gè)類(lèi)繼承Handler--------------------------------
// * 用軟引用的方法持有Activity對(duì)象防止內(nèi)存泄露
private WeakReference<MainActivity> activity ;
public TestHandler(MainActivity activity){
this.activity = new WeakReference<>(activity);
}
// 回調(diào)方法.
@Override
public void handleMessage(Message msg) {
switch (msg.arg1){
case 1:
activity.get().text.setText((String) msg.obj);
break;
}
}
繼承Handler對(duì)象并重寫(xiě)handleMessage()
,然后創(chuàng)建Handler對(duì)象位谋,調(diào)用obtainMessagee()
方法獲取Message對(duì)象山析,將數(shù)據(jù)賦予Message,并發(fā)送出去掏父,而發(fā)送的消息會(huì)回調(diào)給Handler的handleMessage()
方法.
在子線程中創(chuàng)建Handler對(duì)象.
剛才我們?cè)谥骶€程中創(chuàng)建了Handler對(duì)象笋轨,在子線程中調(diào)用Handler的sendMessageDelayed()
方法將Message帶到主線程間完成了線程中的通信,那我們能在子線程中創(chuàng)建Handler嗎 ? 答案是不可以。在子線程中創(chuàng)建Handler對(duì)象會(huì)拋出如下異常:
Can't create handler inside thread that has not called Looper.prepare()
異常說(shuō)的很明白它需要調(diào)用Looper.prepare()
爵政。我們來(lái)看看源碼鸟款,為什么在主線和中可以創(chuàng)建Handler對(duì)象呢?點(diǎn)擊new Handler()茂卦,在兩個(gè)參數(shù)的構(gòu)造方法中我們發(fā)現(xiàn)了剛才拋出異常的代碼.
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
可以看到異常拋出的原因是因?yàn)閙Looper這個(gè)對(duì)象為null何什,我們來(lái)看看myLooper里面做了什么?
-----------可我們并沒(méi)有在主線程中調(diào)用這個(gè)方法暗攘处渣?那是因?yàn)樵摲椒ㄔ?strong>ActivityThread類(lèi)里調(diào)用了,我們這里暫時(shí)不看它. 繼續(xù)查看Looper.myLooper()
里面的內(nèi)容.
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
myLooper()實(shí)現(xiàn)調(diào)用了ThreadLocal身上的get()
方法蛛砰,它返回一個(gè)Looper罐栈,既然有get()
必然在一個(gè)地方set()
了,于是在Looper中發(fā)現(xiàn)以下代碼
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));
}
ThreadLocal
我們把Looper對(duì)象存放在了ThreadLocal中泥畅,當(dāng)需要Looper時(shí)也是從ThreadLocal中取的荠诬,為什么要將Looper存放在ThreadLocal中呢?下面是它的get()
方法.
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();
}
ThreadLocal內(nèi)部有一個(gè)Map集合位仁,根據(jù)它所在的線程名取Looper柑贞,而ThreadLocal是在創(chuàng)建Looper時(shí)創(chuàng)建的所以Looper與ThreadLocal是同一線程的,而我們創(chuàng)建handler時(shí)會(huì)調(diào)用myLooper()
方法聂抢,該方法調(diào)用get()
方法時(shí)會(huì)根據(jù)handler所有線程名取Looper對(duì)象钧嘶,所以我們要保證Looper.prepare()
與Handler在同一線程中.
現(xiàn)在讓我們來(lái)理一下思路,首先創(chuàng)建Handler需要Looper琳疏,而Looper是在prepare()
方法中創(chuàng)建的有决,也就是說(shuō)如果我們想在子線程中創(chuàng)建Handler只需要在之前調(diào)用
Looper.prepare()
即可,下面是我們測(cè)試的代碼.
public void run() {
Looper.prepare();
mTestHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
Log.d("MainActivity", Thread.currentThread().getName());
}
};
mTestHandler.sendMessage(mTestHandler.obtainMessage());
}
MesageQuene
可是問(wèn)題又來(lái)了空盼,雖然沒(méi)有拋出異常书幕,handleMessage()
方法卻一直接收不到消息.既然是收不到消息,那我們來(lái)看看Handler內(nèi)部是怎么發(fā)送消息的吧揽趾,我們繼續(xù)查看sendMessage()
方法.
// 這里我們看到了前面的發(fā)送延時(shí)消息方法台汇,延時(shí)+當(dāng)時(shí)時(shí)間 并調(diào)用下一方法
sendMessage(msg) -> sendMessageDelayed(msg, 0)
-> sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
----------------------------------------------------------------------
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
...
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
----------------------------------------------------------------------
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; // this = handler; 把消息與handler進(jìn)行綁定.
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
最后調(diào)用了MesageQuene的enqueueMessage()
方法,即入隊(duì)列但骨,那MesageQuene對(duì)象是在什么時(shí)候創(chuàng)建的呢励七?是在創(chuàng)建Looper對(duì)象時(shí)創(chuàng)建的。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
繼續(xù)來(lái)看MesageQuene的enqueueMessage()
方法.
...
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;
...
enqueueMessage()
就是入隊(duì)列的意思奔缠,Message 是單鏈表結(jié)構(gòu)掠抬,它持有下一個(gè)Message 對(duì)象的引用,因?yàn)?strong>MesageQuene中可能存有多個(gè)未處理的消息校哎,所以需要判斷MesageQuene中有多少消息两波,若有多個(gè)則將當(dāng)前消息放置在最后瞳步,可以看成集合的add方法.
Looper
sendMessage()方法只是將消息加入消息隊(duì)列中,那消息是如何取出并發(fā)給handler的呢腰奋?我們來(lái)看下Looper.loop()
...
for (;;) {
Message msg = queue.next(); // might block
...
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
這里的target就是handler(在handler的enqueueMessage()
方法里单起,我們將消息與handler進(jìn)行了綁定),所以調(diào)用了handler身上的dispatchMessage()
方法劣坊,而該方法最終又調(diào)用了handleMessage()
方法嘀倒,并將消息傳遞進(jìn)去.
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
上面就是handler發(fā)送消息的大致流程最后我們總結(jié)一下.
總結(jié):創(chuàng)建Handler對(duì)象時(shí)會(huì)調(diào)用Looper.prepare()
方法,該方法會(huì)用從ThreadLocal對(duì)象中取出looper, 所以如果ThreadLocal中沒(méi)有先存Looper或不在同一線程中則取不到對(duì)象,Handler就會(huì)拋出異常.
- 問(wèn)題1.:主線程在哪里調(diào)用了
prepare()
方法的?見(jiàn)底部代碼.
用handler發(fā)送消息方法時(shí)局冰,會(huì)調(diào)用到MesageQuene的方法, 而mQuene是在創(chuàng)建Looper對(duì)象時(shí)創(chuàng)建的, Looper對(duì)象是在調(diào)用prepare()
方法時(shí)創(chuàng)建的, 也就是說(shuō)mQuene與Looper是在同一線程.
我們發(fā)送消息時(shí)會(huì)將所發(fā)送的消息加入消息隊(duì)列测蘑,而后調(diào)用Looper.loop()方法才能將消息取出并傳送給handler,如果不調(diào)用Looper.loop()則消息無(wú)法取出
- 問(wèn)題2:為什么在主線中創(chuàng)建可以接收消息康二?見(jiàn)底部代碼.
最后在子線程中創(chuàng)建handler那handleMessage是在哪個(gè)線程回調(diào)的呢碳胳?
這個(gè)問(wèn)題在上面就已經(jīng)說(shuō)了,handleMessage在loop()方法調(diào)用沫勿,而loop()與Looper是在同一線程挨约,也就是說(shuō)最終會(huì)在子線程回調(diào).那如何讓回調(diào)在主線程呢?調(diào)用prepareMainLooper()方法产雹,而不是prepare()就可讓回調(diào)在主線程中運(yùn)行.
下面代碼很好回答了第1和第2問(wèn)題.
public static final void main(String[] args) {
....
// 1.主線程創(chuàng)建Looper
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = new Handler();
}
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
...