PS:本文中的例子来源于官网地址:
PS:本文中的例子来源于官网地址:Caching Bitmaps,源码地址(自备梯子):Caching Bitmaps Demo,并在其基础上稍微改变了一下。
PPS:本文仅用于学习利用LruCache、DiskLruCache图片缓存策略、实现瀑布流和Matix查看大图缩放移动等功能,如果想用到项目中,建议用更成熟的框架,如glide、picasso 等。
在开始本文之前,请先了解下LruCache和DiskLruCache的用法,不了解的可以先看下这两篇:
1、Android使用磁盘缓存DiskLruCache
2、Android内存缓存LruCache源码解析
嗯,效果还是不错的~代码已上传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);
接下来就依次分析一下各个类的作用:
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 (用来存放缩略图)
共同学习,写下你的评论
评论加载中...
作者其他优质文章