引言
目前市面上的圖片加載框架不要太多:universal-imageloader如蚜、Glide探赫、Piccosa撬呢、Freso...簡直群魔亂舞。但是作為一名有追求的coder搁嗓,必須懂其中的實(shí)現(xiàn)原理腺逛,并敢于制造輪子棍矛。由于這個專題涉及的內(nèi)容比較多够委,暫定分兩篇博客講完它茁帽。一個完整的圖片加載框架主要包括三大塊:
1.圖片下載任務(wù)調(diào)度.
2.圖片的縮放.
3.圖片的兩級緩存處理.
其中圖片加載任務(wù)的調(diào)度是框架的核心脐雪,這里結(jié)合Android的Handler機(jī)制單獨(dú)講解恢共,在本篇的結(jié)尾會模擬圖片的加載場景脂信,初步搭建圖片加載并發(fā)框架透硝,第二篇博客會給出完全的實(shí)現(xiàn)代碼濒生。
Android消息機(jī)制分析
提到消息機(jī)制讀者應(yīng)該都不陌生:由于子線程不能直接做UI操作丽声,Handler用于耗時子線程工作完畢觉义,發(fā)送消息給UI線程晒骇,更新UI。這的確沒錯撕氧,但是更新UI只是Handler的一個特殊的使用場景呵曹。我的理解是,它背后的本質(zhì)是一種生產(chǎn)者-消費(fèi)者模型海洼。消息機(jī)制結(jié)合java線程并發(fā)機(jī)制富腊,可以實(shí)現(xiàn)并發(fā)任務(wù)管理是整。所以本節(jié)著重理解一下Handler機(jī)制背后的實(shí)現(xiàn)原理浮入,然后據(jù)此構(gòu)建并發(fā)任務(wù)管理模型事秀。整個消息模型結(jié)構(gòu)如下:MessageQueue是倉庫易迹,Handler是生產(chǎn)者,消費(fèi)者(主要是UI線程睹欲,也可以是綁定Lopper的線程窘疮,如HandlerThread考余,這個后面會講到)通過Looper不斷的從消息隊列中取出消息并將其交給各自的目標(biāo)Handler消費(fèi)掉楚堤。
Looper源碼分析
Looper為線程維護(hù)一個消息循環(huán),先看看它的構(gòu)造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
可以看到構(gòu)造方法為私有衅胀,初始化了消息隊列酥筝,綁定當(dāng)前線程嘿歌,構(gòu)造方法在prepare()方法中被調(diào)用:
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
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));
}
根據(jù)源碼及注釋丧凤,prepare為當(dāng)前線程創(chuàng)建一個Looper;在開啟消息循環(huán)之前必須調(diào)用該方法;且Looper實(shí)例存放在TheadLocal當(dāng)中;一個線程只能綁定一份Looper,否則會拋出異常愿待。
再看看loop()方法
/** * Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the 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;
// 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
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0) { T
race.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
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();
}
}
循環(huán)體中:
1>從消息隊列中取消息msg
2>msg的target(即Handler)分發(fā)處理消息
Loop的主要作用:
1>與當(dāng)前線程綁定鸳君,保證一個線程只會有一個Looper實(shí)例相嵌,同時一個Looper實(shí)例也只有一個MessageQueue批糟。
2>loop()方法徽鼎,不斷從MessageQueue中去取消息否淤,交給消息的target屬性的dispatchMessage去處理∈眨現(xiàn)在異步消息處理線程已經(jīng)有了消息隊列(MessageQueue)嚎京,也有了在無限循環(huán)體中取出消息的哥們隐解,那么我們看看生產(chǎn)者Handler吧煞茫。
Handler源碼分析
依然是先從構(gòu)造方法看起:
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
...
}
}
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;
}
1> 通過Looper.myLooper()獲取了當(dāng)前線程保存的Looper實(shí)例
2>通過Looper實(shí)例得到消息隊列,于是Handler和Looper蚓曼、消息隊列
3>Handler的創(chuàng)建前提:所在的線程必須已經(jīng)綁定Looper(這個很重要,它是handler實(shí)現(xiàn)線程的切換的關(guān)鍵)建立關(guān)聯(lián)
然后我們在看看消息的產(chǎn)生:
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
public boolean sendMessageAtTime(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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
最終調(diào)用到enqueueMessage()方法钦扭,這里將消息的目標(biāo)設(shè)為自己辟躏,然后調(diào)用消息隊列的enqueueMessage()方法[記得前面loop()方法的msg.target.dispatchMessage(msg)吧]。
于是一個消息的流向如下:Handler生成的消息進(jìn)入到消息隊列中土全,Looper將其從消息隊列中取出,然后分配給產(chǎn)生它的Handler對象会涎,由dispatchMessage將它處理掉裹匙。下面看看最后消息是如何被消費(fèi)掉的:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
最后終于走到我們熟悉一萬遍的handleMessage方法。
大致讀完了主要源碼末秃,我們就明白Handler是如何實(shí)現(xiàn)線程切換的:
1>前提:Handler的創(chuàng)建必須依賴已經(jīng)綁定Looper線程
2>sendMessage方法只是對消息隊列做了入隊操作,可以在其他線程中調(diào)用
3>Looper的loop()方法會最終調(diào)用handlerMessage()方法概页,這個方法就運(yùn)行在handler所依附的線程。
那么細(xì)心的讀者會有這樣的疑惑:既然主線程的Looper進(jìn)入死循環(huán)了,那如何處理其他事務(wù)呢?還是看看主線程ActivityThread的初始化代碼吧:
public static void main(String[] args) {
...
//創(chuàng)建Looper和MessageQueue對象惰匙,用于處理主線程的消息
Looper.prepareMainLooper();
//創(chuàng)建ActivityThread對象
ActivityThread thread = new ActivityThread();
//建立Binder通道 (創(chuàng)建新線程)
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//消息循環(huán)運(yùn)行
Looper.loop();
...
}
我們看到main方法里在開啟循環(huán)之前绘盟,調(diào)用了thread.attach(false)锡垄,這里便會創(chuàng)建一個Binder線程(具體是指ApplicationThread,Binder的服務(wù)端程奠,用于接收系統(tǒng)服務(wù)AMS發(fā)送來的事件),該Binder線程通過Handler將Message發(fā)送給主線程距境。
圖片加載框架的任務(wù)調(diào)度模型
明白了android消息機(jī)制原理,我們可以利用它方便的構(gòu)建并發(fā)任務(wù)管理模型:開啟一個后臺線程綁定Looper進(jìn)行輪詢(這里我們采用HandlerThread)诬滩,不停從任務(wù)隊列里查詢?nèi)蝿?wù)庙曙,如果有,則取出交給線程池執(zhí)行,另外可以通過計數(shù)信號量Semaphore來控制并發(fā)數(shù)察皇。代碼如下:
package com.qicode.backloopthreaddemo;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.util.LinkedList;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;
/** * Created by chenming on 16/12/2.
* 模擬后臺下載圖片的任務(wù)并發(fā)管理代碼
*/
public class ImageLoader {
private static ImageLoader mInstance;
/**
* 線程池
*/
private ExecutorService mThreadPool;
/**
* 后臺輪詢線程及Handler,信號量
*/
private HandlerThread mBackLoopThread;//后臺線程
private BackLoopHandler mBackLoopThreadHandler;//發(fā)消息給后臺Looper嗜闻,調(diào)度下載線程
private Semaphore mBackLoopThreadSemaphore;//后臺下載任務(wù)個數(shù)限制的信號量,控制同時下載的數(shù)量
private LinkedList<Runnable> mTaskQueue;//所有任務(wù)隊列
private Type mType = Type.LIFO;//調(diào)度方式,默認(rèn)后進(jìn)先出
private class BackLoopHandler extends Handler {
BackLoopHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//同時間的下載任務(wù)個數(shù)信號量同步,執(zhí)行任務(wù)到達(dá)上限,則阻塞
try {
mBackLoopThreadSemaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
mThreadPool.execute(getTask());
}
}
public static ImageLoader getInstance(Context context) {
if (mInstance == null) {
synchronized (ImageLoader.class) {
if (mInstance == null) {
mInstance = new ImageLoader(context, 3, Type.LIFO);
}
}
}
return mInstance;
}
/**
* @param threadCount 同時下載圖片線程個數(shù)
* @param type 調(diào)度策略
*/
private ImageLoader(Context context, int threadCount, Type type) {
init(context, threadCount, type);
}
private void init(Context context, int threadCount, Type type) {
initTaskDispatchThread();
mBackLoopThreadSemaphore = new Semaphore(threadCount);//并發(fā)數(shù)量控制信號量
// 創(chuàng)建線程池
mThreadPool = Executors.newFixedThreadPool(threadCount);
mTaskQueue = new LinkedList();
mType = type;
}
/**
* 初始化后臺調(diào)度線程,結(jié)合信號量及調(diào)度策略,實(shí)現(xiàn)并發(fā)下載
*/
private void initTaskDispatchThread() {
//后臺輪詢線程初始化HandlerThread
mBackLoopThread = new HandlerThread("backthread");
mBackLoopThread.start();
mBackLoopThreadHandler = new BackLoopHandler(mBackLoopThread.getLooper());
}
/**
* 根據(jù)調(diào)度策略取任務(wù)
*
* @return
*/
private Runnable getTask() {
if (mType == Type.FIFO) {
return mTaskQueue.removeFirst();
}
return mTaskQueue.removeLast();
}
private int runnableIndex;
private class TestRunnable implements Runnable{
private int mIndex;
public TestRunnable(int index){
mIndex = index;
}
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("TAG", "LoadImage:" + mIndex + "下載完成");
mBackLoopThreadSemaphore.release();//執(zhí)行完,釋放信號量
}
}
/**
*模擬下載圖片耗時線程
*/
public void testLoadImage(){
runnableIndex ++;
Runnable runnable = new TestRunnable(runnableIndex);
addTask(runnable);
}
/**
* 新建任務(wù)甥桂,添加到后臺任務(wù)隊列
*
* @param runnable
*/
public synchronized void addTask(Runnable runnable) {
mTaskQueue.add(runnable);
mBackLoopThreadHandler.sendEmptyMessage(0x110);//給后臺調(diào)度Looper發(fā)消息,
}
/**
* 任務(wù)調(diào)度類型
*/
public enum Type {
FIFO, LIFO;
}
}
工作流程如下:
1.初始化工作:綁定mBackLoopThread和mBackLoopThreadHandler办陷,開啟消息循環(huán);
2 addTask()方法將任務(wù)加入隊列,并通過mBackLoopThreadHandler向后臺線程發(fā)送消息,后臺輪詢線程從隊列里取任務(wù);
3.并發(fā)數(shù)未滿上限(這里設(shè)的3),線程池執(zhí)行任務(wù);否則阻塞
4.單個任務(wù)執(zhí)行完成,釋放計數(shù)信號量病附。測試代碼:
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mImageLoader.testLoadImage();
}
});
測試時快速連續(xù)按按鈕6次,得出的Log如下:
12-02 11:57:07.810 17956-18071/com.qicode.backloopthreaddemo E/TAG: LoadImage:1下載完成
12-02 11:57:07.980 17956-18077/com.qicode.backloopthreaddemo E/TAG: LoadImage:2下載完成
12-02 11:57:08.130 17956-18082/com.qicode.backloopthreaddemo E/TAG: LoadImage:3下載完成
12-02 11:57:12.815 17956-18071/com.qicode.backloopthreaddemo E/TAG: LoadImage:6下載完成
12-02 11:57:12.980 17956-18077/com.qicode.backloopthreaddemo E/TAG: LoadImage:5下載完成
12-02 11:57:13.135 17956-18082/com.qicode.backloopthreaddemo E/TAG: LoadImage:4下載完成
希望通過本篇博客,讀者能布局理解Android消息機(jī)制的原理,并加以靈活應(yīng)用,這里提供了它在圖片加載框架里的應(yīng)用,下一篇博客將在此基礎(chǔ)上,討論一下整個圖片加載框架的實(shí)現(xiàn)。