图片的三级缓存:1、内存缓存;2、本地缓存;3、网络缓存
缓存的流程图:
这里是用的LruCache来进行内存的缓存的
关键代码:
package com.example.ccc; import java.io.IOException; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import android.content.Context; import android.graphics.Bitmap; import android.os.Handler; import android.os.Message; import android.support.v4.util.LruCache; import android.widget.ImageView; public class ImageDownLoader { private static final int LOAD_SUCCESS = 1; //缓存Image的类,当存储Image的大小大于LruCache设定的值,系统自动释放内存 private LruCache<String, Bitmap> lruCache; // 文件操作工具类 private FileUtils utils; //线程池 private ThreadPoolExecutor executor; public ImageDownLoader(Context context) { super(); // 开启线程池 最小线程数 executor = new ThreadPoolExecutor(1, 4, 2, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>()); // 获取系统分配给应用程序的最大内存 int maxMemory = (int) Runtime.getRuntime().maxMemory(); int maxSize = maxMemory / 8; lruCache = new LruCache<String, Bitmap>(maxSize) { @Override protected int sizeOf(String key, Bitmap value) { // 测量Bitmap的大小 默认返回图片数量 return value.getRowBytes() * value.getHeight(); } }; utils = new FileUtils(context); } /** * * @Title: downLoader * @说 明: 加载图片,在调用此方法之前,应该为Imageview预设一个tag,防止出现图片错位的情况 * @参 数: @param url * @参 数: @param loaderlistener * @参 数: @return * @return Bitmap 返回类型 * @throws */ public Bitmap downLoader(final ImageView imageView, final ImageLoaderlistener loaderlistener) { final String url = (String) imageView.getTag(); if (url != null) { final Bitmap bitmap = showCacheBitmap(url); if (bitmap != null) { return bitmap; } else { final Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); loaderlistener.onImageLoader((Bitmap) msg.obj, imageView); } }; executor.execute(new Runnable() { @Override public void run() { Bitmap bitmap = HttpUtils.getBitmapFormUrl(url); if (bitmap != null) { Message msg = handler.obtainMessage(); msg.obj = bitmap; msg.what = LOAD_SUCCESS; handler.sendMessage(msg); try { utils.savaBitmap(url, bitmap); } catch (IOException e) { e.printStackTrace(); } lruCache.put(url, bitmap); } } }); } } return null; } /** * * @Title: showCacheBitmap * @说 明: 获取bitmap对象 : 内存中没有就去sd卡中去找 * @参 数: @param url 图片地址 * @参 数: @return * @return Bitmap 返回类型 图片 * @throws */ public Bitmap showCacheBitmap(String url) { Bitmap bitmap = getMemoryBitmap(url); if (bitmap != null) { return bitmap; } else if (utils.isFileExists(url) && utils.getFileSize(url) > 0) { bitmap = utils.getBitmap(url); lruCache.put(url, bitmap); return bitmap; } return null; } /** * * @Title: getMemoryBitmap * @说 明:获取内存中的图片 * @参 数: @param url * @参 数: @return * @return Bitmap 返回类型 * @throws */ private Bitmap getMemoryBitmap(String url) { return lruCache.get(url); } public interface ImageLoaderlistener { public void onImageLoader(Bitmap bitmap, ImageView imageView); } /** * * @Title: cancelTask * @说 明:停止所有下载线程 * @参 数: * @return void 返回类型 * @throws */ public void cancelTask() { if (executor != null) { executor.shutdownNow(); } } }
import java.io.IOException; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import android.graphics.Bitmap; import android.graphics.BitmapFactory; public class HttpUtils { /** * * @Title: getBitmapFormUrl * @说 明:从服务器获取Bitmap * @参 数: @param url * @参 数: @return * @return Bitmap 返回类型 * @throws */ public static Bitmap getBitmapFormUrl(String url) { Bitmap bitmap = null; HttpClient httpClient = new DefaultHttpClient(); // 设置超时时间 HttpConnectionParams.setConnectionTimeout(new BasicHttpParams(), 6 * 1000); HttpGet get = new HttpGet(url); try { HttpResponse response = httpClient.execute(get); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { HttpEntity entity = response.getEntity(); bitmap = BitmapFactory.decodeStream(entity.getContent()); } } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return bitmap; } }
操作文件的类:
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.os.Environment; public class FileUtils { /** * sd卡的根目录 */ private static String mSdRootPath = Environment.getExternalStorageDirectory().getPath(); /** * 手机的缓存根目录 */ private static String mDataRootPath = null; /** * 保存Image的目录名 */ private final static String FOLDER_NAME = "/tea/image"; public FileUtils(Context context) { mDataRootPath = context.getCacheDir().getPath(); } /** * 获取储存Image的目录 * * @return */ private String getStorageDirectory() { return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) ? mSdRootPath + FOLDER_NAME : mDataRootPath + FOLDER_NAME; } /** * 保存Image的方法,有sd卡存储到sd卡,没有就存储到手机目录 * * @param fileName * @param bitmap * @throws IOException */ public void savaBitmap(String url, Bitmap bitmap) throws IOException { if (bitmap == null) { return; } String path = getStorageDirectory(); File folderFile = new File(path); if (!folderFile.exists()) { folderFile.mkdirs(); } File file = new File(path + File.separator + getFileName(url)); file.createNewFile(); FileOutputStream fos = new FileOutputStream(file); bitmap.compress(CompressFormat.JPEG, 100, fos); fos.flush(); fos.close(); } /** * 从手机或者sd卡获取Bitmap * * @param fileName * @return */ public Bitmap getBitmap(String url) { return BitmapFactory.decodeFile(getStorageDirectory() + File.separator + getFileName(url)); } /** * 判断文件是否存在 * * @param fileName * @return */ public boolean isFileExists(String fileName) { return new File(getStorageDirectory() + File.separator + getFileName(fileName)).exists(); } /** * 获取文件的大小 * * @param fileName * @return */ public long getFileSize(String url) { return new File(getStorageDirectory() + File.separator + getFileName(url)).length(); } /** * 删除SD卡或者手机的缓存图片和目录 */ public void deleteFile() { File dirFile = new File(getStorageDirectory()); if (!dirFile.exists()) { return; } if (dirFile.isDirectory()) { String[] children = dirFile.list(); for (int i = 0; i < children.length; i++) { new File(dirFile, children[i]).delete(); } } dirFile.delete(); } /** * * @Title: getFileName * @说 明: 根据url截取文件名 * @参 数: @param url * @参 数: @return * @return String 返回类型 * @throws */ public String getFileName(String url) { return url.substring(url.lastIndexOf("/") + 1); } }
二.当有异步加载数据的时候
1.假设 Item1 的图片下载的比较慢,Item8 的图片下载的比较快,你滚上去
使 Item8 可见,这时 Item8 先显示它自己下载的图片没错,但等到 Item1 的图片也下载完时你发现
Item8 的图片也变成了 Item1 的图片,因为它们复用的是同一个 view。
2.如果 Item1 的图片下载的比Item8 的图片快, Item1 先刷上自己下载的图片,这时你滑下去,Item8 的图片还没下载完, Item8会先显示 Item1 的图片,因为它们是同一快内存,当 Item8 自己的图片下载完后 Item8 的图片又刷成了自己的,你再滑上去使 Item1 可见, Item1 的图片也会和 Item8 的图片是一样的,因为它们指向的是同一块内存
解决方案
给 ImageView 设置一个 tag, 并预设一个图片。
当 Item1 比 Item8 图片下载的快时, 你滚下去使 Item8 可见,这时 ImageView 的 tag 被设成了
Item8 的 URL, 当 Item1 下载完时,由于 Item1 不可见现在的 tag 是 Item8 的 URL,所以不满足条件,
虽然下载下来了但不会设置到 ImageView 上, tag 标识的永远是可见 view 中图片的 URL。