前言:对于图片的缓存现在都倾向于使用开源项目,如下:
1. Android-Universal-Image-Loader 图片缓存
目前使用最广泛的图片缓存,支持主流图片缓存的绝大多数特性。
项目地址:https://github.com/nostra13/Android-Universal-Image-Loader
2. picasso square开源的图片缓存
项目地址:https://github.com/square/picasso
特点:(1)可以自动检测adapter的重用并取消之前的下载
(2)图片变换
(3)可以加载本地资源
(4)可以设置占位资源
(5)支持debug模式
3. ImageCache 图片缓存,包含内存和Sdcard缓存
项目地址:https://github.com/Trinea/AndroidCommon
特点:
(1)支持预取新图片,支持等待队列
(2)包含二级缓存,可自定义文件名保存规则
(3)可选择多种缓存算法(FIFO、LIFO、LRU、MRU、LFU、MFU等13种)或自定义缓存算法
(4)可方便的保存及初始化恢复数据
(5)支持不同类型网络处理
(6)可根据系统配置初始化缓存等
4.以及FaceBook新近开源的图片下载框架Fresco
本教程适合新手,能力有限,如有错误欢迎指出;
开始撸码:
核心:LruCache和DiskLruCache以及线程池
1. LruCache进行内存缓存(Google官方提供)
2. DiskLruCache硬盘缓存(非Google官方提供,已经获得官方认证)源码地址:https://github.com/JakeWharton/DiskLruCache
本文代码是在郭神以及这位大神的基础上修改而成,感谢两位大神!
LruCache和DiskLruCache用法参考以上两位大神;
项目:
代码API和Android-Universal-Image-Loader类似
package cc.mnbase.image;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ThumbnailUtils;
import android.os.Handler;
import android.os.Message;
import android.support.v4.util.LruCache;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import cc.mnbase.image.libcore.io.DiskLruCache;
/**
* Date: 2015-10-22
* Time: 10:22
* Version 1.0
*/
public class ImageLoader {
private LruCache<String, Bitmap> mMemoryCache; //内存缓存
private DiskLruCache mDiskLruCache; //google官方推荐处理本地缓存库
private ExecutorService mDownlodThreadPool; //下载图片线程池
private int maxDiskMemory = 20*1024*1024; //本地最大缓存:20M
private FileUtils fileUtils;
private Context mContext;
private String tag = ImageLoader.class.getSimpleName();
public ImageLoader(Context context){
mContext = context;
//获取系统为每个应用分配的最大内存
int maxMemory = (int)Runtime.getRuntime().maxMemory();
int mCacheSize = maxMemory / 8;
//分配最大内存的1/8
mMemoryCache = new LruCache<String, Bitmap>(mCacheSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
//可以进行强制回收oldValue
//oldValue.recycle();
//oldValue = null;
}
};
fileUtils = new FileUtils(context);
try {
mDiskLruCache = DiskLruCache.open(new File(fileUtils.getStorageDirectory()), getAppVersion(context),
1, maxDiskMemory);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取线程池
* */
// private ExecutorService getThreadPool(){
// if(mDownlodThreadPool==null){
// synchronized (ExecutorService.class){
// if(mDownlodThreadPool==null){
// mDownlodThreadPool = Executors.newFixedThreadPool(1);
// }
// }
// }
// return mDownlodThreadPool;
// }
/**
* 获取线程池
* */
private ExecutorService getThreadPool(){
if(mDownlodThreadPool==null){
mDownlodThreadPool = Executors.newFixedThreadPool(5);
}
return mDownlodThreadPool;
}
/**
* 添加Bitmap到内存缓存中
* */
private void addBitmapToMemoryCache(String key, Bitmap value){
if(getBitmapFromMemoryCache(key) == null && value!=null){
mMemoryCache.put(key, value);
}
}
/**
* 从内存缓存中获取Bitmap
* */
private Bitmap getBitmapFromMemoryCache(String key){
return mMemoryCache.get(key);
}
/**
* 下载图片
* @param imageView 图片控件
* @param listener 回调方法
* @param url 图片地址
* */
public void displayImage(String url, ImageView imageView, onImageLoaderListener listener){
downLoadImage(url, null, imageView, null, listener, null);
}
/**
* 下载图片
* @param imageSize 设置图片尺寸
* @param imageView 图片控件
* @param listener 回调方法
* @param url 图片地址
* */
public void loadImage(String url, ImageSize imageSize, ImageView imageView, DisplayImageOptions options, onImageLoaderListener listener,
onImageLoadingProgressListener progressListener){
downLoadImage(url, imageSize, imageView, options, listener, progressListener);
}
/**
* 下载图片
* @param imageSize 设置图片尺寸
* @param imageView 图片控件
* @param listener 回调方法
* @param url 图片地址
* */
public void loadImage(String url, ImageSize imageSize, ImageView imageView, onImageLoaderListener listener,
onImageLoadingProgressListener progressListener){
loadImage(url, imageSize, imageView, null, listener, progressListener);
}
/**
* 下载图片
* @param imageSize 设置图片尺寸
* @param imageView 图片控件
* @param listener 回调方法
* @param url 图片地址
* */
public void loadImage(String url, ImageSize imageSize, ImageView imageView, onImageLoaderListener listener){
loadImage(url, imageSize, imageView, listener, null);
}
/**
* 下载图片
* @param imageSize 设置图片尺寸
* @param imageView 图片控件
* @param url 图片地址
* */
public void loadImage(String url, ImageSize imageSize, ImageView imageView){
loadImage(url, imageSize, imageView, null);
}
/**
* 下载图片
* @param imageView 图片控件
* @param url 图片地址
* */
public void loadImage(String url, ImageView imageView){
loadImage(url, null, imageView);
}
/**
* 下载图片
* */
private void downLoadImage(final String url, final ImageSize imageSize, final ImageView imageView, final DisplayImageOptions options,
final onImageLoaderListener listener, final onImageLoadingProgressListener progressListener){
if(options!=null){
imageView.setImageDrawable(options.getImageOnLoading(mContext.getResources()));
}
final String key = MD5.hashKeyForDisk(url);
Bitmap bitmap = showCacheBitmap(key);
if(bitmap!=null){
if(imageSize!=null){
bitmap = createNewBitmap(bitmap, imageSize);
}
if(imageView!=null){
imageView.setImageBitmap(bitmap);
}
if(listener!=null){
listener.onImageLoader(bitmap, url);
}
return;
} else {
final int[] fileLength = {0};
final Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 0:
if(imageView!=null){
Bitmap tmpBitmap = (Bitmap)msg.obj;
if(tmpBitmap!=null){
imageView.setImageBitmap(tmpBitmap);
if(listener!=null){
listener.onImageLoader(tmpBitmap, url);
}
} else {
if(options!=null){
imageView.setImageDrawable(options.getImageOnFail(mContext.getResources()));
}
}
}
break;
case 1:
if(progressListener!=null){
progressListener.onProgressUpdate(url, imageView, (int)msg.obj, fileLength[0]);
}
break;
case 2:
fileLength[0] = (int)msg.obj;
break;
}
}
};
getThreadPool().execute(new Runnable() {
@Override
public void run() {
Bitmap bitmap = saveBitmapToDisk(url, key, handler); //下载并保存在储存卡中
if(bitmap!=null && imageSize!=null){
bitmap = createNewBitmap(bitmap, imageSize);
}
Message msg = handler.obtainMessage(0, bitmap);
handler.sendMessage(msg);
addBitmapToMemoryCache(key, bitmap); //添加Bitmap到手机内存里
}
});
}
}
/**
* 根据ImageSize重新设置图片尺寸
* */
private Bitmap createNewBitmap(Bitmap bitmap, ImageSize imageSize){
//系统提供的图片缩放方法
Bitmap tmp = ThumbnailUtils.extractThumbnail(bitmap, imageSize.getWidth(), imageSize.getHeight());
return tmp;
}
/**
* 从内存或者SD卡中获取图片
* */
private Bitmap showCacheBitmap(String key){
if(getBitmapFromMemoryCache(key) != null){
return getBitmapFromMemoryCache(key);
} else {
Bitmap bitmap = getBitmapFromDisk(key);
addBitmapToMemoryCache(key, bitmap);
return bitmap;
}
}
/**
* 从储存卡缓存中获取Bitmap
* */
private Bitmap getBitmapFromDisk(String key){
Bitmap bitmap = null;
try{
DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
if(snapshot!=null){
InputStream is = snapshot.getInputStream(0);
bitmap = BitmapFactory.decodeStream(is);
}
} catch (Exception e){
e.printStackTrace();
}
return bitmap;
}
/**
* 获取储存卡已缓存图片的大小
* */
public int getDiskMemory(){
return mMemoryCache.size();
}
/**
* 保存Bitmap到储存卡中
* */
private Bitmap saveBitmapToDisk(String url, String key, Handler handler){
Bitmap bitmap = null;
try{
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if(editor!=null){
OutputStream os = editor.newOutputStream(0);
boolean succ = getBitmapFromUrl(url, key, handler, os);
if(succ){
editor.commit(); //一定要执行此方法否则不会保存图片
bitmap = getBitmapFromDisk(key); //从SD卡获取
} else {
editor.abort();
}
}
mDiskLruCache.flush();
} catch (Exception e){
e.printStackTrace();
}
return bitmap;
}
/**
* 从网络中获取图片
* */
private boolean getBitmapFromUrl(String url, String key, Handler handler, OutputStream fos){
boolean succ = false;
HttpURLConnection con = null;
try{
URL imgUrl = new URL(url);
con = (HttpURLConnection)imgUrl.openConnection();
con.setConnectTimeout(10 * 1000);
con.setReadTimeout(10 * 1000);
InputStream is = con.getInputStream();
int fileLength = con.getContentLength();
Message message = handler.obtainMessage(2, fileLength);
handler.sendMessage(message);
byte buf[] = new byte[1024];
int downLoadFileSize = 0;
do{
int numread = is.read(buf);
if(numread == -1){
break; //终止循环
}
fos.write(buf, 0, numread);
downLoadFileSize += numread;
Message msg = handler.obtainMessage(1, downLoadFileSize);
handler.sendMessage(msg);
} while (true);
succ = true; //下载完成
} catch (Exception e){
e.printStackTrace();
succ = false;
} finally {
if(con != null){
con.disconnect();
}
}
return succ;
}
/**
* 取消下载任务
* */
//public synchronized void cancelTask(){
// if(mDownlodThreadPool != null){
// mDownlodThreadPool.shutdownNow();
// mDownlodThreadPool = null;
// }
//}
/**
* 取消下载任务
* */
public void cancelTask(){
if(mDownlodThreadPool != null){
mDownlodThreadPool.shutdownNow();
mDownlodThreadPool = null;
}
}
/**
* 获取app当前版本号
* */
private int getAppVersion(Context context){
try {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 1);
return info.versionCode;
} catch (Exception e){
e.printStackTrace();
}
return 1;
}
/**
* 图片异步下载的回调方法
* */
public interface onImageLoaderListener{
void onImageLoader(Bitmap bitmap, String url);
}
/**
* 图片异步下载的进度回调方法
* */
public interface onImageLoadingProgressListener{
void onProgressUpdate(String imageUri, View view, int current, int total);
}
}
FileUtil类主要是获取图片缓存路径:
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Date: 2015-10-22
* Time: 11:49
* Version 1.0
*/
public class FileUtils {
public static String mSdRootPath = Environment.getExternalStorageDirectory().getPath();
private static String mDataRootPath = null;
private final static String APP_PACKAGE_FOLDER = "/Android/data";
private final static String FOLDER_NAME = "/image";
private String appPackage = null;
private String tag = FileUtils.class.getSimpleName();
public FileUtils(Context context){
appPackage = context.getPackageName();
mDataRootPath = context.getCacheDir().getPath();
}
/**
* 获取图片保存路径:优先保存在SD卡
* */
public String getStorageDirectory(){
String path = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ?
mSdRootPath + APP_PACKAGE_FOLDER + File.separator + appPackage + FOLDER_NAME : mDataRootPath + FOLDER_NAME;
File folderFile = new File(path);
if(!folderFile.exists()){ //目录不存在时,进行创建
folderFile.mkdirs();
}
return path;
}
}
ImageSize类主要为了设置图片大小:
package cc.mnbase.image;
/**
* Date: 2015-10-22
* Time: 14:57
* Version 1.0
*/
public class ImageSize {
private int width;
private int height;
public ImageSize(int width, int height){
this.width = width;
this.height = height;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
}
DisplayImageOptions类主要设置图片在下载中,失败时显示的默认图片(和 Android-Universal-Image-Loader同名类用法类似):
package cc.mnbase.image;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
/**
* Date: 2015-10-22
* Time: 21:55
* Version 1.0
*/
public final class DisplayImageOptions {
private final int imageResOnLoading;
private final int imageResForEmptyUri;
private final int imageResFail;
private final Drawable imageLoading;
private final Drawable imageForEmptyUri;
private final Drawable imageOnFail;
private DisplayImageOptions(DisplayImageOptions.Builder builder){
this.imageResOnLoading = builder.imageResOnLoading;
this.imageResForEmptyUri = builder.imageResForEmptyUri;
this.imageResFail = builder.imageResOnFail;
this.imageLoading = builder.imageLoading;
this.imageForEmptyUri = builder.imageForEmptyUri;
this.imageOnFail = builder.imageOnFail;
}
public Drawable getImageOnLoading(Resources res){
return this.imageResOnLoading != 0 ? res.getDrawable(this.imageResOnLoading):this.imageLoading;
}
public Drawable getImageForEmptyUri(Resources res){
return this.imageResForEmptyUri != 0 ? res.getDrawable(this.imageResForEmptyUri):this.imageForEmptyUri;
}
public Drawable getImageOnFail(Resources res){
return this.imageResFail != 0 ? res.getDrawable(this.imageResFail):this.imageOnFail;
}
public static class Builder{
private int imageResOnLoading = 0;
private int imageResForEmptyUri = 0;
private int imageResOnFail = 0;
private Drawable imageLoading = null;
private Drawable imageForEmptyUri = null;
private Drawable imageOnFail = null;
public Builder(){
}
/**
* 设置正在加载时显示图片
* */
public DisplayImageOptions.Builder showImageOnLoading(int imageRes){
this.imageResOnLoading = imageRes;
return this;
}
/**
*
* */
public DisplayImageOptions.Builder showImageForEmptyUri(int imageRes){
this.imageResForEmptyUri = imageRes;
return this;
}
/**
* 设置图片加载失败时显示的图片
* */
public DisplayImageOptions.Builder showImageOnFail(int imageRes){
this.imageResOnFail = imageRes;
return this;
}
/**
* 设置正在加载时显示图片
* */
public DisplayImageOptions.Builder showImageOnLoading(Drawable drawable){
this.imageLoading = drawable;
return this;
}
/**
*
* */
public DisplayImageOptions.Builder showImageForEmptyUri(Drawable drawable){
this.imageForEmptyUri = drawable;
return this;
}
/**
* 设置图片加载失败时显示的图片
* */
public DisplayImageOptions.Builder showImageOnFail(Drawable drawable){
this.imageOnFail = drawable;
return this;
}
public DisplayImageOptions build(){
return new DisplayImageOptions(this);
}
}
}
MD5类主要是处理图片名称:
package cc.mnbase.image;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* Date: 2015-10-23
* Time: 15:22
* Version 1.0
*/
public class MD5 {
public static String hashKeyForDisk(String key){
String cacheKey = null;
try {
MessageDigest mDigest = MessageDigest.getInstance("MD5");
mDigest.update(key.getBytes());
cacheKey = byteToHexString(mDigest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
cacheKey = String.valueOf(key.hashCode());
}
return cacheKey;
}
private static String byteToHexString(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();
}
}
OK,以上是主要代码,看下效果截图: