之前實現(xiàn)了一個選擇本地圖片進(jìn)行加載顯示的選擇器,利用Glide作為圖片加載器,Glide是一個十分方便的圖片加載庫泣栈,在項目中使用Glide也十分方便秸抚。由于項目需要自己搭建一個圖片加載框架,實現(xiàn)功能比較簡單,查閱資料后開始著手實現(xiàn)。
一、 實現(xiàn)目標(biāo)
要實現(xiàn)的圖片加載器主要是加載網(wǎng)絡(luò)圖片進(jìn)行顯示变擒,加入LruCache進(jìn)行內(nèi)存緩存,在進(jìn)行列表顯示的時候加載重復(fù)圖片可以直接從緩存中取圖片進(jìn)行顯示寝志,節(jié)約了網(wǎng)絡(luò)資源娇斑,不用再重復(fù)下載策添。但是這個內(nèi)存緩存只能在程序運行時分配到內(nèi)存才能進(jìn)行緩存,在程序結(jié)束時緩存也就釋放了毫缆,下一次打開程序還是要重新下載唯竹。利用硬盤緩存DiskLruCache作為一個二級緩存目錄,將下載的圖片保存到本地緩存苦丁,由于本地緩存目錄是默認(rèn)創(chuàng)建的可以隨程序卸載而刪除浸颓,也可以顯示調(diào)用刪除緩存的方法進(jìn)行清除,因此不用擔(dān)心緩存占用很大空間的問題旺拉。
二产上、 框架實現(xiàn)
1. 線程池
在進(jìn)行圖片下載的時候要開辟一個新線程進(jìn)行耗時操作,往往在加載列表顯示的圖片時會一次性開啟很多線程蛾狗,這里使用線程池來進(jìn)行管理線程晋涣,利用一個任務(wù)Map進(jìn)行管理,如果該圖片地址存在線程正在進(jìn)行下載沉桌,就不會創(chuàng)建新的線程谢鹊,而是等待線程的下載完成。之前已經(jīng)進(jìn)行過線程池的使用留凭,在之前的基礎(chǔ)上進(jìn)行開發(fā)佃扼。
2. LruCache
上一篇日記是關(guān)于LruCache的使用的,在使用內(nèi)存緩存進(jìn)行下載任務(wù)的緩存已經(jīng)搞懂了蔼夜,具體的實現(xiàn)代碼見上一篇兼耀。
3. DiskLruCache
硬盤緩存是在LruCache的基礎(chǔ)上進(jìn)行改進(jìn)的緩存機(jī)制,相比于內(nèi)存緩存挎扰,硬盤緩存可以把緩存保存到本地翠订,利用生成的key進(jìn)行保存和獲取,這個key可以根據(jù)傳入的網(wǎng)絡(luò)地址進(jìn)行生成遵倦,得到一個哈希值,在取值的時候傳入要查找的key就可以找到緩存文件官撼,進(jìn)行解碼顯示梧躺。
首先新建一個類,在這個類的基礎(chǔ)上進(jìn)行
public class ImageDiskCache {
/**
* 得到硬盤緩存目錄
* @param context
* @param uniqueName
* @return
*/
public static File getDiskCacheDir(Context context, String uniqueName) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
cachePath = context.getExternalCacheDir().getPath();
} else {
cachePath = context.getCacheDir().getPath();
}
return new File(cachePath + File.separator + uniqueName);
}
/**
* 得到版本號
* @param context
* @return
*/
public static int getAppVersion(Context context) {
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
return info.versionCode;
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return 1;
}
/**
* 獲得緩存文件hash值
* @param key
* @return
*/
public static String hashKeyForDisk(String key) {
String cacheKey;
try {
final MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = bytesToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private static String bytesToHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(0xFF & bytes[i]);
if (hex.length() == 1) {
sb.append('0');
}
sb.append(hex);
}
return sb.toString();
}
}
創(chuàng)建一個DiskLruCache對象不能直接new傲绣,要調(diào)用open方法進(jìn)行
try {
File cacheDir = ImageDiskCache.getDiskCacheDir (context, "bitmap");
if(!cacheDir.exists ()) {
cacheDir.mkdirs ();
}
//創(chuàng)建硬盤緩存實例
mDiskLruCache = DiskLruCache.open (cacheDir, ImageDiskCache.getAppVersion (context), 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace ();
}
下載完成時把緩存文件保存到本地
String key = ImageDiskCache.hashKeyForDisk (imageUrl);
DiskLruCache.Editor editor = mDiskLruCache.edit (key);
if(editor != null) {
OutputStream outputStream = editor.newOutputStream (0);
if(downloadStream (imageUrl, outputStream)) {
editor.commit ();
} else {
editor.abort ();
}
}
mDiskLruCache.flush ();
加載圖片時首先訪問內(nèi)存緩存掠哥,如果內(nèi)存緩存有就調(diào)用,沒有繼續(xù)訪問硬盤緩存秃诵,如果硬盤緩存也沒用再開啟一個新的下載線程進(jìn)行下載顯示
public void loadImage(String imageUrl, ImageView iv) {
iv.setTag (imageUrl);
Bitmap bitmap = getBitmapFromCache (imageUrl);
if(bitmap != null) {
iv.setImageBitmap (bitmap);
Log.i (TAG, "cache image");
} else {
try {
String key = ImageDiskCache.hashKeyForDisk (imageUrl);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get (key);
if(snapShot != null) {
InputStream is = snapShot.getInputStream (0);
bitmap = BitmapFactory.decodeStream (is);
iv.setImageBitmap (bitmap);
Log.i (TAG, "disk cache image");
}
} catch (IOException e) {
e.printStackTrace ();
}
}
if(bitmap == null) {
if(!mLoadMap.containsKey (imageUrl)) {
ImageDownloaderTask task = new ImageDownloaderTask (imageUrl, iv);
mLoadMap.put (imageUrl, task);
try {
mThreadPoolExecutor.execute (task);
} catch (Exception e) {
mLoadMap.remove (imageUrl);
}
}else {
ImageDownloaderTask task = mLoadMap.get (imageUrl);
task.run ();
}
Log.i (TAG, "download image");
}
}
4. 自定義ImageView
從網(wǎng)絡(luò)上下載的圖片尺寸不一续搀,在使用一般的ImageView進(jìn)行顯示的時候不能實現(xiàn)按圖片比例進(jìn)行填充屏幕的顯示,在顯示圖片的時候?qū)挾裙潭槭謾C(jī)屏幕尺寸菠净,高度隨圖片比例進(jìn)行顯示禁舷。
public class ImageLoadView extends android.support.v7.widget.AppCompatImageView {
public ImageLoadView(Context context) {
super (context);
}
public ImageLoadView(Context context, @Nullable AttributeSet attrs) {
super (context, attrs);
}
public ImageLoadView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super (context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Drawable drawable = getDrawable ();
if(drawable != null){
int width = drawable.getMinimumWidth();
int height = drawable.getMinimumHeight();
//確定顯示比例彪杉,寬高比不變
float scale = (float)height/width;
int widthMeasure = MeasureSpec.getSize(widthMeasureSpec);
int heightMeasure = (int)(widthMeasure*scale);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightMeasure, MeasureSpec.EXACTLY);
}
super.onMeasure (widthMeasureSpec, heightMeasureSpec);
}
}
三、 完整代碼
package com.ruadong.utils.imageloader;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;
import android.widget.Toast;
import com.jakewharton.disklrucache.DiskLruCache;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author ruandong
* @create 2018/8/18
* @package com.ruadong.utils
* @Describe 圖片加載器
*/
public class ImageLoader {
private static final String TAG = "ImageLoader";
/**
* 返回Java虛擬機(jī)可用的處理器數(shù)量
*/
private static final int CPU_COUNT = Runtime.getRuntime ().availableProcessors ();
/**
* 線程池基本大小
*/
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
/**
* 線程池最大線程數(shù)
*/
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT + 1;
/**
* 空閑線程存活時間為2秒
*/
private static final int KEEP_ALIVE_TIME = 2;
/**
* 最大任務(wù)隊列數(shù)
*/
private static final int MAX_TASK_COUNT = 1000;
/**
* 內(nèi)存緩存最大值
*/
private static final int MAX_CACHE_SIZE = (int) (Runtime.getRuntime ().maxMemory () / 8);
/**
* 圖片加載器實例
*/
private static volatile ImageLoader mImageLoader;
/**
* 圖片加載線程池
*/
private ThreadPoolExecutor mThreadPoolExecutor;
/**
* 任務(wù)隊列
*/
private BlockingQueue<Runnable> mBlockingDeque;
/**
* 下載集合
*/
private Map<String, ImageDownloaderTask> mLoadMap;
/**
* 主線程handler
*/
private Handler mMainHandler;
/**
* 上下文
*/
private Context mContext;
/**
* 內(nèi)存緩存LruCache
*/
private static LruCache<String, Bitmap> mLruCache;
/**
* 硬盤緩存DiskLruCache作為圖片加載器的二級緩存,可以緩存從內(nèi)存緩存中remove的bitmap,利用open方法創(chuàng)建實例
*/
private DiskLruCache mDiskLruCache;
/**
* 圖片加載器構(gòu)造器
*/
private ImageLoader() {
}
/**
* 單例模式,獲取圖片加載器實例
*
* @return
*/
public static ImageLoader getInstance() {
if(mImageLoader == null) {
synchronized (ImageLoader.class) {
if(mImageLoader == null) {
mImageLoader = new ImageLoader ();
}
}
}
return mImageLoader;
}
/**
* 初始化ImageLoader
*
* @param context
*/
public void initImageLoader(Context context) {
this.mContext = context.getApplicationContext ();
mBlockingDeque = new LinkedBlockingDeque<> ();
mThreadPoolExecutor = new ThreadPoolExecutor (CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME, TimeUnit.SECONDS, mBlockingDeque);
mMainHandler = new Handler ();
mLruCache = new LruCache<String, Bitmap> (MAX_CACHE_SIZE) {
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getByteCount ();
}
};
try {
File cacheDir = ImageDiskCache.getDiskCacheDir (context, "bitmap");
if(!cacheDir.exists ()) {
cacheDir.mkdirs ();
}
//創(chuàng)建硬盤緩存實例
mDiskLruCache = DiskLruCache.open (cacheDir, ImageDiskCache.getAppVersion (context), 1, 10 * 1024 * 1024);
} catch (IOException e) {
e.printStackTrace ();
}
}
/**
* 獲取內(nèi)存緩存
*
* @return
*/
public LruCache<String, Bitmap> getLruCache() {
return mLruCache;
}
/**
* 清除內(nèi)存緩存
*/
public void clearLruCache() {
mLruCache.evictAll ();
}
/**
* 保存bitmap到內(nèi)存緩存
*
* @param key
* @param bitmap
*/
public void saveBitmapToCache(String key, Bitmap bitmap) {
if(getBitmapFromCache (key) == null) {
mLruCache.put (key, bitmap);
Log.i (TAG, "cache size is " + mLruCache.size ());
}
}
/**
* 查詢內(nèi)存緩存是否已經(jīng)存在當(dāng)前bitmap
*
* @param key
* @return
*/
public Bitmap getBitmapFromCache(String key) {
return mLruCache.get (key);
}
/**
* 從內(nèi)存緩存中移除該bitmap
*/
public void clearDiskLruCache() {
try {
mDiskLruCache.delete ();
Log.i (TAG, "clear disk cache");
} catch (IOException e) {
e.printStackTrace ();
}
}
/**
* 從外部開啟任務(wù)線程加載圖片
*
* @param imageUrl
* @param iv
*/
public void loadImage(String imageUrl, ImageView iv) {
iv.setTag (imageUrl);
Bitmap bitmap = getBitmapFromCache (imageUrl);
if(bitmap != null) {
iv.setImageBitmap (bitmap);
Log.i (TAG, "cache image");
} else {
try {
String key = ImageDiskCache.hashKeyForDisk (imageUrl);
DiskLruCache.Snapshot snapShot = mDiskLruCache.get (key);
if(snapShot != null) {
InputStream is = snapShot.getInputStream (0);
bitmap = BitmapFactory.decodeStream (is);
iv.setImageBitmap (bitmap);
Log.i (TAG, "disk cache image");
}
} catch (IOException e) {
e.printStackTrace ();
}
}
if(bitmap == null) {
if(!mLoadMap.containsKey (imageUrl)) {
ImageDownloaderTask task = new ImageDownloaderTask (imageUrl, iv);
mLoadMap.put (imageUrl, task);
try {
mThreadPoolExecutor.execute (task);
} catch (Exception e) {
mLoadMap.remove (imageUrl);
}
}else {
ImageDownloaderTask task = mLoadMap.get (imageUrl);
task.run ();
}
Log.i (TAG, "download image");
}
}
/**
* 圖片加載任務(wù)類
*/
private class ImageDownloaderTask implements Runnable {
private static final int CONNECT_TIMEOUT = 5 * 1000;
private static final int READ_TIMEOUT = 20 * 1000;
protected static final String ALLOWED_URI_CHARS = "@#&=*+-_.,:!?()/~'%";
private Bitmap bitmap;
private String imageUrl;
private ImageView ivLoader;
public ImageDownloaderTask(String imageUrl, ImageView ivLoader) {
this.imageUrl = imageUrl;
this.ivLoader = ivLoader;
}
@Override
public void run() {
boolean flag = true;
try {
while (flag) {
bitmap = downloadBitmap (imageUrl);
mImageLoader.saveBitmapToCache (imageUrl, bitmap);
String key = ImageDiskCache.hashKeyForDisk (imageUrl);
DiskLruCache.Editor editor = mDiskLruCache.edit (key);
if(editor != null) {
OutputStream outputStream = editor.newOutputStream (0);
if(downloadStream (imageUrl, outputStream)) {
editor.commit ();
} else {
editor.abort ();
}
}
mDiskLruCache.flush ();
Log.i (TAG, "disk cache success");
//圖片下載完成牵咙,handler通知主線程更新界面
if(mMainHandler != null) {
mMainHandler.post (new Runnable () {
@Override
public void run() {
if(ivLoader != null && imageUrl.equals (ivLoader.getTag ())) {
ivLoader.setImageBitmap (bitmap);
Toast.makeText (mContext, "download success", Toast.LENGTH_SHORT).show ();
}
}
});
}
flag = false;
}
} catch (Exception e) {
e.printStackTrace ();
}
}
/**
* 解析圖片流為bitmap
*
* @param imageUrl
* @return
*/
protected Bitmap downloadBitmap(String imageUrl) {
Bitmap bitmap;
InputStream is = getInputStreamFromURL (imageUrl);
bitmap = BitmapFactory.decodeStream (is);
return bitmap;
}
/**
* 根據(jù)傳入的圖片地址獲取網(wǎng)絡(luò)連接
*
* @param imageUrl
* @return
*/
protected HttpURLConnection getConnection(String imageUrl) {
HttpURLConnection connection = null;
String encodedUrl = Uri.encode (imageUrl, ALLOWED_URI_CHARS);
try {
if(connection != null) {
connection.disconnect ();
} else {
URL url = new URL (encodedUrl);
connection = (HttpURLConnection) url.openConnection ();
connection.setConnectTimeout (CONNECT_TIMEOUT);
connection.setReadTimeout (READ_TIMEOUT);
}
} catch (MalformedURLException e) {
e.printStackTrace ();
} catch (IOException e) {
e.printStackTrace ();
}
return connection;
}
/**
* 從網(wǎng)絡(luò)連接獲取圖片流
*
* @param imageUrl
* @return
*/
protected InputStream getInputStreamFromURL(String imageUrl) {
InputStream inputStream = null;
HttpURLConnection connection = getConnection (imageUrl);
try {
inputStream = connection.getInputStream ();
} catch (IOException e) {
e.printStackTrace ();
if(inputStream != null) {
try {
inputStream.close ();
} catch (IOException e1) {
e1.printStackTrace ();
}
}
}
return inputStream;
}
/**
* @param imageUrl
* @param outputStream
* @return
*/
protected Boolean downloadStream(String imageUrl, OutputStream outputStream) {
BufferedOutputStream out = null;
BufferedInputStream in = null;
try {
in = new BufferedInputStream (getInputStreamFromURL (imageUrl), 8 * 1024);
out = new BufferedOutputStream (outputStream, 8 * 1024);
int b;
while ((b = in.read ()) != -1) {
out.write (b);
}
return true;
} catch (final IOException e) {
e.printStackTrace ();
} finally {
if(getConnection (imageUrl) != null) {
getConnection (imageUrl).disconnect ();
}
try {
if(out != null) {
out.close ();
}
if(in != null) {
in.close ();
}
} catch (final IOException e) {
e.printStackTrace ();
}
}
return false;
}
}
}
使用也很方便
ImageLoader imageLoader = ImageLoader.getInstance ();
String imageUrl = "...圖片地址";
ImageLoadView imageView = findViewById(R.id.iv);
imageLoader.initImageLoader (context);
imageLoader.loadImage(imageUrl,imageView);