KJFrameForAndroid框架學習----高效設置網絡圖片
KJFrameForAndroid框架項目地址:https://github.com/kymjs/KJFrameForAndroid
或備用地址http://git.oschina.net/kymjs/KJFrameForAndroid
我們都知道,計算機讀取數據時:內存的讀取速度是最快的,然后是文件的讀取速度,最后是網絡資源的讀取。
如果每次加載同一張圖片都要從網絡獲取,那代價實在太大了。所以同一張圖片只要從網絡獲取一次就夠了,然后在本地緩存起來,之后加載同一張圖片時就 從緩存中加載就可以了。
從內存緩存讀取圖片是最快的,但是因為Android對每個應用所能使用的內存容量都有限制,所以最好再加上文件緩存。文件緩存空間也不是無限大的,容量越大讀取效率越低,這個很好理解,從沙漠中找出丟失的一根針和從盤子中找到一根針,哪個容易一想即知。因此我們常設置一個限定大小比如10M。
所以,加載圖片的流程應該是:
1、先從內存緩存中獲取,取到則返回,取不到則進行下一步;
2、從文件緩存中獲取,取到則返回并更新到內存緩存,取不到則進行下一步;
3、從網絡下載圖片,并更新到內存緩存和文件緩存。
如果您只想了解文件緩存與內存緩存公用,請查看下一篇博文。
在過去,我們經常會使用一種非常流行的內存緩存技術的實現,即軟引用或弱引用 (SoftReference or WeakReference)。但是根據Google的描述:現在已經不再推薦使用這種方式了,因為從 Android 2.3 (API Level 9)開始,垃圾回收器會更傾向于回收持有軟引用或弱引用的對象,這讓軟引用和弱引用變得不再可靠。另外,Android 3.0 (API Level 11)中,圖片的數據會存儲在本地的內存當中,因而無法用一種可預見的方式將其釋放,這就有潛在的風險造成應用程序的內存溢出并崩潰。
因此,我們更多的是去使用lru算法(Least Recently Used 近期最少使用算法)最初這種算法是用在操作系統調度上的。他的原理是通過個線性表存儲數據,并記錄數據每次調用次數,越常用到的排名就越靠前,越少用到的排名就越靠后,如果是一個新加入的數據,就會把它放在第一位,然后移除掉排名最后一位的數據。這里是KJFrameForAndroid框架中關于內存lru算法的實現方式 LruMemoryCache
既然是加載網絡圖片,那么當然需要加載的控件和網絡圖片地址作為參數,示例如下所示
private void loadImage(ImageView imageView, String imageUrl) {
// 首先訪問內存緩存,判斷圖片是否已經存在
Bitmap bitmap = mMemoryCache.get(StringUtils.md5(imageUrl));
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
}else{
//否則就去網絡下載
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
task.execute(imageUrl);
}
}
至于實際下載的方法,我就不詳細講解了,相信大家都能想到,就是一個網絡請求,然后下載圖片,再轉成bitmap,最后設置為控件圖片。然而這里有一個需要注意的重要地方,就是當我們把圖片下載成功后要記得在mMemoryCache中緩存起來。
這里是KJFraemForAndroid應用開發框架中的一段網絡圖片加載的代碼:
/**
* 加載圖片(核心方法)
*
* @param imageView
* 要顯示圖片的控件(ImageView設置src,普通View設置bg)
* @param imageUrl
* 圖片的URL
*/
private void loadImage(View imageView, String imageUrl) {
if (config.callBack != null)
config.callBack.imgLoading(imageView);
Bitmap bitmap = mMemoryCache.get(StringUtils.md5(imageUrl));
if (bitmap != null) {
if (imageView instanceof ImageView) {
((ImageView) imageView).setImageBitmap(bitmap);
} else {
imageView.setBackgroundDrawable(new BitmapDrawable(bitmap));
}
if (config.callBack != null)
config.callBack.imgLoadSuccess(imageView);
if (config.isDEBUG)
KJLoger.debugLog(getClass().getName(),
"download success, from memory cache\n" + imageUrl);
} else {
if (imageView instanceof ImageView) {
((ImageView) imageView).setImageBitmap(config.loadingBitmap);
} else {
imageView.setBackgroundDrawable(new BitmapDrawable(
config.loadingBitmap));
}
BitmapWorkerTask task = new BitmapWorkerTask(imageView);
taskCollection.add(task);
task.execute(imageUrl);
}
}
/********************* 異步獲取Bitmap并設置image的任務類 *********************/
private class BitmapWorkerTask extends AsyncTask<String, Void, Bitmap> {
private View imageView;
public BitmapWorkerTask(View imageview) {
this.imageView = imageview;
}
@Override
protected Bitmap doInBackground(String... params) {
Bitmap bitmap = null;
byte[] res = downloader.loadImage(params[0]);
if (res != null) {
bitmap = BitmapCreate.bitmapFromByteArray(res, 0, res.length,
config.width, config.height);
}
if (bitmap != null && config.openMemoryCache) {
// 圖片載入完成后緩存到LrcCache中
putBitmapToMemory(params[0], bitmap);
if (config.isDEBUG)
KJLoger.debugLog(getClass().getName(),
"put to memory cache\n" + params[0]);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (imageView instanceof ImageView) {
if (bitmap != null) {
((ImageView) imageView).setImageBitmap(bitmap);
}
} else {
imageView.setBackgroundDrawable(new BitmapDrawable(bitmap));
}
if (config.callBack != null)
config.callBack.imgLoadSuccess(imageView);
taskCollection.remove(this);
}
}
深入理解圖片加載在實際項目中的應用:
以上只是網絡圖片加載并緩存的基本操作,那么我們如果在實際項目中使用必須考慮到代碼的完備性與可擴展性。
①比如我們想指定圖片的大小,雖然我們可以通過設置view的固定寬高來強制圖片的顯示大小,但如果是一張幾兆的圖片,而我們只需要15*15分辨率大小的顯示區域,這顯然是浪費的;
②又比如,我們希望控件在網絡正在下載圖片時先顯示一個默認的圖片(比如一個灰色的頭像)又或者是圖片下載的時候顯示一個環形的進度條,那么上面的代碼是沒有辦法的;
③再比如,我們希望圖片的下載方式有多種,對于不同網站來源有不同的下載方式。。。。
這些種種特殊的需求告訴我們,上面的代碼完全沒有辦法做到。那么為了控件的完備性與可擴展性,我們就需要一個配置器、一個顯示器、一個下載器。。等等根據特殊需要而添加的插件式開發。
因此,我們可以看到在KJFrameForAndroid框架的org.kymjs.aframe.bitmap包下有著KJBitmapConfig、I_ImageLoder、I_Display等等用final修飾的類或者協議接口。
比如KJBitmapConfig類,是一個用final修飾的配置器類,通過這個配置器,我們就可以動態的對每一張下載的圖片設置寬高、以及內存大小等。而I_ImageLoder、I_Display則是兩個協議接口,分別定義了下載器和顯示器的方法,這里實際上是GoF設計模式中工廠方法模式的應用,只是這里的工廠實際上并不是用來創建對象,而是用來定義顯示方法或下載方法的,不論是哪個實現了I_ImageLoder抽象工廠的實際工廠,都必須有一個加載圖片的方法。那么在項目的實際應用中,就可以不管這個下載器的實際工廠是什么,只需要調用工廠的加載圖片的方法就行了。
/**
* 圖片載入接口協議,可自定義實現此協議的下載器
*
* @explain 采用工廠方法模式設計的下載器,本類也是一個抽象工廠類,用于生產byte[]產品
* @author kymjs(kymjs123@gmail.com)
* @version 1.0
* @created 2014-7-11
*/
public interface I_ImageLoder {
public byte[] loadImage(String imageUrl);
}
這里我們應該就可以知道上面的代碼段中有這么一段代碼原因了
byte[] res = downloader.loadImage(params[0]);
downloader實際上就是下載器的抽象工廠。
至于顯示器的邏輯和下載器是一樣的,這里我就不詳細介紹了,大家可以自己查看KJFrameForAndroid的源代碼或示例項目。
這里是I_ImageLoader下載器協議的一個實現類 Downloader.java,大家當然也可以根據自己的需要去實現自己的下載器,這完全沒有任何作為擴展試開發,這對于圖片設置代碼本身沒有任何影響。