封装Image-Loader
一、背景
        universal-image-loader是一项伟大的开源项目,作者在其中运用到的软件工程解决办法让人印象深刻,在本篇文章的开篇,首先向universal-image-loader的作者致以敬意,详细地址:https://github.com/nostra13/Android-Universal-Image-Loader ,(源码详解可以参考:http://a.codekk.com/detail/Android/huxian99/Android%20Universal%20Image%20
Loader%20%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90),相对于一个轻量级的app而言,universal-image-loader完全能够承担相关开发工作,但越到后来,对于体量相对较大的app而言,universal-image-loader的缺点逐渐显现出来(以下内容用u代表universal-image-loader):
        1.u的下载和保存在同一子线程进行,这就造成了下载到显示的过程有时间的浪费
        2.u的线程池高达三个,虽然每个线程池的管理方式不一样,且可自定义,但是如果一次性加载大量图片,(比如照片流),会消耗大量内存,本人在实验的过程当中,一次性加载60张图片,小米4上面还是能够加载出来,但是很卡,当加载图片的数量增加到70张的时候,不仅是app挂了,手机也挂了!如果用u来加载相册,会感受得比较明显
        3.u 封装的效果有限,在日常开发工作当中,显示的图片效果可能默认的效果,也可能是经过特殊裁剪和设计的效果,比如圆角矩形、圆形、圆形+环形、高斯模糊、LOMO效果等特效等等,所以需要进行扩展

二、结果
        进行相关改造和封装之后,基本解决了上面的问题,代码详见: https://github.com/pinguo-fandong/Fan-Image-Loader, 整个项目只有一个线程池,负责加载网络数据,而对于图片数据的缓存,用了一个Thread+Queue的方式,这样一来,整个项目的CPU消耗就只有一个线程池+一个子线程,性能提高不少,相较u而言,提高了图片的加载速度,减少了资源消耗
三、详细的解决办法
1. 准备工作
        首先看了u的所有源码,通过别人的分析和自己的理解,基本明白了整个流程,为了增加程序的可扩展性,采用builder模式进行封装。

2.修改缓存过程
     2.1 在接口DiskCache.java当中增加两个方法

/*** 从memorycache当中拿到bitmap,然后保存到sd卡上面** @param cacheKey 图片对应的内存缓存的key和sd卡上面的缓存key* @return 是否保存成功* @throws IOException*/
boolean save(String cacheKey) throws IOException;/*** 通过生成的cache Key保存图片到缓存路径* @param cacheKey 缓存key(缓存的文件名称)* @param bitmap 图片* @return* @throws IOException*/
boolean saveByCacheKey(String cacheKey, Bitmap bitmap) throws IOException;

2.2 自定义本地缓存策略

/*** time: 15/11/17* description: 自定义的本地缓存机制** @author fandong*/
public class CustomDiskCache extends BaseDiskCache {private ConcurrentLinkedQueue<String> mQueue;//标识是否正在轮循private boolean mIsPoll;//标识是否销毁private boolean mIsDestroy;public CustomDiskCache(File cacheDir, File reserveCacheDir) {super(cacheDir, reserveCacheDir);this.mQueue = new ConcurrentLinkedQueue<String>();}@Overridepublic boolean save(String cacheKey) throws IOException {if (!mQueue.contains(cacheKey)) {mQueue.add(cacheKey);}if (!mIsPoll) {mIsPoll = true;Thread thread = new Thread(getCacheTask());thread.start();}return true;}public Runnable getCacheTask() {return new Runnable() {@Overridepublic void run() {try {String cacheKey = mQueue.poll();do {//0.如果销毁就跳出线程if (mIsDestroy) {break;}boolean savedSuccessfully = false;//1.从内存当中拿出缓存Bitmap bitmap = FanImageLoader.getMemoryCache(cacheKey);if (bitmap == null || bitmap.isRecycled()) {continue;}//2.保存到sd卡上面File imageFile = getFileByCacheKey(cacheKey);FileOutputStream os = new FileOutputStream(imageFile);try {savedSuccessfully = bitmap.compress(compressFormat, compressQuality, os);} finally {IoUtils.closeSilently(os);if (!savedSuccessfully) {if (imageFile.exists()) {imageFile.delete();}}}} while ((cacheKey = mQueue.poll()) != null);} catch (Exception e) {e.printStackTrace();} finally {mIsPoll = false;}}};}@Overridepublic void close() {this.mIsDestroy = true;if (mQueue != null) {mQueue.clear();}}
}

这就是将图片缓存到本地的核心部分了,由于从网络上面下载好图片之后,经过相应的图片处理(裁剪、加特效)等,会将bitmapkey-value的形式缓存在内存当中,当需要缓存到sd卡上面的时候,只需要从内存缓存当中拿到bitmap就可以了,那么怎样能够拿到内存当中的缓存bitmap呢?在FanImageLoader当中设计了这样的方法:

public static Bitmap getMemoryCache(String memoryKey) {return mImageLoader.getMemoryCache().get(memoryKey);
}

2.3 本地缓存时机
     在u当中,加载图片是首先会从内存当中读取数据,如果没有缓存,会到sd卡上面去读取缓存文件,如果没有缓存文件,会到网络或者其他源获取数据,获取成功之后会首先放在内存当中,然后同步放入sd卡,然后显示在界面上面,整个加载本地缓存和缓存网络图片到sd卡上面,都在LoadAndDisplayImageTask.java里面,在tryLoadBitmap()方法当中,做如下修改:

private Bitmap tryLoadBitmap() throws TaskCancelledException {Bitmap bitmap = null;try {//2.从sd卡上面得到带宽高的缓存文件File imageFile = configuration.diskCache.getFileByCacheKey(memoryCacheKey);//3.如果没有带宽高的缓存文件,那么if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {loadedFrom = LoadedFrom.DISC_CACHE;checkTaskNotActual();bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));}if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {loadedFrom = LoadedFrom.NETWORK;if (options.isCacheOnDisk()) {bitmap = tryCacheImageOnDisk();}if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {fireFailEvent(FailType.DECODING_ERROR, null);}}} catch (IllegalStateException e) {fireFailEvent(FailType.NETWORK_DENIED, null);} catch (TaskCancelledException e) {throw e;} catch (OutOfMemoryError e) {L.e(e);fireFailEvent(FailType.OUT_OF_MEMORY, e);} catch (Throwable e) {L.e(e);fireFailEvent(FailType.UNKNOWN, e);}return bitmap;
}

从上面的过程可以看到,到sd卡上面读取缓存,如果没有就会去加载远程的图片资源,这个过程在tryCacheImageOnDisk()方法当中完成,下载的具体过程由 downloadImage()方法完成,做如下修改:

private Bitmap downloadImage() throws IOException {InputStream is = getDownloader().getStream(uri, options.getExtraForDownloader());if (is == null) {L.e(ERROR_NO_IMAGE_STREAM, memoryCacheKey);return null;} else {try {Bitmap bitmap = null;//String url = uri;int width = targetSize.getWidth();int height = targetSize.getHeight();if (width > 0 || height > 0) {bitmap = BitmapUtils.createScaledBitmap(is, width, height);}if (bitmap == null) {BitmapFactory.Options options = new BitmapFactory.Options();options.inPreferredConfig = Bitmap.Config.RGB_565;options.inSampleSize = 1;bitmap = BitmapFactory.decodeStream(is, null, options);}if (bitmap != null) {//1.存放在内存当中if (options.isCacheInMemory()) {configuration.memoryCache.put(memoryCacheKey, bitmap);}//2.存放到sd卡上面if (uri.startsWith("content") || uri.startsWith("http")) {configuration.diskCache.save(memoryCacheKey);}}return bitmap;} finally {IoUtils.closeSilently(is);}}
}

需要说明的是,在封装的过程当中,存放到memory里面的key和存放到sd卡上面的key采用统一的生成方式,从上面的过程不难看出,存放数据到sd卡的过程是一个异步的过程,下载得到bitmap之后,会将memoryCacheKey传递给 CustomDiskCache,这样,CustomDiskCache就可以根据memoryCacheKey取出bitmap,然后进行存放。
     从上面的方法中不难看出,得到网络bitmap之后,程序对bitmap进行了裁剪,就是这句代码:

int width = targetSize.getWidth();
int height = targetSize.getHeight();
if (width > 0 || height > 0) {bitmap = BitmapUtils.createScaledBitmap(is, width, height);
}

targetSize就是我们需要显示图片的ImageView或者ImageSwitcher,它的确定可以通过FanImageLoader.create("http://a.jpg").setShowSize(100,100)来确定,也可以通过给ImageView或者ImageSwitcher设置宽高,或者MaxWidth/MaxHeight实现。
     2.4 缓存key的生成
     缓存key默认是url+尺寸信息生成的md5码形成的,这样一来,同一个url会根据不同的size进行存储,大大加快了图片的加载速度,也是软件工程当中“以空间换时间”的概念,具体的实现方式如下:

public class NameGeneratorUtil {private static FileNameGenerator mFileNameGenerator;static {mFileNameGenerator = new Md5FileNameGenerator();}/*** 生成缓存的key,包括内存缓存和sd卡缓存** @param imageURI 原始的imageUrl* @param width    视图的宽度* @param height   视图的高度* @return*/public synchronized static String generateCacheKey(String imageURI, int width, int height) {imageURI = encodeURL(imageURI, width, height);return mFileNameGenerator.generate(imageURI);}/*** 生成缓存的key,包括内存缓存和sd卡缓存** @param imageURI  原始的imageUrl* @param imageSize 视图的尺寸* @return*/public synchronized static String generateCacheKey(String imageURI, ImageSize imageSize) {imageURI = encodeURL(imageURI, imageSize.getWidth(), imageSize.getHeight());return mFileNameGenerator.generate(imageURI);}/*** 生成缓存的key,包括内存缓存和sd卡缓存** @param imageURI 原始的imageUrl* @return*/public synchronized static String generateCacheKey(String imageURI) {return mFileNameGenerator.generate(imageURI);}/*** 根据宽高信息将原来的url转变成?width=1080&height=1920** @param url    原来的url* @param width  缓存的宽度* @param height 缓存的高度* @return 添加宽高信息的url*/public static String encodeURL(String url, int width, int height) {if (TextUtils.isEmpty(url)) {return "";}if (width <= 0 && height <= 0) {return url;}StringBuilder builder = new StringBuilder(url);if (!builder.toString().contains("?")) {builder.append("?");}url = builder.toString();if (!url.endsWith("&") && !url.endsWith("?")) {builder.append("&");}builder.append("width=").append(width).append("&").append("height=").append(height);return builder.toString();}
}

2.5 得到sd卡缓存
     在FanImageloader当中提供了三个得到本地缓存数据的方法,分别是:

/*** 得到缓存数据** @param url* @return*/
public static String getDiskCachePath(String url) {return getDiskCachePath(url, 0, 0);
}/*** 得到url对应的硬盘缓存数据** @param url    原始的url* @param width  指定宽度* @param height 指定高度* @return*/
public static String getDiskCachePath(String url, int width, int height) {String cacheKey;if (width <= 0 || height <= 0) {cacheKey = NameGeneratorUtil.generateCacheKey(url, getMaxImageSize());} else {cacheKey = NameGeneratorUtil.generateCacheKey(url, width, height);}DiskCache diskCache = mImageLoader.getDiskCache();File imageFile = diskCache.getFileByCacheKey(cacheKey);if (imageFile != null) {return imageFile.getAbsolutePath();}return null;
}/*** 得到url对应的硬盘缓存数据(url没有加七牛的信息)** @param url  原始的url* @param view 原始的url显示的控件,这个控件是用来计算宽高用的* @return*/
public static String getDiskCachePath(String url, View view) {ImageAware aware;if (view instanceof ImageView) {aware = new ImageViewAware((ImageView) view);} else if (view instanceof ImageSwitcher) {aware = new ImageSwitcherAware(view);} else {aware = new SimpleViewAware(view);}return getDiskCachePath(url, aware.getWidth(), aware.getHeight());
}
private static ImageSize getMaxImageSize() {DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();return new ImageSize(displayMetrics.widthPixels, displayMetrics.heightPixels);
}

四、封装图片处理效果
1.实现背景淡出,前景淡入的效果
        在很多场景下,如果我们通过背景淡出,前景淡入的方式显示图片,整个过程显得很柔和,很优雅,那么如何实现呢,如果是ImageView,进行动画显示的是整个控件,能够实现淡入,但不能实现淡出的效果,所以这里我们采用ImageSwitcher的方式进行了实现,首先我们知道,在u当中,并没有直接将一个下载的bitmap设置给控件显示,而是通过封装一层aware来进行显示,这里我们自定义封装ImageSwitcherAware.如下所示:

public class ImageSwitcherAware extends ViewAware {public ImageSwitcherAware(View view) {super(view);}public ImageSwitcherAware(View view, boolean checkActualViewSize) {super(view, checkActualViewSize);}protected void setImageDrawableInto(Drawable drawable, View view) {((ImageSwitcher) view).setImageDrawable(drawable);}protected void setImageBitmapInto(Bitmap bitmap, View view) {((ImageSwitcher) view).setImageDrawable(new BitmapDrawable(view.getResources(), bitmap));}@Overridepublic int getHeight() {View view = viewRef.get();if (view != null) {final ViewGroup.LayoutParams params = view.getLayoutParams();int height = 0;if (view instanceof FanImageView) {FanImageView iv = (FanImageView) view;height = iv.getShowHeight();}if (height <= 0 && checkActualViewSize && params != null && params.height != ViewGroup.LayoutParams.WRAP_CONTENT) {height = view.getHeight(); // Get actual image height}if (height <= 0 && params != null)height = params.height; // Get layout height parameterreturn height;}return 0;}@Overridepublic int getWidth() {View view = viewRef.get();if (view != null) {final ViewGroup.LayoutParams params = view.getLayoutParams();int width = 0;if (view instanceof FanImageView) {FanImageView iv = (FanImageView) view;width = iv.getShowWidth();}if (width <= 0 && checkActualViewSize && params != null && params.width != ViewGroup.LayoutParams.WRAP_CONTENT) {width = view.getWidth(); // Get actual image width}if (width <= 0 && params != null) width = params.width; // Get layout width parameterreturn width;}return 0;}
}

那么,我们要用ImageSwitcherAware实现淡入淡出的效果,需要封装ImageSwitcher的动画执行方法,于是这里进行了对ImageSwitcher的封装:
FanImageView:

package com.fans.loader.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.LinearInterpolator;
import android.widget.ImageSwitcher;
import android.widget.ImageView;
import android.widget.ViewSwitcher.ViewFactory;/*** time: 15/11/11* description:封装了淡入淡出的ImageSwitcher** @author fandong*/
public class FanImageView extends ImageSwitcher implements ViewFactory {private int showWidth;private int showHeight;public FanImageView(Context context) {super(context);initView();}public FanImageView(Context context, AttributeSet attrs) {super(context, attrs);initView();}private void initView() {AlphaAnimation in = new AlphaAnimation(0.0f, 1.0f);in.setInterpolator(new LinearInterpolator());in.setDuration(800);AlphaAnimation out = new AlphaAnimation(1.0f, 0.0f);in.setInterpolator(new LinearInterpolator());out.setDuration(800);setInAnimation(in);setOutAnimation(out);setFactory(this);}@SuppressWarnings("deprecation")@Overridepublic View makeView() {ImageView view = new ImageView(getContext());view.setBackgroundColor(0x00000000);view.setScaleType(ImageView.ScaleType.CENTER_CROP);view.setLayoutParams(new LayoutParams(android.widget.Gallery.LayoutParams.MATCH_PARENT,android.widget.Gallery.LayoutParams.MATCH_PARENT));return view;}public int getShowWidth() {return showWidth;}public void setShowWidth(int showWidth) {this.showWidth = showWidth;}public int getShowHeight() {return showHeight;}public void setShowHeight(int showHeight) {this.showHeight = showHeight;}
}

我们要使用,那么就需要在display方法当中进行判断,当前需要显示的控件,是ImageView还是ImageSwitcher,在FanImageLoader.java当中,有这样的方法:

private synchronized static void display(String url, View view, DisplayImageOptions displayImageOptions,ImageLoadingListener imageLoadingListener, ImageLoadingProgressListener imageLoadingProgressListener) {try {if (view instanceof ImageView) {mImageLoader.displayImage(url, new ImageViewAware((ImageView) view), displayImageOptions,imageLoadingListener, imageLoadingProgressListener);} else if (view instanceof ImageSwitcher) {mImageLoader.displayImage(url, new ImageSwitcherAware(view), displayImageOptions, imageLoadingListener,imageLoadingProgressListener);} else {mImageLoader.displayImage(url, new SimpleViewAware(view), displayImageOptions, imageLoadingListener,imageLoadingProgressListener);}} catch (OutOfMemoryError e1) {e1.printStackTrace();} catch (Exception e2) {e2.printStackTrace();}}

2.实现圆角矩形的显示
        在源码包com.fans.loader.core.display包下面自定了各种displayer,其中的RoundedBitmapDisplayer就是实现圆角矩形显示的控制器,核心方法:

public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {imageAware.setImageDrawable(new RoundedDrawable(bitmap, this.cornerRadius, this.margin));
}

这里可以看出,我们将处理之后的bitmap转换成了一个圆角矩形的drawable,那么这个RoundedDrawable是如何生成的呢?

public static class RoundedDrawable extends Drawable {protected final float cornerRadius;protected final int margin;protected final RectF mRect = new RectF();protected final Rect mBitmapRect;protected final BitmapShader bitmapShader;protected final Paint paint;public RoundedDrawable(Bitmap bitmap, int cornerRadius, int margin) {this.cornerRadius = (float) cornerRadius;this.margin = margin;this.bitmapShader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);this.mBitmapRect = new Rect(margin, margin, bitmap.getWidth() - margin, bitmap.getHeight() - margin);this.paint = new Paint();this.paint.setAntiAlias(true);this.paint.setShader(this.bitmapShader);}protected void onBoundsChange(Rect bounds) {super.onBoundsChange(bounds);this.mRect.set((float) this.margin, (float) this.margin, (float) (bounds.width() - this.margin), (float) (bounds.height() - this.margin));Matrix shaderMatrix = new Matrix();float dx = 0.0F;float dy = 0.0F;int dwidth = this.mBitmapRect.width();int dheight = this.mBitmapRect.height();int vwidth = bounds.width() - this.margin;int vheight = bounds.height() - this.margin;float scale;if (dwidth * vheight > vwidth * dheight) {scale = (float) vheight / (float) dheight;dx = ((float) vwidth - (float) dwidth * scale) * 0.5F;} else {scale = (float) vwidth / (float) dwidth;dy = ((float) vheight - (float) dheight * scale) * 0.5F;}shaderMatrix.setScale(scale, scale);shaderMatrix.postTranslate((float) ((int) (dx + 0.5F)), (float) ((int) (dy + 0.5F)));this.bitmapShader.setLocalMatrix(shaderMatrix);}public void draw(Canvas canvas) {canvas.drawRoundRect(this.mRect, this.cornerRadius, this.cornerRadius, this.paint);}public int getOpacity() {return -3;}public void setAlpha(int alpha) {this.paint.setAlpha(alpha);}public void setColorFilter(ColorFilter cf) {this.paint.setColorFilter(cf);}
}

从上面可以看出,我们是通过shader的方式实现了圆角drawable的生成。
3.高斯模糊的实现
        这里的高斯模糊采用了github上面的开源库,StackBlur,当然也可以通过renderscript来实现,

/*** time: 15/11/11* description:显示高斯模糊的图片** @author fandong*/
public class BlurBitmapDisplayer implements BitmapDisplayer {private final int depth;public BlurBitmapDisplayer(int depth) {this.depth = depth;}public void display(Bitmap bitmap, ImageAware imageAware, LoadedFrom loadedFrom) {GaussianBlur blurProcess = new GaussianBlur();Bitmap blurBitmap = blurProcess.blur(bitmap, (float) this.depth);if (blurBitmap != null && !blurBitmap.isRecycled()) {imageAware.setImageBitmap(blurBitmap);}}
}

GaussianBlur.java处于com.fans.loader.core.util包下面
4.如何使用
        上面我们定义了各种displayer现在是时候运用在我们的FanImageLoader上面了,观察FanImageLoader内部类Builder,提供的方法build()里面,会根据传递进来的效果类型,生成对应的displayer,并传入到DisplayImageOptions.Builder里面去,关键代码如下:

DisplayImageOptions.Builder builder = new DisplayImageOptions.Builder().showImageOnFail(this.mFailDrawable).showImageForEmptyUri(this.mEmptyDrawable).showImageOnLoading(this.mDefaultDrawable).showImageOnFail(this.mFailRes).showImageForEmptyUri(this.mEmptyRes).showImageOnLoading(this.mDefaultRes).imageScaleType(this.mImageScaleType).cacheInMemory(true).cacheOnDisk(true).decodingOptions(this.decodingOptions).considerExifParams(true);
DisplayImageOptions displayImageOptions = null;
switch (this.mDisplayType) {case DISPLAY_DEFAULT:// 简单default:displayImageOptions = builder.displayer(new SimpleBitmapDisplayer()).build();break;case DISPLAY_FADE_IN:// 淡入displayImageOptions = builder.displayer(new FadeInBitmapDisplayer(this.mFadeInTime)).build();break;case DISPLAY_ROUND:// 圆角矩形displayImageOptions = builder.displayer(new RoundedBitmapDisplayer(this.mRoundRadius)).build();break;case DISPLAY_ROUND_FADE_IN:// 圆角矩形淡入displayImageOptions = builder.displayer(new RoundedFadeInBitmapDisplayer(this.mRoundRadius, this.mFadeInTime)).build();break;case DISPLAY_ROUND_VIGNETTE:// 圆角阴影(LOMO)displayImageOptions = builder.displayer(new RoundedLomoBitmapDisplayer(this.mRoundRadius)).build();break;case DISPLAY_ROUND_VIGNETTE_FADE_IN:// 圆角阴影淡入displayImageOptions = builder.displayer(new RoundedLomoFadeInBitmapDisplayer(this.mRoundRadius, this.mFadeInTime)).build();break;case DISPLAY_CIRCLE:// 圆形displayImageOptions = builder.displayer(new CircleBitmapDisplayer()).build();break;case DISPLAY_CIRCLE_FADE_IN:// 圆形淡入displayImageOptions = builder.displayer(new CircleFadeInBitmapDisplayer(this.mFadeInTime)).build();break;case DISPLAY_CIRCLE_RING:// 圆形带环displayImageOptions = builder.displayer(new CircleRingBitmapDisplayer().setStrokeWidth(mStrokeWidth).setColor(mRingColor).setRingPadding(mRingPadding)).build();break;case DISPLAY_BLUR:// 高斯模糊displayImageOptions = builder.displayer(new BlurBitmapDisplayer(this.mBlurDepth)).build();break;case DISPLAY_BLUR_FADE_IN:// 高斯模糊淡入displayImageOptions = builder.displayer(new BlurFadeInBitmapDisplayer(this.mBlurDepth, this.mFadeInTime)).build();break;case DISPLAY_ROUND_BLUR:// 圆角高斯模糊displayImageOptions = builder.displayer(new RoundedBlurBitmapDisplayer(this.mRoundRadius, this.mBlurDepth)).build();break;case DISPLAY_ROUND_BLUR_VIGNETTE:// 圆角高斯模糊的LOMOdisplayImageOptions = builder.displayer(new RoundedLomoBlurBitmapDisplayer(this.mRoundRadius, this.mBlurDepth)).build();break;case DISPLAY_CIRCLE_BLUR:// 圆形高斯模糊displayImageOptions = builder.displayer(new CircleBlurBitmapDisplayer(this.mBlurDepth)).build();
}

五、滑动优化
        当我们使用ListView或者RecyclerView进行图片显示的时候,通常会让ListView/RecyclerView在滑动的过程当中停止图片加载,universal-image-loader的处理方式有bug,采用的同步锁根本不能锁住,所以第一步,我们在加载图片的LoadAndDisplayImageTask.java的run方法当中加上同步锁:

@Override
public void run() {//1.处理在滑动的时候不加载图片,只有在idle状态之下才会加载图片AtomicBoolean pause = engine.getPause();if (pause.get()) {synchronized (engine.pauseLock) {try {engine.pauseLock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}Bitmap bmp;try {checkTaskNotActual();
……
}

第二步、自定义OnScrollListener,用于ListView的OnScrollListener:

/*** time: 15/6/11* description:当控件(ListView)在滑动过程当中时暂停图片的加载,停止后恢复加载** @author fandong*/
public class AbsListPauseOnScrollListener implements OnScrollListener {private final boolean pauseOnScroll;private final boolean pauseOnFling;private final OnScrollListener externalListener;public AbsListPauseOnScrollListener(boolean pauseOnScroll, boolean pauseOnFling) {this(pauseOnScroll, pauseOnFling, null);}public AbsListPauseOnScrollListener(boolean pauseOnScroll, boolean pauseOnFling, OnScrollListener customListener) {this.pauseOnScroll = pauseOnScroll;this.pauseOnFling = pauseOnFling;this.externalListener = customListener;}public void onScrollStateChanged(AbsListView view, int scrollState) {switch (scrollState) {case SCROLL_STATE_IDLE:FanImageLoader.resume();break;case SCROLL_STATE_TOUCH_SCROLL:if (this.pauseOnScroll) {FanImageLoader.pause();}break;case SCROLL_STATE_FLING:if (this.pauseOnFling) {FanImageLoader.pause();}}if (this.externalListener != null) {this.externalListener.onScrollStateChanged(view, scrollState);}}public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {if (this.externalListener != null) {this.externalListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);}}
}

使用方式也很简单:

mListView.setOnScrollListener(new AbsListPauseOnScrollListener(true, true, mOnScrollListener));

第三步、自定义recyclerView对应的OnScrollListener

/*** time: 15/11/11* description: RecyclerView滑动时候是否加载图片** @author fandong*/
public class RecyclerPauseOnScrollListener extends RecyclerView.OnScrollListener {private final boolean pauseOnScroll;private final boolean pauseOnFling;public RecyclerPauseOnScrollListener(boolean pauseOnScroll, boolean pauseOnFling) {this.pauseOnScroll = pauseOnScroll;this.pauseOnFling = pauseOnFling;}@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {switch (newState) {case RecyclerView.SCROLL_STATE_IDLE:FanImageLoader.resume();break;case RecyclerView.SCROLL_STATE_DRAGGING:if (this.pauseOnScroll) {FanImageLoader.pause();}break;case RecyclerView.SCROLL_STATE_SETTLING:if (this.pauseOnFling) {FanImageLoader.pause();}}}}

六.好了,以上就是改写的大部分了,当然了,还有其他一些改写内容,相信大家在看源码的过程当中就会领会清楚,比如load()方法等,整个Fan-Image-Loader的使用方法如下所示:
第一步、在Application或者SplashActivity当中初始化

FanImageLoader.init(context.getApplicationContext(), FileUtil.getPathByType(FileUtil.DIR_TYPE_CACHE));
L.writeDebugLogs(DebugUtil.isDebug());

第二步、在需要显示图片的地方调用(详见下面的示例)
第三步、(可选)在程序退出的时候,调用FanImageLoader.destroy();

    //示例0 、背景淡出,图片淡入FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setShowSize(100,100).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setDisplayType(FanImageLoader.DISPLAY_DEFAULT).into(mIs);/* 示例一、普通加载图片*/FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setShowSize(100,100).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setDisplayType(FanImageLoader.DISPLAY_DEFAULT).into(mIv1);FanImageLoader.create("assets://xiada01.jpg").setImageScaleType(ImageScaleType.EXACTLY).setShowSize(100,100).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setDisplayType(FanImageLoader.DISPLAY_DEFAULT).into(mIv2);/* 示例二、渐变显示*/FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setShowSize(100,100).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setDisplayType(FanImageLoader.DISPLAY_FADE_IN).setFadeInTime(1000).into(mIv2);/* 示例三、圆角矩形显示*/FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setShowSize(100,100).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setRoundRadius(30).setDisplayType(FanImageLoader.DISPLAY_ROUND).into(mIv3);/* 示例四、圆角矩形淡入显示*/FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setShowSize(100,100).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setRoundRadius(30).setDisplayType(FanImageLoader.DISPLAY_ROUND_FADE_IN).setFadeInTime(1000).into(mIv4);/* 示例五、圆角矩形LOMO显示*/FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setShowSize(100,100).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setRoundRadius(30).setDisplayType(FanImageLoader.DISPLAY_ROUND_VIGNETTE).into(mIv5);/* 示例六、圆角矩形LOMO淡入显示*/FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setShowSize(100,100).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setRoundRadius(30).setDisplayType(FanImageLoader.DISPLAY_ROUND_VIGNETTE_FADE_IN).setFadeInTime(1000).into(mIv6);/* 示例七、圆形显示*/FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setShowSize(100,100).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setDisplayType(FanImageLoader.DISPLAY_CIRCLE).into(mIv7);/* 示例八、圆形淡入显示*/FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setShowSize(100,100).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setDisplayType(FanImageLoader.DISPLAY_CIRCLE_FADE_IN).setFadeInTime(1000).into(mIv8);/* 示例九、带环的圆形图片*/FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setStrokeWidth(5.f).setRingColor(0xff00ff00).setRingPadding(3.f).setDisplayType(FanImageLoader.DISPLAY_CIRCLE_RING).into(mIv9);/* 示例十、模糊图片显示*/FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setBlurDepth(20).setDisplayType(FanImageLoader.DISPLAY_BLUR).into(mIv10);/* 示例十一、模糊图片显示*/FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setBlurDepth(20).setFadeInTime(1000).setDisplayType(FanImageLoader.DISPLAY_BLUR_FADE_IN).into(mIv11);/* 示例十二、模糊图片显示*/FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setBlurDepth(20).setRoundRadius(20).setDisplayType(FanImageLoader.DISPLAY_ROUND_BLUR).into(mIv12);/* 示例十三、模糊图片显示*/FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setBlurDepth(20).setRoundRadius(20).setDisplayType(FanImageLoader.DISPLAY_ROUND_BLUR_VIGNETTE).into(mIv13);/* 示例十四、模糊图片显示*/FanImageLoader.create(url).setImageScaleType(ImageScaleType.EXACTLY).setDefaultRes(R.drawable.ic_launcher).setFailRes(R.drawable.ic_launcher).setEmptyRes(R.drawable.ic_launcher).setBlurDepth(20).setDisplayType(FanImageLoader.DISPLAY_CIRCLE_BLUR).into(mIv14);

android学习之路(六)---- 图片加载库的优化、封装相关推荐

  1. Android之Google推荐的图片加载库Glide介绍

    原文链接:Google推荐的图片加载库Glide介绍 作者 : nuuneoi 译者 : jianghejie 校对者 :

  2. Android图片加载库的封装实战

    重磅更新 2017-02-16 2017-05-09 优化圆形图片加载 更新demo 前言 主流图片加载库的对比 Android-Universal-Image-Loader Picasso Glid ...

  3. 常用的图片加载库的区别(Gilde,Picasso,Image-loader,Fresco)

    Gilde,Picasso,Image-loader,Fresco图片加载库的使区别 Glide Glide是一个非常成熟的图片加载库,他可以从多个源加载图片,如:网路,本地,Uri等,更重要的是他内 ...

  4. android开发学习之路——连连看之加载图片(三)

    正如前面AbstractBoard类的代码中看到的,当程序需要创建N个Piece对象时,程序会直接调用ImageUtil的getPlayImages()方法去获取图片,该方法将会随机从res\ dra ...

  5. Android踩坑日记:使用Fesco图片加载库在GridView上的卡顿优化

    1,fresco是一个强大的图片加载库 2,fresco设计了一个叫做image pipeline(图片管道)的模块,它负责从从网络,从本地文件系统,从本地资源加载图片,为了最大限度节约资源和cpu时 ...

  6. android 图片加载库 Glide 的使用介绍

    一:简介 在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫 Glide 的图片加载库,作者是bumptech.这个库被广泛的运用在google的开源项目中,包括2014年google I/O大会 ...

  7. android图片加载库Glide

    什么是Glide? Glide是一个加载图片的库,作者是bumptech,它是在泰国举行的google 开发者论坛上google为我们介绍的,这个库被广泛的运用在google的开源项目中. Glide ...

  8. Android图片加载库—Picasso一个强大的图像下载和缓存库

    介绍 GitHub源码 点击查看 Picasso是一款强大的图片下载和缓存开源软件,只能在Android平台上使用,由Square开发.使用Picasso可以添加一些必须的特性和视觉效果到Androi ...

  9. Android开源框架——图片加载库Glide

    Glide是有google开发的图片加载库,支持图片加载与处理,包括动态图片的加载,以及视频的解码. 开源地址:https://github.com/bumptech/glide build.grad ...

最新文章

  1. 微信小程序 在使用wx.request时显示加载中
  2. TCP第四次挥手为什么要等待2MSL(最长报文段寿命,Maximum Segment Lifetime)
  3. Android 手把手教您自定义ViewGroup
  4. 5.1matlab数据统计分析(最大值、最小值、平均值、中值、和、积、累加和、累加积、标准差、相关系数、排序)
  5. EOS 源代码解读 (2)插件-插件模板
  6. BZOJ 5330 Luogu P4607 [SDOI2018]反回文串 (莫比乌斯反演、Pollard Rho算法)
  7. 脉冲宽度测量程序 c51 c语言,基于C51单片机和LCD1602显示的超声波测距仪C语言程序...
  8. 收藏|2021年阿里云开源镜像站最热门镜像王全梳理(附下载链接和Top20镜像王排名)
  9. 圆环内外圆毛刺(凸起)缺口(凹陷)检测halcon
  10. 浏览器加载渲染HTML、DOM、CSS、 JAVASCRIPT、IMAGE、FLASH、IFRAME、SRC属性等资源的顺序总结...
  11. HCIE-Security Day20:GRE协议:实验(一)配置基于静态路由的GRE隧道
  12. PTA 程序设计-一帮一(C语言)
  13. 我的世界里 有你还不知道的秘密 边走边学习 且行且珍惜吧
  14. 妹子图APP(四)—— SQLite保存数据实现离线图片查看
  15. 2021浙江大学软件学院软件工程方向推免复试
  16. Docker镜像下载加速及设置代理
  17. 第七节 可执行程序的装载——20135203齐岳
  18. C# GPIO通道调试(DMCI驱动)
  19. [机器学习] --- 红楼梦后四十回到底是谁写的?机器学习分析法
  20. SpringMVC的相关知识

热门文章

  1. fbx转3dtiles
  2. latex中表格怎么加标题_latex中如何设置两个标题
  3. Unity3D 引擎学习2022资料整理(二)
  4. 6、如何用backtrader实现双均线策略?以工商银行为例
  5. 五连阳回调买入法_5日均线和20日均线波浪买入法
  6. 【HYSTA Talk】对话美国生鲜电商Weee!创始人Larry—如何快速成长为独角兽企业?
  7. DCCA互相关系数 实例 python
  8. flex弹性布局之弹性盒子模型
  9. 大板加腋 弹性板6计算_弹性板6、弹性板3、弹性模对比分析
  10. 文件上传下载和Excel读写全家桶