《Android原始碼設計模式》學習筆記之ImageLoader

IT大飛說發表於2019-01-16

微信公眾號:CodingAndroid
cnblog:www.cnblogs.com/angel88/
CSDN:blog.csdn.net/xinpengfei5…

  • 需求:設計一個圖片載入工具類。
  • 要求:職責單一、可擴充套件性強、實現三級快取,遵循開閉原則。

1.改造前原始程式碼

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.LruCache;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by xpf on 2017/10/22 ?
* Function:
*/

public class ImageLoader {
// 圖片快取
LruCache<String, Bitmap> mImageCache;
// 執行緒池,執行緒池數量為CPU的數量
ExecutorService mExecutorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
public ImageLoader() {
initImageCache();
}
/**
* 初始化圖片快取大小
*/

private void initImageCache() {
// 計算可使用的最大記憶體
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 取1/4的可用記憶體作為快取
final int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
/**
* 載入顯示圖片
*
* @param url
* @param imageView
*/

public void displayImage(final String url, final ImageView imageView) {
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if (bitmap == null) return;
if (imageView.getTag().equals(url)) {
imageView.setImageBitmap(bitmap);
}
mImageCache.put(url, bitmap);
}
});
}
/**
* 下載圖片
*
* @param imageUrl
* @return
*/

private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
複製程式碼

2.遵循單一原則將原始類分為載入和快取兩個類(功能)

2.1.圖片載入類為:

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by xpf on 2017/10/22 ?
* Function:
*/

public class ImageLoader {
// 圖片快取
ImageCache mImageCache = new ImageCache();
// 執行緒池,執行緒池數量為CPU的數量
ExecutorService mExecutorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
/**
* 載入顯示圖片
*
* @param url
* @param imageView
*/

public void displayImage(final String url, final ImageView imageView) {
// 優先從快取中載入
Bitmap bitmap = mImageCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if (bitmap == null) return;
if (imageView.getTag().equals(url)) {
imageView.setImageBitmap(bitmap);
}
mImageCache.put(url, bitmap);
}
});
}
/**
* 下載圖片
*
* @param imageUrl
* @return
*/

private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
複製程式碼

2.2.快取類為

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
import android.util.LruCache;
/**
* Created by xpf on 2017/10/22 ?
* Function:圖片快取類
*/

public class ImageCache {
// 圖片LRU快取
LruCache<String, Bitmap> mImageCache;
public ImageCache() {
initImageCache();
}
/**
* 初始化圖片快取大小
*/

private void initImageCache() {
// 計算可使用的最大記憶體
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 取1/4的可用記憶體作為快取
final int cacheSize = maxMemory / 4;
mImageCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value)
{
return value.getRowBytes() * value.getHeight() / 1024;
}
};
}
public void put(String url, Bitmap bitmap) {
mImageCache.put(url, bitmap);
}
public Bitmap get(String url) {
return mImageCache.get(url);
}
}
複製程式碼

3.提高擴充套件性,增加SD卡快取

以上將程式碼的功能分開了,邏輯更清晰了,職責也單一了,但是可擴充套件性還是比較差,接下來進行增加SD卡快取。

3.1增加SD卡快取類

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Created by xpf on 2017/10/22 ?
* Function:
*/

public class DiskCache {
static String cacheDir = "/sdcard/cache/image/";
/**
* 從SD卡中讀取
*
* @param url
* @return
*/

public Bitmap get(String url) {
return BitmapFactory.decodeFile(cacheDir + url);
}
/**
* 快取到SD卡中
*
* @param url
* @param bmp
*/

public void put(String url, Bitmap bmp) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(cacheDir + url);
bmp.compress(Bitmap.CompressFormat.PNG, 100, fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
複製程式碼

3.2ImageLoader中增加一個boolean值來設定使用哪種快取方式

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by xpf on 2017/10/22 ?
* Function:
*/

public class ImageLoader {
// 記憶體快取
ImageCache mImageCache = new ImageCache();
// SD卡快取
DiskCache mDiskCache = new DiskCache();
// 是否使用SD卡快取
boolean isUseDiskCache = false;
// 執行緒池,執行緒池數量為CPU的數量
ExecutorService mExecutorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
/**
* 載入顯示圖片
*
* @param url
* @param imageView
*/

public void displayImage(final String url, final ImageView imageView) {
// 優先從快取中載入
Bitmap bitmap = isUseDiskCache ? mImageCache.get(url) : mDiskCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if (bitmap == null) return;
if (imageView.getTag().equals(url)) {
imageView.setImageBitmap(bitmap);
}
mImageCache.put(url, bitmap);
}
});
}
/**
* 下載圖片
*
* @param imageUrl
* @return
*/

private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 設定是否使用SD卡快取
*
* @param useDiskCache
*/

public void setUseDiskCache(boolean useDiskCache) {
isUseDiskCache = useDiskCache;
}
}
複製程式碼

4.進一步改造,使用雙快取,優先使用記憶體載入,如果無再使用SD卡快取

以上程式碼修改雖然增加了SD卡快取,但是為了節省使用者的流量及載入速度我們應該設計成優先使用記憶體載入,如果無再使用SD卡快取。

4.1增加雙快取類

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
/**
* Created by xpf on 2017/10/22 ?
* Function:
*/

public class DoubleCache {
ImageCache mMemoryCache = new ImageCache();
DiskCache mDiskCache = new DiskCache();
/**
* 優先使用記憶體載入,如果無再使用SD卡快取
*
* @param url
* @return
*/

public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap == null) {
bitmap = mDiskCache.get(url);
}
return bitmap;
}
/**
* 將圖片快取到記憶體和SD卡中
*
* @param url
* @param bitmap
*/

public void put(String url, Bitmap bitmap) {
mMemoryCache.put(url, bitmap);
mDiskCache.put(url, bitmap);
}
}
複製程式碼

4.2ImageLoader增加雙快取配置

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by xpf on 2017/10/22 ?
* Function:
*/

public class ImageLoader {
// 記憶體快取
ImageCache mImageCache = new ImageCache();
// SD卡快取
DiskCache mDiskCache = new DiskCache();
// 雙快取
DoubleCache mDoubleCache = new DoubleCache();
// 是否使用SD卡快取
boolean isUseDiskCache = false;
// 是否使用雙快取
boolean isUseDoubleCache = false;
// 執行緒池,執行緒池數量為CPU的數量
ExecutorService mExecutorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
/**
* 載入顯示圖片
*
* @param url
* @param imageView
*/

public void displayImage(final String url, final ImageView imageView) {
// 優先從快取中載入
Bitmap bitmap = null;
if (isUseDoubleCache) {
bitmap = mDoubleCache.get(url);
} else if (isUseDiskCache) {
bitmap = mDiskCache.get(url);
} else {
bitmap = mImageCache.get(url);
}
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if (bitmap == null) return;
if (imageView.getTag().equals(url)) {
imageView.setImageBitmap(bitmap);
}
mImageCache.put(url, bitmap);
}
});
}
/**
* 下載圖片
*
* @param imageUrl
* @return
*/

private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 設定是否使用SD卡快取
*
* @param useDiskCache
*/

public void setUseDiskCache(boolean useDiskCache) {
isUseDiskCache = useDiskCache;
}
/**
* 設定是否使用雙快取
*
* @param useDoubleCache
*/

public void setUseDoubleCache(boolean useDoubleCache) {
isUseDoubleCache = useDoubleCache;
}
}
複製程式碼

以上改造總算可以了,但是這樣每次增加快取策略都要修改原始碼,這樣很有可能引入bug,所以我們的原則是要對修改關閉,對擴充套件開放,這樣以後有新需求的時候我們就可以使用擴充套件的方法來實現。

5.抽象公共方法的介面

5.1介面抽取

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
/**
* Created by xpf on 2017/10/22 ?
* Function:
*/

public interface ImageCache {
Bitmap get(String url);
void put(String url, Bitmap bitmap);
}
複製程式碼

5.2ImageLoader注入介面的實現類

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by xpf on 2017/10/22 ?
* Function:
*/

public class ImageLoader {
ImageCache mImageCache = new MemoryCache();
// 執行緒池,執行緒池數量為CPU的數量
ExecutorService mExecutorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
/**
* 注入快取實現
*
* @param mImageCache
*/

public void setmImageCache(ImageCache mImageCache) {
this.mImageCache = mImageCache;
}
/**
* 載入顯示圖片
*
* @param url
* @param imageView
*/

public void displayImage(final String url, final ImageView imageView) {
Bitmap bitmap = mImageCache.get(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
// 圖片沒有快取提交到執行緒池中下載
submitLoadRequest(url, imageView);
}
private void submitLoadRequest(final String url, final ImageView imageView) {
imageView.setTag(url);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(url);
if (bitmap == null) return;
if (imageView.getTag().equals(url)) {
imageView.setImageBitmap(bitmap);
}
mImageCache.put(url, bitmap);
}
});
}
/**
* 下載圖片
*
* @param imageUrl
* @return
*/

private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
return bitmap;
}
}
複製程式碼

5.3記憶體快取、SD卡快取和雙快取分別實現介面

package com.anloq.sdk.imageloader;
import android.graphics.Bitmap;
/**
* Created by xpf on 2017/10/22 ?
* Function:
*/

public class DoubleCache implements ImageCache {
ImageCache mMemoryCache = new MemoryCache();
ImageCache mDiskCache = new DiskCache();
/**
* 優先使用記憶體載入,如果無再使用SD卡快取
*
* @param url
* @return
*/

@Override
public Bitmap get(String url) {
Bitmap bitmap = mMemoryCache.get(url);
if (bitmap == null) {
bitmap = mDiskCache.get(url);
}
return bitmap;
}
/**
* 將圖片快取到記憶體和SD卡中
*
* @param url
* @param bitmap
*/

@Override
public void put(String url, Bitmap bitmap) {
mMemoryCache.put(url, bitmap);
mDiskCache.put(url, bitmap);
}
}
複製程式碼

記憶體快取、SD卡快取實現同上。

6.外部呼叫及設定快取策略

private void loadImage() {
ImageLoader imageLoader = new ImageLoader();
// 使用記憶體快取
imageLoader.setmImageCache(new MemoryCache());
// 使用SD卡快取
imageLoader.setmImageCache(new DiskCache());
// 使用雙快取
imageLoader.setmImageCache(new DoubleCache());
// 使用自定義的圖片快取
imageLoader.setmImageCache(new ImageCache() {
@Override
public Bitmap get(String url) {
return null;
}
@Override
public void put(String url, Bitmap bitmap) {
}
});
String imageUrl = "http://p1.meituan.net/160.0.80/xianfu/5e369ac9d6aa54125ad1b6562282b2ca36024.jpeg";
imageLoader.displayImage(imageUrl, imageView);
}
複製程式碼

經過上述程式碼的重構,我們可以通過setImageCache(ImageCache cache)方法注入不同的快取實現,來使得ImageLoader更簡單、健壯、擴充套件性好靈活性也更高。以上三種快取圖片的具體實現完全不一樣,但是它們都有一個共同的特點是都實現了ImageCache介面。當使用者需要增加一種新的快取策略時,我們只需新建一個實現ImageCache介面等待類就可以了,這樣就實現了千變萬化的快取策略,並且新擴充套件的策略不會影響導致ImageLoader類的修改,這正是體現了“對修改關閉,對擴充套件開放的”原則,所以,我們在設計寫程式碼的時候應該認真地進行思考,希望大家一起思考,一起學習,有所成長!

原始碼連結:https://github.com/xinpengfei520/MyImageLoader

如果本文對你有幫助,歡迎大家點贊、評論,碼字不易,再小的支援也是對博主的莫大鼓勵!

今天的分享就到這裡註明,謝謝!


宣告:文中部分程式碼摘抄自《Android原始碼設計模式》一書。

注:本文由博主原創,轉載請註明出處,謝謝!

若在使用過程中遇到什麼問題,或有好提議,歡迎在下方留言、評論,或者關注我的公眾號“CodingAndroid”留言。

這裡寫圖片描述

相關文章