Android使用LruCache、DiskLruCache实现图片缓存+图片瀑布流

来源:互联网 发布:立林jb2000 进入编程 编辑:程序博客网 时间:2024/06/10 13:51

PS:本文中的例子来源于官网地址:Caching Bitmaps,源码地址(自备梯子):Caching Bitmaps Demo,并在其基础上稍微改变了一下。

PPS:本文仅用于学习利用LruCache、DiskLruCache图片缓存策略、实现瀑布流和Matix查看大图缩放移动等功能,如果想用到项目中,建议用更成熟的框架,如glide、picasso 等。

在开始本文之前,请先了解下LruCache和DiskLruCache的用法,不了解的可以先看下这两篇:
1、Android使用磁盘缓存DiskLruCache
2、Android内存缓存LruCache源码解析

先上效果图:

GIF.gif

嗯,效果还是不错的~代码已上传Github:LruCache、DiskLruCache实现图片缓存

###图片瀑布流
这个用RecycleView来实现已经很简单了,直接上代码:

 recycler_view = (RecyclerView) findViewById(R.id.recycler_view); recycler_view.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));

首先初始化StaggeredGridLayoutManager,这里设置显示方式是竖直方向3列,然后通过recycleView的setLayoutManager设置好,接着看RecyclerView.Adapter中的处理:

public class WaterFallAdapter extends RecyclerView.Adapter<WaterFallAdapter.MyHolder> {  private int DATA_SIZE = Constant.imageUrls.length;  private List<Integer> hList;//定义一个List来存放图片的height  public WaterFallAdapter(Context mContext) {     this.mContext = mContext;     hList = new ArrayList<>();     for (int i = 0; i < DATA_SIZE; i++) {         //每次随机一个高度并添加到hList中         int height = new Random().nextInt(200) + 300;//[100,500)的随机数         hList.add(height);     }    } @Override  public void onBindViewHolder(final MyHolder holder, int position) {      //通过setLayoutParams(params)来设置图片的宽高信息      int width = DpUtil.getScreenSizeWidth(mContext) / 3;      RecyclerView.LayoutParams params = new RecyclerView.LayoutParams(width, hList.get(position));      holder.imageView.setLayoutParams(params);   }}

在WaterFallAdapter构造方法中将随机高度添加到存放图片height的hList中,并在onBindViewHolder()中取出图片的宽高并通过setLayoutParams(params)来设置图片的宽高信息,经过上面的代码,一个图片的瀑布流效果就出来了,so easy~

图片缓存

下面接着看本文的重点,实现图片的缓存策略:

先看怎么使用的:

 private static final String IMAGE_CACHE_DIR = "thumbs";//图片缓存目录 @Override protected void onCreate(@Nullable Bundle savedInstanceState) {   ImageCache.ImageCacheParams cacheParams = new   ImageCache.ImageCacheParams(this, IMAGE_CACHE_DIR);   cacheParams.setMemCacheSizePercent(0.25f);// Set memory cache to 25% of app memory   // The ImageFetcher takes care of loading images into our ImageView children asynchronously   mImageFetcher = new ImageFetcher(this, (int) DpUtil.dp2px(this, 100));   mImageFetcher.setLoadingImage(R.mipmap.img_default_bg);   mImageFetcher.addImageCache(cacheParams); }

上面初始化了缓存大小和下载时ImageView加载默认的图片等操作,然后在RecyclerView的onBindViewHolder()开始加载图片:

@Override public void onBindViewHolder(final MyHolder holder, int position) {    .............其他操作.............  mImageFetcher.loadImage(Constant.imageUrls[position], holder.imageView);   }

用起来还是挺简单的,里面主要的几个核心类:

main_class.png

接下来就依次分析一下各个类的作用:

ImageCache.java:
public class ImageCache {  // Default memory cache size in kilobytes  private static final int DEFAULT_MEM_CACHE_SIZE = 1024 * 5; // 5KB  // Default disk cache size in bytes  private static final int DEFAULT_DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB  //内存缓存核心类,用于缓存已经下载好的图片  private DiskLruCache mDiskLruCache;  //磁盘缓存核心类,用于缓存图片到外部存储中  private LruCache<String, BitmapDrawable> mMemoryCache;  //ImageCacheParams 是ImageCache 的内部类,用于设置图片缓存缓存的各个参数  private ImageCacheParams mCacheParams;  //使用Set保存数据可以确保没有重复元素,使用软引用SoftReference关联Bitmap,当内存不足时Bitmap会被回收  private Set<SoftReference<Bitmap>> mReusableBitmaps;  //实现一个单例模式  private volatile static ImageCache imageCache;  public static ImageCache getInstance(ImageCacheParams cacheParams) {      if (imageCache == null) {          synchronized (ImageCache.class) {              if (imageCache == null) {                  imageCache = new ImageCache(cacheParams);              }          }      }      return imageCache;  } private ImageCache(ImageCacheParams cacheParams) {     init(cacheParams);  } private void init(ImageCacheParams cacheParams) {     mCacheParams = cacheParams;     if (Utils.hasHoneycomb()) {         mReusableBitmaps =                 Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());     }      //初始化LruCache并覆写entryRemoved()和sizeOf()方法      mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {        @Override        protected void entryRemoved(boolean evicted, String key,                                        BitmapDrawable oldValue, BitmapDrawable newValue) {              if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {                  // The removed entry is a recycling drawable, so notify it                  // that it has been removed from the memory cache                  ((RecyclingBitmapDrawable) oldValue).setIsCached(false);            } else {                  // The removed entry is a standard BitmapDrawable                  if (Utils.hasHoneycomb()) {                      // We're running on Honeycomb or later, so add the bitmap                      // to a SoftReference set for possible use with inBitmap later                      mReusableBitmaps.add(new SoftReference<Bitmap>(oldValue.getBitmap()));                  }              }          }       @Override       protected int sizeOf(String key, BitmapDrawable value) {           final int bitmapSize = getBitmapSize(value) / 1024;           return bitmapSize == 0 ? 1 : bitmapSize;       }   };   // By default the disk cache is not initialized here as it should be initialized   // on a separate thread due to disk access.   if (cacheParams.initDiskCacheOnCreate) {      // Set up disk cache       initDiskCache();    } }//初始化DiskLruCache,因为操作外部存储比较耗时,所以这部分最好放在子线程中执行 public void initDiskCache() {    // Set up disk cache    synchronized (mDiskCacheLock) {        if (mDiskLruCache == null || mDiskLruCache.isClosed()) {            File diskCacheDir = mCacheParams.diskCacheDir;            if (mCacheParams.diskCacheEnabled && diskCacheDir != null) {                if (!diskCacheDir.exists()) {                    diskCacheDir.mkdirs();                }                if (getUsableSpace(diskCacheDir) > mCacheParams.diskCacheSize) {                    try {                        mDiskLruCache = DiskLruCache.open(                                    diskCacheDir, 1, 1, mCacheParams.diskCacheSize);                        } catch (final IOException e) {                        mCacheParams.diskCacheDir = null;                        Log.e(TAG, "initDiskCache - " + e);                    }                }            }        }        mDiskCacheStarting = false;        mDiskCacheLock.notifyAll();    } }//将图片添加到内存缓存和外部缓存中public void addBitmapToCache(String data, BitmapDrawable value) {    if (data == null || value == null) {        return;    }    // Add to memory cache    if (mMemoryCache != null) {        if (RecyclingBitmapDrawable.class.isInstance(value)) {            // The removed entry is a recycling drawable, so notify it            // that it has been added into the memory cache            ((RecyclingBitmapDrawable) value).setIsCached(true);        }        mMemoryCache.put(data, value);    }    synchronized (mDiskCacheLock) {        //Add to disk cache        if (mDiskLruCache != null) {            final String key = hashKeyForDisk(data);            OutputStream out = null;            try {                DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);                if (snapshot == null) {                    final DiskLruCache.Editor editor = mDiskLruCache.edit(key);                    if (editor != null) {                        out = editor.newOutputStream(DISK_CACHE_INDEX);                        value.getBitmap().compress(                                mCacheParams.compressFormat, mCacheParams.compressQuality, out);                        editor.commit();                        out.close();                    }                } else {                    snapshot.getInputStream(DISK_CACHE_INDEX).close();                }            } catch (Exception e) {                Log.e(TAG, "addBitmapToCache - " + e);            } finally {                try {                    if (out != null) {                        out.close();                    }                } catch (IOException e) {                }            }        }    } } //清除缓存,该方法也应该在子线程中调用 public void clearCache() {     if (mMemoryCache != null) {         mMemoryCache.evictAll();         if (BuildConfig.DEBUG) {             Log.d(TAG, "Memory cache cleared");         }     }     synchronized (mDiskCacheLock) {         mDiskCacheStarting = true;         if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {             try {                 mDiskLruCache.delete();                } catch (Exception e) {                    Log.e(TAG, "clearCache - " + e);                }                mDiskLruCache = null;                initDiskCache();            }        }    }//从内存缓存中取出图片 public BitmapDrawable getBitmapFromMemCache(String data) {     BitmapDrawable memValue = null;     if (mMemoryCache != null) {         memValue = mMemoryCache.get(data);     }     return memValue; }//从外部存储中取出图片 public Bitmap getBitmapFromDiskCache(String data) {     final String key = hashKeyForDisk(data);     Bitmap bitmap = null;     synchronized (mDiskCacheLock) {            while (mDiskCacheStarting) {             try {                 mDiskCacheLock.wait();             } catch (Exception e) {             }         }         if (mDiskLruCache != null) {             InputStream inputStream = null;             try {                 final DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);                 if (snapshot != null) {                     inputStream = snapshot.getInputStream(DISK_CACHE_INDEX);                     if (inputStream != null) {                         FileDescriptor fd = ((FileInputStream) inputStream).getFD();                         // Decode bitmap, but we don't want to sample so give                         // MAX_VALUE as the target dimensions                         bitmap = ImageResizer.decodeSampledBitmapFromDescriptor(                                    fd, Integer.MAX_VALUE, Integer.MAX_VALUE, this);                     }                 }             } catch (Exception e) {                 Log.e(TAG, "getBitmapFromDiskCache - " + e);             } finally {                 try {                     if (inputStream != null) {                         inputStream.close();                     }                 } catch (Exception e) {                 }             }         }     }     return bitmap;  }  public static class ImageCacheParams {    public int memCacheSize = DEFAULT_MEM_CACHE_SIZE;    public int diskCacheSize = DEFAULT_DISK_CACHE_SIZE;    public File diskCacheDir;    public Bitmap.CompressFormat compressFormat = DEFAULT_COMPRESS_FORMAT;    public int compressQuality = DEFAULT_COMPRESS_QUALITY;    public boolean memoryCacheEnabled = DEFAULT_MEM_CACHE_ENABLED;    public boolean diskCacheEnabled = DEFAULT_DISK_CACHE_ENABLED;    public boolean initDiskCacheOnCreate = DEFAULT_INIT_DISK_CACHE_ON_CREATE;    public ImageCacheParams(Context context, String diskCacheDirectoryName) {         //外存缓存目录        diskCacheDir = getDiskCacheDir(context, diskCacheDirectoryName);        }    //设置图片内存缓存和应用最大内存的比例    public void setMemCacheSizePercent(float percent) {        memCacheSize = Math.round(percent * Runtime.getRuntime().maxMemory() / 1024);      }  }}

ImageCache这个类主要是初始化了LruCache和DiskLruCache用来缓存图片到内存和外存中去,并从内存外存中取出图片及清除缓存等操作

ImageWorker.java:
public abstract class ImageWorker {    //初始化ImageCache和外存缓存    public void addImageCache(ImageCache.ImageCacheParams cacheParams) {        mImageCacheParams = cacheParams;        mImageCache = ImageCache.getInstance(mImageCacheParams);        new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);    }    //加载本地图片,如果没有去服务器下载该图片    public void loadImage(Object data, ImageView imageView, OnImageLoadedListener listener) {        if (data == null) return;        BitmapDrawable bitmapDrawable = null;        if (mImageCache != null) {            bitmapDrawable = mImageCache.getBitmapFromMemCache(String.valueOf(data));        }        if (bitmapDrawable != null) {            // Bitmap found in memory cache            imageView.setImageDrawable(bitmapDrawable);            if (listener != null) {                listener.onImageLoaded(true);            }        } else if (cancelPotentialWork(data, imageView)) {            final BitmapWorkerTask task = new BitmapWorkerTask(data, imageView, listener);            final AsyncDrawable asyncDrawable = new AsyncDrawable(mResources, mLoadingBitmap, task);            imageView.setImageDrawable(asyncDrawable);            // NOTE: This uses a custom version of AsyncTask that has been pulled from the            // framework and slightly modified. Refer to the docs at the top of the class            // for more info on what was changed.            task.executeOnExecutor(AsyncTask.DUAL_THREAD_EXECUTOR);        }    }}

ImageWorker处理ImageView加载Bitmap时的耗时操作,如处理内存缓存和外存缓存的使用,ImageWorker中的两个主要方法:addImageCache()loadImage() 方法。

1、addImageCache()中获得了ImageCache的单例对象,并开启CacheAsyncTask在新线程中初始化缓存:

 protected class CacheAsyncTask extends AsyncTask<Object, Void, Void> {     @Override     protected Void doInBackground(Object... params) {         int param = (int) params[0];         switch (param) {             case MESSAGE_CLEAR:                 clearCacheInternal();                 break;             case MESSAGE_INIT_DISK_CACHE:                 initDiskCacheInternal();                 break;             case MESSAGE_FLUSH:                 flushCacheInternal();                 break;             case MESSAGE_CLOSE:                 closeCacheInternal();                 break;         }         return null;     } } //清除缓存 protected void clearCacheInternal() {     if (mImageCache != null) {         mImageCache.clearCache();     } } //初始化缓存 protected void initDiskCacheInternal() {     if (mImageCache != null) {         mImageCache.initDiskCache();     } } //强制将缓存刷新到文件系统中 protected void flushCacheInternal() {     if (mImageCache != null) {         mImageCache.flush();     } } //关闭缓存 protected void closeCacheInternal() {     if (mImageCache != null) {         mImageCache.close();         mImageCache = null;     } }

2、loadImage()方法中,首先通过mImageCache.getBitmapFromMemCache尝试从内存中取出图片,如果内存中有,直接取出来显示,流程结束;如果内存中没有,就调用到了cancelPotentialWork(),来看这个方法:

  public static boolean cancelPotentialWork(Object data, ImageView imageView) {      final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);      if (bitmapWorkerTask != null) {          final Object bitmapData = bitmapWorkerTask.mData;          if (bitmapData == null || !bitmapData.equals(data)) {              bitmapWorkerTask.cancel(true);            } else {              // The same work is already in progress.              return false;          }      }      return true;  } //获得与ImageView关联的BitmapWorkerTask ,如果没有返回Null  private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {      if (imageView != null) {          final Drawable drawable = imageView.getDrawable();          if (drawable instanceof AsyncDrawable) {              final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;              return asyncDrawable.getBitmapWorkerTask();          }      }      return null;  }

首先通过getBitmapWorkerTask()获得与ImageView关联的BitmapWorkerTask(以下简称task),如果作用在ImageView上的task存在并且task.mData(图片的URL)没有改变,那么task接着执行,直到下载完图片;如果task.mData变化了,那么取消之前作用在ImageView上的task,重新去new一个Task下载新的图片。其实是这样:当我们不滑动界面的时候,task.mData(图片的URL)是不会改变的,但是当我们滑动界面的时候,比如现在RecycleView一屏显示5个ItemView,往上滑动时,第一个ItemView不可见并且此时第一个ItemView的图片还没有下载完成,第6个ItemView会复用之前第一个ItemView,如果此时继续执行第一个ItemView对应的task,那么这时候第6个ItemView显示的就是第一个ItemView应该显示的图片了,这就是我们经常遇到的显示错位问题。所以这里要判断task.mData(图片的URL)是否一致,如果不一致,需要取消之前的task,重启一个新的task,并把新的task关联给ImageView。看看task里面都干了些什么:

private class BitmapWorkerTask extends AsyncTask<Void, Void, BitmapDrawable> {    private Object mData;    private final WeakReference<ImageView> imageViewWeakReference;    private final OnImageLoadedListener mOnImageLoadedListener;    /**     * Background processing.     */    public BitmapWorkerTask(Object data, ImageView imageView) {        mData = data;        imageViewWeakReference = new WeakReference<ImageView>(imageView);        mOnImageLoadedListener = null;    }    public BitmapWorkerTask(Object data, ImageView imageView, OnImageLoadedListener listener) {        mData = data;        //弱引用关联一个ImageView        imageViewWeakReference = new WeakReference<>(imageView);        mOnImageLoadedListener = listener;    }    @Override    protected BitmapDrawable doInBackground(Void... params) {          final String dataString = String.valueOf(mData);        Bitmap bitmap = null;        BitmapDrawable drawable = null;        // Wait here if work is paused and the task is not cancelled        synchronized (mPauseWorkLock) {            while (mPauseWork && !isCancelled()) {                try {                    mPauseWorkLock.wait();                } catch (Exception e) {                }            }        }        if (mImageCache != null && !isCancelled() && getAttachedImageView() != null                    && !mExitTasksEarly) {            //从外部存储去取图片            bitmap = mImageCache.getBitmapFromDiskCache(dataString);        }        if (bitmap == null && !isCancelled() && getAttachedImageView() != null                    && !mExitTasksEarly) {              //如果外存中也没有图片,那么就去服务器上下载              bitmap = processBitmap(mData);          }       if (bitmap != null) {           if (Utils.hasHoneycomb()) {               // Running on Honeycomb or newer, so wrap in a standard BitmapDrawable               drawable = new BitmapDrawable(mResources, bitmap);           } else {               // Running on Gingerbread or older, so wrap in a RecyclingBitmapDrawable               // which will recycle automagically               drawable = new RecyclingBitmapDrawable(mResources, bitmap);           }           if (mImageCache != null) {                //把下载的图片缓存到内存和外存中去               mImageCache.addBitmapToCache(dataString, drawable);           }       }       return drawable;   }   @Override   protected void onPostExecute(BitmapDrawable value) {       boolean success = false;       // if cancel was called on this task or the "exit early" flag is set then we're done       if (isCancelled() || mExitTasksEarly) {           value = null;       }       final ImageView imageView = getAttachedImageView();       if (value != null && imageView != null) {           success = true;           //给ImageView设置图片           setImageDrawable(imageView, value);       }       if (mOnImageLoadedListener != null) {           //执行回调           mOnImageLoadedListener.onImageLoaded(success);       }   }   @Override   protected void onCancelled(BitmapDrawable value) {       super.onCancelled(value);       synchronized (mPauseWorkLock) {           mPauseWorkLock.notifyAll();       }   }   private ImageView getAttachedImageView() {       final ImageView imageView = imageViewWeakReference.get();       final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);       if (this == bitmapWorkerTask) {           return imageView;       }       return null;   } }

BitmapWorkerTask 的工作很明确,首先开启一个新线程,先确认外存中是否有相应缓存图片,如果有,直接拿出来使用并返回结果;如果没有,去服务器上下载该图片,并将该图片缓存到内存和外存中去,最后onPostExecute()中加载图片并执行结果回调。

ImageResizer.java (extends ImageWorker):
 public class ImageResizer extends ImageWorker {  protected int mImageWidth;  protected int mImageHeight;  protected ImageResizer(Context context, int imageWidth, int imageHeight) {     super(context);     setImageSize(imageWidth, imageHeight);  }  //设置图片的宽高  private void setImageSize(int imageWidth, int imageHeight) {      this.mImageWidth = imageWidth;      this.mImageHeight = imageHeight;  }  @Override  protected Bitmap processBitmap(Object data) {       //覆写父类的方法,加载本地图片      return processBitmap(Integer.parseInt(String.valueOf(data)));  }  private Bitmap processBitmap(int resId) {      return decodeSampledBitmapFromResource(mResources, resId, mImageWidth,                mImageHeight, getImageCache());  }  //从本地resource中解码bitmap并采样源bitmap至指定大小的宽高  public static Bitmap decodeSampledBitmapFromResource(Resources res,                                                   int resId, int reqWidth, int reqHeight, ImageCache cache) {     // First decode with inJustDecodeBounds=true to check dimensions     final BitmapFactory.Options options = new BitmapFactory.Options();     options.inJustDecodeBounds = true;     BitmapFactory.decodeResource(res, resId, options);     // Calculate inSampleSize     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);     // If we're running on Honeycomb or newer, try to use inBitmap     if (Utils.hasHoneycomb()) {         addInBitmapOptions(options, cache);     }     // Decode bitmap with inSampleSize set     options.inJustDecodeBounds = false;     return BitmapFactory.decodeResource(res, resId, options);    }} //从本地文件中解码bitmap并采样源bitmap至指定大小的宽高 public static Bitmap decodeSampledBitmapFromFile(String filename,                                                     int reqWidth, int reqHeight, ImageCache cache) {     // First decode with inJustDecodeBounds=true to check dimensions     final BitmapFactory.Options options = new BitmapFactory.Options();     options.inJustDecodeBounds = true;     BitmapFactory.decodeFile(filename, options);     // Calculate inSampleSize     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);     // If we're running on Honeycomb or newer, try to use inBitmap     if (Utils.hasHoneycomb()) {         addInBitmapOptions(options, cache);     }     // Decode bitmap with inSampleSize set     options.inJustDecodeBounds = false;     return BitmapFactory.decodeFile(filename, options);  } //从文件输入流中解码bitmap并采样源bitmap至指定大小的宽高 public static Bitmap decodeSampledBitmapFromDescriptor(            FileDescriptor fileDescriptor, int reqWidth, int reqHeight, ImageCache cache) {     // First decode with inJustDecodeBounds=true to check dimensions     final BitmapFactory.Options options = new BitmapFactory.Options();     options.inJustDecodeBounds = true;     BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);     // Calculate inSampleSize     options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);     // Decode bitmap with inSampleSize set     options.inJustDecodeBounds = false;     // If we're running on Honeycomb or newer, try to use inBitmap     if (Utils.hasHoneycomb()) {         addInBitmapOptions(options, cache);     }     return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); }//计算采样率大小 public static int calculateInSampleSize(BitmapFactory.Options options,                                            int reqWidth, int reqHeight) {      // Raw height and width of image      final int height = options.outHeight;      final int width = options.outWidth;      //采样率      int inSampleSize = 1;      //判断图片的宽高之一是否大于所需宽高      if (height > reqHeight || width > reqWidth) {          //图片宽高之一大于所需宽高,那么宽高都除以2          final int halfHeight = height / 2;          final int halfWidth = width / 2;          //循环宽高除以采样率的值,如果大于所需宽高,采样率inSampleSize翻倍          //PS:采样率每变大2倍,图片大小缩小至原来大小的1/4          while ((halfHeight / inSampleSize) > reqHeight                    && (halfWidth / inSampleSize) > reqWidth) {              inSampleSize *= 2;          }      //下面逻辑是为了处理一些不规则图片,如一张图片的height特别大,远大于width,此时图片的大小很大,      //此时加到内存中去依然不合适,算出总此时图片总像素大小totalPixels      long totalPixels = width * height / inSampleSize;       //如果图片总像素大于所需总像素的2倍,继续扩大采样率      final long totalReqPixelsCap = reqHeight * reqWidth * 2;      while (totalPixels > totalReqPixelsCap) {          inSampleSize *= 2;          totalPixels /= 2;         }      }     //返回最终的采样率inSampleSize     return inSampleSize;  } //如果SDK>11,通过设置options.inBitmap尝试复用ImageCache中mReusableBitmaps的缓存bitmap, //这样可以避免频繁的申请内存和销毁内存 private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {     // inBitmap only works with mutable bitmaps(可变的位图) so force the decoder to     // return mutable bitmaps.     options.inMutable = true;     if (cache != null) {         // Try and find a bitmap to use for inBitmap         Bitmap inBitmap = cache.getBitmapFromReusableSet(options);         if (inBitmap != null) {             options.inBitmap = inBitmap;         }     }  }}

ImageResizer继承自ImageWorker,ImageResizer类可以调整bitmap到指定宽高,主要用到了BitmapFactory.Options这个内部类来处理加载图片(后面介绍),当本地图片很大时,不能直接加载到内存中去,需要经过ImageResizer处理一下。下面介绍一下BitmapFactory.Options的常用方法:

BitmapFactory用来解码创建一个Bitmap,Options是BitmapFactory的一个静态内部类,是解码Bitmap时的各种参数控制:

public class BitmapFactory {  private static final int DECODE_BUFFER_SIZE = 16 * 1024;  public static class Options {      public boolean inJustDecodeBounds;      public int inSampleSize;      public Bitmap.Config inPreferredConfig = Bitmap.Config.ARGB_8888;      public int outWidth;      public int outHeight;      public String outMimeType;      public boolean inMutable;      public Bitmap inBitmap;      ............其他参数............   }}
  • inJustDecodeBounds:如果设置为true,则不会把bitmap加载到内存中,但是依然可以得到bitmap的宽高,当需要知道bitmap的宽高而又不想把bitmap加载到内存中去的时候,就可以通过设置这个属性来实现。

  • inSampleSize:采样率,如果value > 1,解码器将会按(1/inSampleSize)的比例减小宽高,然后返回一个压缩后的图片到内存中,例如: inSampleSize == 4,返回一个宽高都是原图片1/4的压缩图片,图片被压缩到原来的1/16,;如果inSampleSize的value <= 1,都会被当成1处理,即保持原样。

  • inPreferredConfig :设置色彩模式,默认值是ARGB_8888,在这个模式下,一个像素点占用4bytes空间;如果对透明度不做要求的话,可以采用RGB_565模式,这个模式下一个像素点占用2bytes。

  • outWidth:bitmap的宽度,如果inJustDecodeBounds=true,outWidth将是原图的宽度;如果inJustDecodeBounds=false,outWidth将是经过缩放后的bitmap的宽度。如果解码过程中发生错误,将返回-1。

  • outHeight:bitmap的高度,如果inJustDecodeBounds=true,outHeight将是原图的高度;如果inJustDecodeBounds=false,outHeight将是经过缩放后的bitmap的高度。如果解码过程中发生错误,将返回-1。

  • outMimeType:获取图片的类型,如果获取不到图片类型或者解码发生错误,outMimeType将会被设置成null。

  • inMutable:设置Bitmap是否可更改,如在Bitmap上进行额外操作

  • inBitmap:解析Bitmap的时候重用其他Bitmap的内存,避免大块内存的申请和销毁,使用inBitmap时inMutable必须设置为true

ImageResizer .java:
public class ImageFetcher extends ImageResizer {     ..............省略部分代码..............  private static final int HTTP_CACHE_SIZE = 10 * 1024 * 1024; // 10MB  private static final String HTTP_CACHE_DIR = "http";  private static final int IO_BUFFER_SIZE = 8 * 1024;  private DiskLruCache mHttpDiskCache;  private File mHttpCacheDir;  public ImageFetcher(Context context, int imageSize) {      super(context, imageSize);      init(context);  }  //初始化外存缓存  private void init(Context context) {      checkConnection(context);      mHttpCacheDir = ImageCache.getDiskCacheDir(context, HTTP_CACHE_DIR);  }  //检查网络状态  private void checkConnection(Context context) {      final ConnectivityManager cm =                (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);      final NetworkInfo networkInfo = cm.getActiveNetworkInfo();      if (networkInfo == null || !networkInfo.isConnectedOrConnecting()) {          Toast.makeText(context, "no_network_connection_toast", Toast.LENGTH_LONG).show();          Log.e(TAG, "checkConnection - no connection found");      }  }@Override  protected Bitmap processBitmap(Object data) {      return processBitmap(String.valueOf(data));  }  private Bitmap processBitmap(String data) {      final String key = ImageCache.hashKeyForDisk(data);      FileDescriptor fileDescriptor = null;      FileInputStream fileInputStream = null;      DiskLruCache.Snapshot snapshot;      synchronized (mHttpDiskCacheLock) {          // Wait for disk cache to initialize          while (mHttpDiskCacheStarting) {              try {                  mHttpDiskCacheLock.wait();              } catch (InterruptedException e) {              }          }         if (mHttpDiskCache != null) {             try {                 //在外存中读取图片                 snapshot = mHttpDiskCache.get(key);                 if (snapshot == null) {                      //外存中没有,则开始去服务器上下载                     DiskLruCache.Editor editor = mHttpDiskCache.edit(key);                     if (editor != null) {                         if (downloadUrlToStream(data,                             editor.newOutputStream(DISK_CACHE_INDEX))) {                              //写入外存缓存,这里保存的是原始图                             editor.commit();                          } else {                             editor.abort();                         }                     }                     //从外存中读取出该图片                     snapshot = mHttpDiskCache.get(key);                 }                 if (snapshot != null) {                     fileInputStream =                                (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);                     fileDescriptor = fileInputStream.getFD();                 }             } catch (IOException e) {                 Log.e(TAG, "processBitmap - " + e);             } catch (IllegalStateException e) {                 Log.e(TAG, "processBitmap - " + e);             } finally {                 if (fileDescriptor == null && fileInputStream != null) {                     try {                         fileInputStream.close();                     } catch (IOException e) {                     }                 }             }          }      }     Bitmap bitmap = null;     if (fileDescriptor != null) {          //将取出来的图片压缩到指定大小并在后面的逻辑中保存,这里保存的是缩略图         bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth,                    mImageHeight, getImageCache());     }     if (fileInputStream != null) {         try {             fileInputStream.close();         } catch (IOException e) {         }      }     return bitmap;  }//根据URL去下载图片 private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {     disableConnectionReuseIfNecessary();     HttpURLConnection urlConnection = null;     BufferedOutputStream out = null;     BufferedInputStream in = null;     try {         final URL url = new URL(urlString);      urlConnection = (HttpURLConnection) url.openConnection();         in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE);         out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);         int b;         while ((b = in.read()) != -1) {             out.write(b);         }         return true;     } catch (Exception e) {         Log.e(TAG, "Error in downloadBitmap - " + e);     } finally {         if (urlConnection != null) {             urlConnection.disconnect();         }         try {             if (out != null) {                 out.close();             }             if (in != null) {                 in.close();             }         } catch (final IOException e) {         }     }     return false; }}

ImageFetcher继承自ImageResizer ,ImageFetcher根据URL从服务器中得到图片并分别把原始图和缩略图缓存到外存中,这里设置的两个缓存目录:
/storage/emulated/0/Android/data/package_name/http (用来存放原始图)
/storage/emulated/0/Android/data/package_name/thumbs (用来存放缩略图)
看下面两张图:

原始图.jpg

缩略图.jpg
原始图大小基本都在100Kb以上,而缩略图基本都在10Kb以内~

最后再贴下源码地址:LruCache、DiskLruCache实现图片缓存

好了,本文暂时到这里,文章有点长了,后续会加上查看大图功能~

阅读全文
1 0
原创粉丝点击