目錄
從零實(shí)現(xiàn)ImageLoader(一)—— 架構(gòu)
從零實(shí)現(xiàn)ImageLoader(二)—— 基本實(shí)現(xiàn)
從零實(shí)現(xiàn)ImageLoader(三)—— 線程池詳解
從零實(shí)現(xiàn)ImageLoader(四)—— Handler的內(nèi)心獨(dú)白
從零實(shí)現(xiàn)ImageLoader(五)—— 內(nèi)存緩存LruCache
從零實(shí)現(xiàn)ImageLoader(六)—— 磁盤緩存DiskLruCache
前情回顧
在上一篇文章里,我們實(shí)現(xiàn)了ImageLoader的異步加載功能,探索了線程池的原理,不過卻遺留了一個(gè)問題,也就是這一句代碼:
ImageLoader.HANDLER.post(() -> imageView.setImageBitmap(image));
上一篇文章里只是簡單的提了一下這句代碼將imageView.setImageBitmap(image)切換到了主線程執(zhí)行,可這是怎么做到的呢?要知道,用戶可以在主線程使用我們的ImageLoader,同樣也可以在子線程使用,我們甚至都不知道自己處于什么線程。
而這句代碼可以成功的關(guān)鍵就在于HANDLER的初始化:
public class ImageLoader {
static final Handler HANDLER = new Handler(Looper.getMainLooper());
}
看到這句代碼,有的同學(xué)可能已經(jīng)有了答案,也有同學(xué)可能依然一頭霧水,不管你看沒看懂,今天的這篇文章一定會讓你對Handler的運(yùn)行機(jī)制有一個(gè)更加清晰的理解。
原理
Handler這個(gè)我們平時(shí)開發(fā)時(shí)常見的老朋友,他的作用應(yīng)該已經(jīng)不必多說了,大多數(shù)的同學(xué)應(yīng)該也對MessageQueue、Looper和Message這幾個(gè)類有所了解,可他們之間是怎么協(xié)同工作的呢?讓我們先來看一張圖:

很明顯,這是一個(gè)典型的生產(chǎn)者-消費(fèi)者模型,Handler通過sendMessage()方法將消息放入阻塞隊(duì)列MessageQueue中,而Looper.loop()方法則會一直檢測MessageQueue中是否有可用的消息,得到消息后,Looper就會調(diào)用Handler.dispatchMessage(),進(jìn)而通過handleMessage()處理消息。
需要注意的是,這只是一個(gè)線程里的結(jié)構(gòu),如果是多線程的話,每個(gè)使用Handler線程都應(yīng)該有一個(gè)這樣的結(jié)構(gòu),所以準(zhǔn)確一點(diǎn)的圖應(yīng)該是這樣:

那有人就要問了,按照上面的說法,消息在同一個(gè)線程里轉(zhuǎn)來轉(zhuǎn)去有什么意義呢?說好的線程間通信呢?
Java里的堆內(nèi)存是線程間共享的,所以理論上來說在一個(gè)線程里可以拿到另一個(gè)線程的任意對象(其實(shí)對象都在一個(gè)地方,也就不分哪個(gè)線程了,這么說只是為了方便理解),我們這里需要的只是Handler,他就是開啟線程間通信大門的鑰匙,拿到了哪個(gè)線程的Handler也就可以向哪個(gè)線程發(fā)送消息。而我們平時(shí)也就是這么使用的:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(() -> {
//在子線程中使用主線程的Handler向主線程發(fā)送消息
mHandler.sendMessage(Message.obtain());
}).start();
}
private Handler mHandler = new MyHandler();
static class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
doSomething();
}
}
}
Looper的線程獨(dú)立性
明白了Handler的工作原理,我們再來學(xué)習(xí)源碼加深一下印象。
要想明白Handler是怎么實(shí)現(xiàn)的,就得先知道Looper是怎么做到線程獨(dú)立的。
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));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
可以看到這里使用ThreadLocal實(shí)現(xiàn)了每個(gè)線程都擁有獨(dú)立的Looper,而MessageQueue作為Looper的成員變量也同時(shí)做到了線程的獨(dú)立。
Handler與Looper的聯(lián)系
那Handler又是如何和Looper聯(lián)系到一起的呢?這就要看Handler的構(gòu)造方法了:
public Handler(Callback callback, boolean async) {
...
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;
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
就在這里Handler確認(rèn)了自己的歸屬,默認(rèn)當(dāng)然是屬于創(chuàng)建自己的線程,而通過Looper參數(shù)手動指定歸屬線程也未嘗不可,這也就是我們文章一開頭所做的。
消息入列
Handler所有的sendMessage()方法最終都調(diào)用了enqueueMessage():
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
很明顯Handler通過queue.enqueueMessage()方法將消息放入了消息隊(duì)列。
消息出列
消息的出列自然是在Looper.loop()方法中了:
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;
for (;;) {
Message msg = queue.next(); // 由于MessageQueue是阻塞隊(duì)列,這里有可能阻塞住
if (msg == null) {
// 如果msg為空證明消息隊(duì)列已經(jīng)退出了
return;
}
...
msg.target.dispatchMessage(msg);
...
}
}
loop()中的代碼看起來很多實(shí)際上大多數(shù)都是一些log代碼,而刪去log代碼剩下的這些相信已經(jīng)一目了然了,loop()中的for循環(huán)會一直嘗試從消息隊(duì)列中取出Message,之后根據(jù)Message.target調(diào)用Handler.dispatchMessage()方法。而dispatchMessage()又會調(diào)用Message.handleMessage(),最終完成消息的處理。
時(shí)序圖
最后我們讓以一個(gè)時(shí)序圖來結(jié)束今天Handler的原理探索:
