本文轉(zhuǎn)自Mr.Simple的博客,如侵刪
前言
在教你寫Android ImageLoader框架之初始配置與請(qǐng)求調(diào)度中,我們已經(jīng)講述了ImageLoader的請(qǐng)求配置與調(diào)度相關(guān)的設(shè)計(jì)與實(shí)現(xiàn)。今天我們就來深入了解圖片的具體加載過程以及加載的策略(包括按順序加載和逆序加載) ,在這其中我會(huì)分享我的一些設(shè)計(jì)決策,也歡迎大家給我提建議。
圖片的加載
Loader與LoaderManager的實(shí)現(xiàn)
在上一篇文章教你寫Android ImageLoader框架之初始配置與請(qǐng)求調(diào)度中,我們聊到了Loader與LoaderManager。 ImageLoader不斷地從隊(duì)列中獲取請(qǐng)求,然后解析到圖片uri的schema,從schema的格式就可以知道它是存儲(chǔ)在哪里的圖片。例如網(wǎng)絡(luò)圖片對(duì)象的schema是http或者h(yuǎn)ttps,sd卡存儲(chǔ)的圖片對(duì)應(yīng)的schema為file,schemae與Loader有一個(gè)對(duì)應(yīng)關(guān)系。根據(jù)schema我們從LoaderManager中獲取對(duì)應(yīng)的Loader來加載圖片。這個(gè)設(shè)計(jì)保證了SimpleImageLoader可加載圖片類型的可擴(kuò)展性,這就是為什么會(huì)增加loader這個(gè)包的原因。用戶只需要根據(jù)uri的格式來構(gòu)造圖片uri,并且實(shí)現(xiàn)自己的Loader類,然后將Loader對(duì)象注入到LoaderManager即可。RequestDispatcher中的run函數(shù)如下 :
@Override
public void run() {
try {
while (!this.isInterrupted()) {
final BitmapRequest request = mRequestQueue.take();
if (request.isCancel) {
continue;
}
final String schema = parseSchema(request.imageUri);
// 根據(jù)schema獲取loader
Loader imageLoader = LoaderManager.getInstance().getLoader(schema);
imageLoader.loadImage(request);
}
} catch (InterruptedException e) {
Log.i("", "### 請(qǐng)求分發(fā)器退出");
}
}
Loader只定義了一個(gè)接口,只用一個(gè)加載圖片的方法。
public interface Loader {
public void loadImage(BitmapRequest result);
}
抽象是為了可擴(kuò)展,定義這個(gè)接口,我們就可以注入自己的圖片加載實(shí)現(xiàn)類。例如從資源、assets中加載。不管從網(wǎng)絡(luò)還是本地加載圖片,我們加載圖片的過程有如下幾個(gè)步驟:
1.判斷緩存中是否含有該圖片;
2.如果有則將圖片直接投遞到UI線程,并且更新UI;
3.如果沒有緩存,則從對(duì)應(yīng)的地方獲取到圖片,并且將圖片緩存起來,然后再將結(jié)果投遞給UI線程,更新UI;
我們可以發(fā)現(xiàn),不管從哪里加載圖片,這些邏輯都是通用的,因此我抽象了一個(gè)AbsLoader類。它將這幾個(gè)過程抽象起來,只將變化的部分交給子類處理,就相當(dāng)于AbsLoader封裝了一個(gè)邏輯框架( 可以思考用了什么設(shè)計(jì)模式),大致代碼如下 :
/**
* @author mrsimple
*/
public abstract class AbsLoader implements Loader {
/**
* 圖片緩存
*/
private static BitmapCache mCache = SimpleImageLoader.getInstance().getConfig().bitmapCache;
@Override
public final void loadImage(BitmapRequest request) {
// 1、從緩存中獲取
Bitmap resultBitmap = mCache.get(request);
Log.e("", "### 是否有緩存 : " + resultBitmap + ", uri = " + request.imageUri);
if (resultBitmap == null) {
showLoading(request);
// 2、沒有緩存,調(diào)用onLoaderImage加載圖片
resultBitmap = onLoadImage(request);
// 3、緩存圖片
cacheBitmap(request, resultBitmap);
} else {
request.justCacheInMem = true;
}
// 4、將結(jié)果投遞到UI線程
deliveryToUIThread(request, resultBitmap);
}
/** 加載圖片的hook方法,留給子類處理
* @param request
* @return
*/
protected abstract Bitmap onLoadImage(BitmapRequest request);
// 代碼省略
}
代碼邏輯如上所述實(shí)現(xiàn)了一個(gè)模板函數(shù),變化的部分就是onLoadImage,子類在這里實(shí)現(xiàn)真正的加載圖片的方法。比如從網(wǎng)絡(luò)上加載圖片。
/**
* @author mrsimple
*/
public class UrlLoader extends AbsLoader {
@Override
public Bitmap onLoadImage(BitmapRequest request) {
final String imageUrl = request.imageUri;
FileOutputStream fos = null;
InputStream is = null;
try {
URL url = new URL(imageUrl);
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
is = new BufferedInputStream(conn.getInputStream());
is.mark(is.available());
final InputStream inputStream = is;
BitmapDecoder bitmapDecoder = new BitmapDecoder() {
@Override
public Bitmap decodeBitmapWithOption(Options options) {
Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, options);
//
if (options.inJustDecodeBounds) {
try {
inputStream.reset();
} catch (IOException e) {
e.printStackTrace();
}
} else {
// 關(guān)閉流
conn.disconnect();
}
return bitmap;
}
};
return bitmapDecoder.decodeBitmap(request.getImageViewWidth(),
request.getImageViewHeight());
} catch (Exception e) {
} finally {
IOUtil.closeQuietly(is);
IOUtil.closeQuietly(fos);
}
return null;
}
}
在初始化ImageLoader時(shí)我們會(huì)默認(rèn)將幾個(gè)Loader注入到LoaderManager中,然后在加載圖片時(shí)ImageLoader會(huì)根據(jù)圖片的schema來獲取對(duì)應(yīng)Loader來完成加載功能。
private LoaderManager() {
register(HTTP, new UrlLoader());
register(HTTPS, new UrlLoader());
register(FILE, new LocalLoader());
}
加載策略
加載策略就是你的圖片加載請(qǐng)求提交以后ImageLoader按照一個(gè)什么規(guī)則來加載你的請(qǐng)求。默認(rèn)就是SerialPolicy策略(FIFO),誰(shuí)在隊(duì)列前面就是誰(shuí)優(yōu)先被執(zhí)行。但是事情往往沒有那么簡(jiǎn)單,我們?cè)贚istView滾動(dòng)時(shí),我們希望最后添加到請(qǐng)求隊(duì)列的圖片優(yōu)先得了加載,因此此時(shí)它們就在手機(jī)屏幕上,所以我們又添加了一個(gè)ReversePolicy策略。咦,對(duì)于這種存在各種可能性的部分,我們最不能具體化,還是要抽象!于是我定義了LoadPolicy接口,它的作用是compare兩個(gè)請(qǐng)求,以此來規(guī)定排序原則。
public interface LoadPolicy {
public int compare(BitmapRequest request1, BitmapRequest request2);
}
因?yàn)槲覀兊恼?qǐng)求隊(duì)列使用的是優(yōu)先級(jí)隊(duì)列PriorityBlockingQueue,因此我們的BitmapRequest都實(shí)現(xiàn)了 Comparable 接口,我們?cè)贐itmapRequest的函數(shù)中將compareTo委托給LoadPolicy對(duì)象的compare。
@Override
public int compareTo(BitmapRequest another) {
return mLoadPolicy.compare(this, another);
}
我們看看默認(rèn)的加載策略,即按順序加載,先添加到隊(duì)列的請(qǐng)求先被執(zhí)行。
/**
* 順序加載策略
*
* @author mrsimple
*/
public class SerialPolicy implements LoadPolicy {
@Override
public int compare(BitmapRequest request1, BitmapRequest request2) {
// 那么按照添加到隊(duì)列的序列號(hào)順序來執(zhí)行
return request1.serialNum - request2.serialNum;
}
}
逆序加載則為 :
/**
* 逆序加載策略,即從最后加入隊(duì)列的請(qǐng)求進(jìn)行加載
*
* @author mrsimple
*/
public class ReversePolicy implements LoadPolicy {
@Override
public int compare(BitmapRequest request1, BitmapRequest request2) {
// 注意Bitmap請(qǐng)求要先執(zhí)行最晚加入隊(duì)列的請(qǐng)求,ImageLoader的策略
return request2.serialNum - request1.serialNum;
}
}
呵,想想這不是策略模式么!原來模式無處不在,當(dāng)你習(xí)慣之后你就會(huì)發(fā)現(xiàn)模式在無形之中已經(jīng)運(yùn)用到你的代碼了。如上所示,策略都是簡(jiǎn)單的實(shí)現(xiàn),這個(gè)策略只需要在配置ImageLoader時(shí)指定就行了,用戶也可以根據(jù)自己的需求來實(shí)現(xiàn)策略類,并且注入給ImageLoader。這樣就保證了靈活性、可擴(kuò)展性。
總結(jié)
通過Loader和LoaderManager保證了可加載圖片來源的擴(kuò)展性,即圖片可以存儲(chǔ)在網(wǎng)絡(luò)上、sd卡中、res文件夾中等等,實(shí)現(xiàn)一個(gè)從特定位置加載圖片的Loader,然后給這個(gè)Loader注冊(cè)一個(gè)schema,在加載圖片的時(shí)候根據(jù)圖片的路徑獲取schema,再通過schema獲取Loader,通過Loader加載圖片。
而圖片的加載策略又通過LoadPolicy這個(gè)抽象來定制,用戶可以自行實(shí)現(xiàn)加載策略。這樣就保證了靈活性,當(dāng)然還有后期的圖片緩存也是需要同樣的靈活性。和我在公共技術(shù)點(diǎn)之面向?qū)ο罅笤瓌t所說,面向?qū)ο蟮膸状笤瓌t最終化為幾個(gè)簡(jiǎn)單的關(guān)鍵字: : 抽象、單一職責(zé)、最小化。領(lǐng)悟到了這些思想,我想你的代碼質(zhì)量應(yīng)該會(huì)有一個(gè)質(zhì)的提升。
ImageLoader庫(kù),圖片緩存肯定必不可少。關(guān)于圖片的緩存設(shè)計(jì),還是那句老話,待我下回講解~