android 图片自定义三级缓存实现以及原理、图片错位解决

个人理解:

图片的三级缓存: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);
	}

}



下面来分析一下图片错误:

listview 异步加载图片之所以错位的根本原因是重用了 convertView 且有异步操作.
如果不重用 convertView 不会出现错位现象, 重用 convertView 但没有异步操作也不会有问题


一.当我们没有异步加载数据的时候分析 
      如果我们重用convertView 假如我们一屏显示7条数据,那么getView是不是调用了7次 创建了七个convertView, 当我们的item1滑出屏幕的时候,item8是不是就出现了这个时候我们item8没有去重建
      而是重用了item1的convertView,如果没有异步加载就不存在问题, 虽然item1和item8指向同一块内存的view 但滑动item8的时候,我们是不是又给item8把重新刷上了新数据 但 Item1 已滚出了屏幕你看不见。当 Item1 再次可见时这块 view 又涮上了 Item1 的数据


二.当有异步加载数据的时候
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。



android 图片自定义三级缓存实现以及原理、图片错位解决_第1张图片



你可能感兴趣的:(三级缓存的原理,自定义实现三级缓存,图片错误分析,图片三级缓存)