打造完美的ImageLoader——LruCache+DiskLruCache
来源:互联网 发布:音乐变调软件 编辑:程序博客网 时间:2024/04/30 12:37
做android应用少不了要和网络打交道,在我刚开始学android的时候总是处理不好网络图片的加载,尤其是图片乱跳的问题,后来发现了各种缓存图片的方法:本地缓存、软引用、LruCache....
我知道的这三种方法中,第一中和其他两种并不冲突,我们完全可以缓存到本地一份,在缓存到内存中一份。软引用这样方式,第一次使用软引用的时候,感觉做一个完美的内存缓存太容易了,可惜在android2.3以后android加强了对软引用的回收,这种方式基本上算是废了。
LruCache登场
软引用废了,难道就没有替代品了吗? 有,android sdk中google官方添加了LruCache类, 该类使用Lru算法实现内存缓存,关于LruCache的使用,可以google一下,很简单。
本地缓存方案
以前我的本地缓存方案都是自己写流实现的,这种方式完全可以,而且也不是很费劲,现在也可以使用这种方式。但是,有一个更好用的方式:DiskLruCache,广域DiskLruCache,还是google一下就ok。这里说明一下,DiskLruCache并没有在SDK中需要我们自己下载,可以使用我在github上fork的:https://github.com/qibin0506/DiskLruCache 直接将源码copy到项目中即可。
ImageLoader的思路
给定一个url,我们通过这个url,先去内存中取图片,如果内存中不存在,在去本地获取,本地存在的话,再添加到内存中,不存在就去网络中下载,下载完毕后,缓存到本地和内存中。
实现
思路很清晰,那么现在开始跟着思路实现一下代码吧。
public class ImageLoader {private static final int SUCCESS = 1;private static final int FAILED = 0;private static int sImageWidth;private static Context sContext;private static ImageLoader sInstance;private static ExecutorService sThreadPool = Executors.newCachedThreadPool();private LruCache<String, Bitmap> mLruCache;private DiskLruCache mDiskCache;/** * 初始化ImageLoader * @param context * @param width 预想的图片宽度 */public static void init(Context context, int width) {sContext = context;sImageWidth = width;}/** * 单例 获取ImageLoader的实例 * @return */public synchronized static ImageLoader getInstance() {if(null == sInstance) {sInstance = new ImageLoader();}return sInstance;}private ImageLoader() {// begin 初始化LruCacheint maxMemory = (int) Runtime.getRuntime().maxMemory();int maxSize = maxMemory / 8;mLruCache = new LruCache<String, Bitmap>(maxSize) {@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getByteCount();}};// end// begin 初始化DiskLruCachetry {mDiskCache = DiskLruCache.open(getCacheDir(), getAppVersion(), 1, 10*1024*1024);} catch (Exception e) {e.printStackTrace();}// end}public void load(final String url, final OnImageListener l) {final Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case SUCCESS:l.onResult((Bitmap) msg.obj);break;case FAILED:l.onResult(null);break;}}};// 从memory中获取Bitmap bmp = getFromLru(url);if(null != bmp) {System.out.println("--getFromLru");Message msg = handler.obtainMessage(SUCCESS, bmp);msg.sendToTarget();return;}// 如果memory中没有// 从disk中获取bmp = getFromDisk(url);if(null != bmp) {System.out.println("---getFromDisk");Message msg = handler.obtainMessage(SUCCESS, bmp);msg.sendToTarget();return;}// 如果disk中没有, 则从网络中下载sThreadPool.execute(new Runnable() {@Overridepublic void run() {System.out.println("----getFromNet");DefaultHttpClient client = null;try {client = new DefaultHttpClient();HttpGet get = new HttpGet(url);HttpResponse response = client.execute(get);if(200 == response.getStatusLine().getStatusCode()) {InputStream in = response.getEntity().getContent();Bitmap bmp = BitmapFactory.decodeStream(in);bmp = scaleImage(bmp);// 缓存到本地addToDisk(url, bmp);// 缓存到memoryaddToLru(url, bmp);Message msg = handler.obtainMessage(SUCCESS, bmp);msg.sendToTarget();}} catch (Exception e) {e.printStackTrace();handler.sendEmptyMessage(FAILED);} finally {if(null != client) {client.getConnectionManager().shutdown();}}}});}// 缩放图片private Bitmap scaleImage(Bitmap bmp) {int sample = bmp.getWidth() / sImageWidth;int height = bmp.getHeight() / sample;return ThumbnailUtils.extractThumbnail(bmp, sImageWidth, height);}// 从memory中获取private Bitmap getFromLru(String url) {return mLruCache.get(Encrypt.md5(url));}// 添加到内存中private void addToLru(String url, Bitmap bmp) {if(getFromLru(url) == null) {System.out.println("++addToLru");mLruCache.put(Encrypt.md5(url), bmp);}}// 从本地缓存获取private Bitmap getFromDisk(String url) {Bitmap bmp = null;try {DiskLruCache.Snapshot snapshot = mDiskCache.get(Encrypt.md5(url));InputStream in = snapshot.getInputStream(0);bmp = BitmapFactory.decodeStream(in);addToLru(url, bmp); // 这里可以断言 内存中肯定没有in.close();} catch (Exception e) {}return bmp;}// 添加到本地缓存private void addToDisk(String url, Bitmap bmp) throws Exception {System.out.println("+++addtoDisk");ByteArrayOutputStream baos = new ByteArrayOutputStream();bmp.compress(CompressFormat.PNG, 100, baos);byte[] buf = baos.toByteArray();DiskLruCache.Editor editor = mDiskCache.edit(Encrypt.md5(url));OutputStream out = editor.newOutputStream(0);out.write(buf);out.flush();editor.commit();}// 获取缓存目录private File getCacheDir() {File dir = null;if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {dir = new File(sContext.getExternalCacheDir().getPath() + File.separator + "images" + File.separator);}else {dir = new File(sContext.getCacheDir().getPath() + File.separator + "images" + File.separator);}return dir;}// 获取软件版本private int getAppVersion() {PackageInfo pi;try {pi = sContext.getPackageManager().getPackageInfo(sContext.getPackageName(), 0);return pi.versionCode;} catch (NameNotFoundException e) {e.printStackTrace();}return 0;}public interface OnImageListener {public void onResult(Bitmap bmp);}}
可以看到ImageLoader中有一个init方法,该方法主要是初始化ImageLoader类中的context和我们预想的图片的宽度,为什么没有高度? 高度要根据宽度比自动计算的。
27~32行,获取ImageLoader的实例, 因为ImageLoader是单例的。
构造方法中,36~43行初始化LruCache,47~51行初始化DiskLruCache, DiskLruCache的静态方法open接收4个参数,分别是:缓存的路径,软件的版本号,一个key对应多少文件,最大缓存的大小。
load方法中,71~77行,从memory中尝试获取图片,如果获取到了,则发送消息,并停止执行。81~87行,如果从memory中获取不到,则尝试从本地缓存中获取,如果获取到了,则发送消息,并停止执行。94~110行,从网络下载图片,并添加到本地缓存和memory中。
scaleImage方法,是一个简单的缩放图片功能,使用了ThumbnailUtils.extractThumbnail,如果在低版本中,需要自己去实现。
131~141行,是从memory中获取和添加到memory中。
144~169行,是从本地缓存中获取和添加到本地缓存。
剩下的两个方法是获取缓存路径和软件版本号。
ImageLoader定义好了以后,怎么使用呢? 很简单,调用load方法就行。
首先自定义一个Adapter继承自BaseAdapter,
public class ImageAdapter extends BaseAdapter {private Context mContext;private String[] mData;public ImageAdapter(Context context, String[] data) {mContext = context;mData = data;initImageLoader();}private void initImageLoader() {WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);DisplayMetrics dm = new DisplayMetrics();wm.getDefaultDisplay().getMetrics(dm);ImageLoader.init(mContext, dm.widthPixels / 3);}@Overridepublic int getCount() {return mData.length;}@Overridepublic Object getItem(int position) {return mData[position];}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {final ViewHolder holder;if(null == convertView) {convertView = View.inflate(mContext, R.layout.image_item, null);holder = new ViewHolder();holder.imageView = (ImageView) convertView.findViewById(R.id.iv);convertView.setTag(R.id.iv, holder);}else {holder = (ViewHolder) convertView.getTag(R.id.iv);}holder.imageView.setImageResource(R.drawable.ic_launcher);ImageLoader imageLoader = ImageLoader.getInstance();imageLoader.load(mData[position], new ImageLoader.OnImageListener() {@Overridepublic void onResult(Bitmap bmp) {holder.imageView.setImageBitmap(bmp);}});return convertView;}class ViewHolder {ImageView imageView;}}标准的BaseAdapter,需要注意到是第45行,主要是为了解决图片跳动的问题。
最后看一下效果吧:
图片借用了郭神的, 实在是懒得自己去找了。 效果还不错,挺顺畅的。
- 打造完美的ImageLoader——LruCache+DiskLruCache
- 打造完美的ImageLoader——LruCache+DiskLruCache
- LruCache和DiskLruCache实现二级缓存的自定义ImageLoader
- Android使用LruCache,DiskLruCache结合线程池打造类似(ImageLoader)图片加载器
- 实现自己的ImageLoader(2)-----LruCache与DiskLruCache缓存详解
- Bitmap的缓存(LruCache,DiskLruCache)
- ExecutorService+LruCache+DiskLruCache用一个类打造图片加载库
- Android照片墙完整版,完美结合LruCache和DiskLruCache
- Android照片墙完整版,完美结合LruCache和DiskLruCache
- Android照片墙完整版,完美结合LruCache和DiskLruCache
- Android照片墙完整版,完美结合LruCache和DiskLruCache
- Android照片墙完整版,完美结合LruCache和DiskLruCache
- Android照片墙完整版,完美结合LruCache和DiskLruCache
- Android照片墙完整版,完美结合LruCache和DiskLruCache
- Android照片墙完整版,完美结合LruCache和DiskLruCache
- 【android】照片墙完整版,完美结合LruCache和DiskLruCache
- Android照片墙完整版,完美结合LruCache和DiskLruCache
- Android照片墙完整版,完美结合LruCache和DiskLruCache
- maven 整体学习笔记
- 第9周项目 6.3 等差数列
- 马士兵struts2视频教程第七十六集
- 常见颜色关键字
- iOS中定时器NSTimer的使用
- 打造完美的ImageLoader——LruCache+DiskLruCache
- main函数中两个参数的用法
- 大白话系列之C#委托与事件讲解(三)
- oracle接收函数返回值的三种形式
- scp
- ffmpeg学习资料
- unity3d Optimising with Unity for iOS
- 程序员所知道的一些技巧
- 调试Release发布版程序的Crash错误