Android图片加载框架Fresco使用详解

官方文档: https://www.fresco-cn.org/docs/getting-started.html

前言

Fresco是一个出自Facebook的功能强大的图片加载库

优缺点

优点:

1)内存自动回收。图片不可见时,会及时自动释放所占用的内存,尽可能地避免OOM
2)三级缓存机制。两级内存缓存(解码的与未解码的)+一级磁盘缓存,提升加载速度,节省内存占用空间
3)支持各种加载场景。如动图加载、高斯模糊等常见的图片加载场景。另外还提供了独特的渐进式加载、先加载小图再加载大图,加载进度等功能(很强大)。

缺点:

1)体积大(很胖)。较其他主流图片库体积要大不少
2)侵入性较强。须使用它提供的SimpleDraweeView来代替ImageView加载显示图片
综合来说,如果你的应用对图片的显示、加载等要求高的话,那就建议使用Fresco。但如果要求没那么高的话就用Glide或其它库吧。

介绍

下面通过 配置、SimpleDraweeView、加载图片、混淆、其他 这几个部分来介绍。

1. 配置

1.1 添加依赖

compile 'com.facebook.fresco:fresco:1.5.0'
compile 'com.facebook.fresco:animated-gif:1.5.0'//加载gif动图需添加此库
compile 'com.facebook.fresco:animated-webp:1.5.0'//加载webp动图需添加此库
compile 'com.facebook.fresco:webpsupport:1.5.0'//支持webp需添加此库
compile 'com.facebook.fresco:imagepipeline-okhttp3:1.5.0'//网络实现层使用okhttp3需添加此库
compile 'jp.wasabeef:fresco-processors:2.1.0@aar'//用于提供fresco的各种图片变换

1.2 设置磁盘缓存

ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context);
imagePipelineConfigBuilder.setMainDiskCacheConfig(DiskCacheConfig.newBuilder(context)
.setBaseDirectoryPath(context.getExternalCacheDir())//设置磁盘缓存的路径
.setBaseDirectoryName(BaseConstants.APP_IMAGE)//设置磁盘缓存文件夹的名称
.setMaxCacheSize(MAX_DISK_CACHE_SIZE)//设置磁盘缓存的大小
.build());

1.3 设置内存缓存

设置已解码的内存缓存(Bitmap缓存)

ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context);
imagePipelineConfigBuilder.setBitmapMemoryCacheParamsSupplier(new Supplier() {
public MemoryCacheParams get() {
int MAX_HEAP_SIZE = (int) Runtime.getRuntime().maxMemory();
int MAX_MEMORY_CACHE_SIZE = MAX_HEAP_SIZE / 5;//取手机内存最大值的五分之一作为可用的最大内存数
MemoryCacheParams bitmapCacheParams = new MemoryCacheParams( //
// 可用最大内存数,以字节为单位
MAX_MEMORY_CACHE_SIZE,
// 内存中允许的最多图片数量
Integer.MAX_VALUE,
// 内存中准备清理但是尚未删除的总图片所可用的最大内存数,以字节为单位
MAX_MEMORY_CACHE_SIZE,
// 内存中准备清除的图片最大数量
Integer.MAX_VALUE,
// 内存中单图片的最大大小
Integer.MAX_VALUE);
return bitmapCacheParams;
}
});

设置未解码的内存缓存

ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context);
imagePipelineConfigBuilder.setEncodedMemoryCacheParamsSupplier(new Supplier() {
public MemoryCacheParams get() {
MemoryCacheParams bitmapCacheParams;
//设置大小,可参考上面已解码的内存缓存
return bitmapCacheParams;
}
});

1.4 设置内存紧张时的应对措施

MemoryTrimmableRegistry memoryTrimmableRegistry = NoOpMemoryTrimmableRegistry.getInstance();
memoryTrimmableRegistry.registerMemoryTrimmable(new MemoryTrimmable() {
@Override
public void trim(MemoryTrimType trimType) {
final double suggestedTrimRatio = trimType.getSuggestedTrimRatio();
if (MemoryTrimType.OnCloseToDalvikHeapLimit.getSuggestedTrimRatio() == suggestedTrimRatio
|| MemoryTrimType.OnSystemLowMemoryWhileAppInBackground.getSuggestedTrimRatio() == suggestedTrimRatio
|| MemoryTrimType.OnSystemLowMemoryWhileAppInForeground.getSuggestedTrimRatio() == suggestedTrimRatio) {
//清空内存缓存
ImagePipelineFactory.getInstance().getImagePipeline().clearMemoryCaches();
  }
}
});
ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context);
imagePipelineConfigBuilder.setMemoryTrimmableRegistry(memoryTrimmableRegistry);

1.5 设置渐进式显示的效果

ProgressiveJpegConfig progressiveJpegConfig = new ProgressiveJpegConfig() {
@Override
public int getNextScanNumberToDecode(int scanNumber) {
//返回下一个需要解码的扫描次数
return scanNumber + 2;
}
public QualityInfo getQualityInfo(int scanNumber) {
boolean isGoodEnough = (scanNumber >= 5);
//确定多少个扫描次数之后的图片才能开始显示。
return ImmutableQualityInfo.of(scanNumber, isGoodEnough, false);
}
};
//具体含义可参考 http://wiki.jikexueyuan.com/project/fresco/progressive-jpegs.html
ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context);
imagePipelineConfigBuilder.setProgressiveJpegConfig(progressiveJpegConfig);
//或者使用默认的效果
//imagePipelineConfigBuilder.setProgressiveJpegConfig(new SimpleProgressiveJpegConfig());

设置完效果后,还需在下面介绍的ImageRequest中开启渐进式加载。

1.6 允许解码时调整图片大小

允许后,即可在后面介绍的ImageRequest中对结合ResizeOptions对解码后的图片大小进行调整,从而优化了图片所占大小。默认只支持JPEG图,所以要设置该属性来支持png、jpg、webp。

ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context);

imagePipelineConfigBuilder.setDownsampleEnabled(true);

1.7 开启Log

FLog.setMinimumLoggingLevel(FLog.VERBOSE);

Set requestListeners = new HashSet<>();

requestListeners.add(new RequestLoggingListener());

ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context);

imagePipelineConfigBuilder.setRequestListeners(requestListeners);

1.8 初始化

上面的各种配置都是通过ImagePipelineConfig进行的,接着需要进行初始化,在Application中初始化即可

ImagePipelineConfig.Builder imagePipelineConfigBuilder = ImagePipelineConfig.newBuilder(context);
//...进行各种设置
ImagePipelineConfig config = imagePipelineConfigBuilder.build();
Fresco.initialize(context, config);

如果想直接使用默认的配置,可以

Fresco.initialize(context);

2. SimpleDraweeView

Fresco要求使用SimpleDraweeView来替换ImageView进行图片的加载与显示,不少人也是因为这一点而不想使用Fresco。

下面介绍SimpleDraweeView在xml中的各种属性

//在最外层布局的属性中加入xmlns:fresco="http://schemas.android.com/apk/res-auto"


属性 作用说明

actualImageScaleType 加载完成的图片的缩放样式
fadeDuration 由进度条和占位符图片渐变过渡到加载完成的图片所使用的时间间隔
failureImage 加载失败所使用的图片
failureImageScaleType 加载失败所使用的图片的缩放样式
placeholderImage 占位符图片
placeholderImageScaleType 占位符图片的缩放样式
progressBarAutoRotateInterval 旋转进度条旋转1圈所需要的时间
progressBarImage 旋转进度条所使用的图片
progressBarImageScaleType 旋转进度条所使用的图片的缩放样式
retryImage 重试所使用的图片
retryImageScaleType 重试所使用的图片的缩放样式
backgroundImage 背景图片
overlayImage 覆盖在加载完成后图片上的叠加图片
pressedStateOverlayImage 按压状态下的叠加图片
roundAsCircle 是否将图片剪切为圆形
roundedCornerRadius 圆角图片时候,圆角的半径大小
roundTopLeft 左上角是否为圆角
roundTopRight 右上角是否为圆角
roundBottomLeft 左下角是否为圆角
roundBottomRight 右下角是否为圆角
roundWithOverlayColor 圆角或圆形图叠加的颜色,只能是颜色
roundingBorderWidth 圆角或圆形图边框的宽度
roundingBorderColor 圆角或圆形图边框的颜色
viewAspectRatio 设置宽高比

*注意:

1)android:src属性对于SimpleDraweeView无效,必要的话可用fresco:placeholderImage来设置。
2)SimpleDraweeView不支持android:layout_width和android:layout_height同时都设为wrap_content。

3. 加载图片

使用Fresco加载图片,大致是按以下流程进行的。
1. 设置Hierarchay(上面xml中的属性以及显示加载进度条等,可在这进行设置)
2. 构建ImageRequest(加载路径、开启渐进式加载、图片变换、调整解码图片大小等,可在这进行设置)
3. 构建DraweeController(动图加载、失败后点击重新加载等,可在这进行设置)
4. 进行图片加载

3.1 设置Hierarchay

虽然xml中的属性都能在这一步通过代码进行设置,但一般只在这设置一些统一固定的属性,比如加载占位图、加载失败图等。 另外,显示图片的加载进度也是在这里设置。

Resources res = MyApplication.getInstance().getResources();
Drawable retryImage = ResourcesCompat.getDrawable(res, R.mipmap.ic_image_load, null);
Drawable failureImage = ResourcesCompat.getDrawable(res, R.mipmap.ic_image_load, null);
Drawable placeholderImage = ResourcesCompat.getDrawable(res, R.mipmap.ic_image_load, null);
//对Hierarchy进行设置,如各种状态下显示的图片
public void setHierarchay(GenericDraweeHierarchy hierarchy) {
if (hierarchy != null) {
//重新加载显示的图片
hierarchy.setRetryImage(retryImage);
//加载失败显示的图片
hierarchy.setFailureImage(failureImage, ScalingUtils.ScaleType.CENTER_CROP);
//加载完成前显示的占位图
hierarchy.setPlaceholderImage(placeholderImage, ScalingUtils.ScaleType.CENTER_CROP);
//设置加载成功后图片的缩放模式
hierarchy.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP);
//显示加载进度条,使用自带的new ProgressBarDrawable()
//默认会显示在图片的底部,可以设置进度条的颜色。
hierarchy.setProgressBarImage(new ProgressBarDrawable());
//设置图片加载为圆形
hierarchy.setRoundingParams(RoundingParams.asCircle());
//设置图片加载为圆角,并可设置圆角大小
hierarchy.setRoundingParams(RoundingParams.fromCornersRadius(radius));
//其他设置请查看具体API。
  }
}

3.2 构建ImageRequest

/**
* 构建、获取ImageRequest
* @param uri 加载路径
* @param simpleDraweeView 加载的图片控件
* @return ImageRequest
*/

public ImageRequest getImageRequest(Uri uri, SimpleDraweeView simpleDraweeView) {
int width;
int height;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
width = simpleDraweeView.getWidth();
height = simpleDraweeView.getHeight();
} else {
width = simpleDraweeView.getMaxWidth();
height = simpleDraweeView.getMaxHeight();
}
//根据请求路径生成ImageRequest的构造者
ImageRequestBuilder builder = ImageRequestBuilder.newBuilderWithSource(uri);
//调整解码图片的大小
if (width > 0 && height > 0) {
builder.setResizeOptions(new ResizeOptions(width, height));
}
//设置是否开启渐进式加载,仅支持JPEG图片
builder.setProgressiveRenderingEnabled(true);
//图片变换处理
CombinePostProcessors.Builder processorBuilder = new CombinePostProcessors.Builder();
//加入模糊变换
processorBuilder.add(new BlurPostprocessor(context, radius));
//加入灰白变换
processorBuilder.add(new GrayscalePostprocessor());
//应用加入的变换
builder.setPostprocessor(processorBuilder.build());
//更多图片变换请查看https://github.com/wasabeef/fresco-processors
return builder.build();
}

3.3 构建DraweeController

/**
* 构建、获取Controller
* @param request
* @param oldController
* @return
*/
public DraweeController getController(ImageRequest request, @Nullable DraweeController oldController) {
PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder();
builder.setImageRequest(request);//设置图片请求
builder.setTapToRetryEnabled(false);//设置是否允许加载失败时点击再次加载
builder.setAutoPlayAnimations(true);//设置是否允许动画图自动播放
builder.setOldController(oldController);
return builder.build();
}

3.4 进行图片加载

创建一个loadImage方法将上面的Hierarchy、ImageRequest、DraweeController串在一起,供具体的加载场景使用

/**
* 加载图片核心方法
*
* @param simpleDraweeView 图片加载控件
* @param uri 图片加载地址
*/
public void loadImage(SimpleDraweeView simpleDraweeView, Uri uri) {
//设置Hierarchy
setHierarchay(simpleDraweeView.getHierarchy());
//构建并获取ImageRequest
ImageRequest imageRequest = getImageRequest(uri, simpleDraweeView);
//构建并获取Controller
DraweeController draweeController = getController(imageRequest, simpleDraweeView.getController());
//开始加载
simpleDraweeView.setController(draweeController);
}

具体的加载场景:

  • 加载网络图片,包括gif/webp动图
public void loadNetImage(SimpleDraweeView simpleDraweeView, String url) {
Uri uri = Uri.parse(url);
loadImage(simpleDraweeView, uri);
}

加载本地文件图片

public void loadLocalImage(SimpleDraweeView simpleDraweeView, String fileName) {
Uri uri = Uri.parse("file://" + fileName);
loadImage(simpleDraweeView, uri);
}

加载res下资源图片

public void loadResourceImage(SimpleDraweeView simpleDraweeView, @DrawableRes int resId) {
Uri uri = Uri.parse("res:///" + resId);
loadImage(simpleDraweeView, uri);
}

加载ContentProvider下的图片

public void loadContentProviderImage(SimpleDraweeView simpleDraweeView, int resId) {
Uri uri = Uri.parse("content:///" + resId);
loadImage(simpleDraweeView, uri);
}

加载asset下的图片

public void loadAssetImage(SimpleDraweeView simpleDraweeView, int resId) {
Uri uri = Uri.parse("asset:///" + resId);
}

加载网络图片,先加载小图,待大图加载完成后再替换掉小图

这个需要修改一下DraweeController的构建,通过setLowResImageRequest来添加小图请求

public DraweeController getSmallToBigController(ImageRequest smallRequest, ImageRequest bigRequest, @Nullable DraweeController oldController) {
PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder();
builder.setLowResImageRequest(smallRequest);//小图的图片请求
builder.setImageRequest(bigRequest);//大图的图片请求
builder.setTapToRetryEnabled(false);//设置是否允许加载失败时点击再次加载
builder.setAutoPlayAnimations(true);//设置是否允许动画图自动播放
builder.setOldController(oldController);
return builder.build();
}
public void loadImageSmallToBig(SimpleDraweeView simpleDraweeView, Uri smallUri, Uri bigUri) {
//设置Hierarchy
setHierarchay(simpleDraweeView.getHierarchy());
//构建小图的图片请求
ImageRequest smallRequest = getImageRequest(smallUri, simpleDraweeView);
//构建大图的图片请求
ImageRequest bigRequest = getImageRequest(bigUri, simpleDraweeView);
//构建Controller
DraweeController draweeController = getSmallToBigController(smallRequest, bigRequest, simpleDraweeView.getController());
//开始加载
simpleDraweeView.setController(draweeController);
}
//加载网络图片,先加载小图,待大图加载完成后替换
public void loadNetImageSmallToBig(SimpleDraweeView simpleDraweeView, String smallUrl, String bigUrl) {
Uri smallUri = Uri.parse(smallUrl);
Uri bigUri = Uri.parse(bigUrl);
loadImageSmallToBig(simpleDraweeView, smallUri, bigUri);
}

4. 混淆

在proguard-rules.pro文件中添加以下内容进行混淆配置

-keep class com.facebook.fresco.** { *; }
-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
-keep @com.facebook.common.internal.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.common.internal.DoNotStrip *;
}
-keep class com.facebook.imagepipeline.gif.** { *; }
-keep class com.facebook.imagepipeline.webp.* { *; }
-keepclassmembers class * {
native ;
}
-dontwarn okio.**
-dontwarn com.squareup.okhttp.**
-dontwarn okhttp3.**
-dontwarn javax.annotation.**
-dontwarn com.android.volley.toolbox.**
-keep class com.facebook.imagepipeline.animated.factory.AnimatedFactoryImpl {
public AnimatedFactoryImpl(com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory,com.facebook.imagepipeline.core.ExecutorSupplier);
}

5. 其他

5.1 缓存策略
Fresco采用三级缓存机制,两级内存缓存+一级磁盘缓存,其中两级内存缓存分为已解码的图片缓存(Bitmap缓存)和未解码的图片缓存。
下面通过加载流程来了解其缓存策略。
1. 根据Uri到已解码的图片缓存中查找是否存在对应的Bitmap。如果存在,则返回Bitmap显示;
如果不存在,则到未解码的图片缓存中查找。
2. 如果在未解码的图片缓存中存在对应的数据,则解码,返回Bitmap显示并将其加入到已解码的图片缓存中;如果不存在,则到磁盘缓存中查找。
3. 如果在磁盘缓存中存在对应的数据,则将数据加入到未解码的图片缓存中,然后解码,返回Bitmap显示并将其加入到已解码的图片缓存中;如果不存在,则进行网络请求或者到本地文件加载。
4. 请求或加载成功后,将数据加入到磁盘缓存和未解码的图片缓存中,然后解码,返回Bitmap显示并将其加入到已解码的图片缓存中。
简单整了个示意图,帮助理解下:


缓存策略示意图.png

5.2 兼容共享动画
android5.0之后加入了共享动画,
如果直接结合Fresco和共享动画来实现页面的过渡效果,会发现无效或异常。
Fresco官方也给出了说明,https://www.fresco-cn.org/docs/shared-transitions.html
1.重写共享动画转换效果的xml文件,注释掉changeImageTransform,并将该文件放于res/transition文件夹下










2.在style文件中使用上一步重写的xml文件





5.3 浏览大图
“点击小图浏览大图,并且大图支持缩放。”
这种需求经常能见到,上面提到的SimpleDraweeView并不支持缩放等功能,所以需要重新定制一个控件来显示。
官方给出了一个ZoomableDraweeView来支持该场景,另外也可以参考PhotoDraweeView

5.4 获取网络请求回来的Bitmap
有时候,我们需要拿到网络请求回来的Bitmap对象,那么我们可以这么做:

//加载图片,在ImageListener回调里获取返回的Bitmap
    public void getBitmap(Context context, String url, final ImageListener imageListener) {
        Uri uri = Uri.parse(url);
        ImagePipeline imagePipeline = Fresco.getImagePipeline();
        ImageRequestBuilder builder = ImageRequestBuilder.newBuilderWithSource(uri);
        ImageRequest imageRequest = builder.build();
        DataSource> dataSource = imagePipeline.fetchDecodedImage(imageRequest, context);
        dataSource.subscribe(new BaseDataSubscriber>() {
            @Override
            public void onNewResultImpl(DataSource> dataSource) {
                if (!dataSource.isFinished()) {
                    return;
                }
                CloseableReference imageReference = dataSource.getResult();
                if (imageReference != null) {
                    final CloseableReference closeableReference = imageReference.clone();
                    try {
                        CloseableImage closeableImage = closeableReference.get();
                        //动图处理
                        if (closeableImage instanceof CloseableAnimatedImage) {
                            AnimatedImageResult animatedImageResult = ((CloseableAnimatedImage) closeableImage).getImageResult();
                            if (animatedImageResult != null && animatedImageResult.getImage() != null) {
                                int imageWidth = animatedImageResult.getImage().getWidth();
                                int imageHeight = animatedImageResult.getImage().getHeight();
                                Bitmap.Config bitmapConfig = Bitmap.Config.ARGB_8888;
                                Bitmap bitmap = Bitmap.createBitmap(imageWidth, imageHeight, bitmapConfig);
                                animatedImageResult.getImage().getFrame(0).renderFrame(imageWidth, imageHeight, bitmap);
                                if (imageListener != null) {
                                    imageListener.onSuccess(bitmap);
                                }
                            }
                        }
                        //非动图处理
                        else if (closeableImage instanceof CloseableBitmap) {
                            CloseableBitmap closeableBitmap = (CloseableBitmap) closeableImage;
                            Bitmap bitmap = closeableBitmap.getUnderlyingBitmap();
                            if (bitmap != null && !bitmap.isRecycled()) {
                                final Bitmap tempBitmap = bitmap.copy(bitmap.getConfig(), false);
                                if (imageListener != null) {
                                    imageListener.onSuccess(tempBitmap);
                                }
                            }
                        }
                    } finally {
                        imageReference.close();
                        closeableReference.close();
                    }
                }
            }

            @Override
            public void onFailureImpl(DataSource dataSource) {
                Throwable throwable = dataSource.getFailureCause();
                if (imageListener != null) {
                    imageListener.onFail(throwable);
                }
            }
        }, UiThreadImmediateExecutorService.getInstance());
    }

或者如果缓存里有数据,可以从缓存中取出然后转为bitmap

FileBinaryResource resource = (FileBinaryResource) Fresco.getImagePipelineFactory().getMainFileCache().getResource(new SimpleCacheKey(url));
if (resource != null && resource.getFile() != null) {
Bitmap bitmap = BitmapFactory.decodeFile(resource.getFile().getAbsolutePath());
}

5.5 下载图片

下载图片到指定位置,在ImageListener回调里得到下载结果

  public void downLoadImage(Context context, String url, final File saveFile, final ImageListener imageListener) {
        Uri uri = Uri.parse(url);
        ImagePipeline imagePipeline = Fresco.getImagePipeline();
        ImageRequestBuilder builder = ImageRequestBuilder.newBuilderWithSource(uri);
        ImageRequest imageRequest = builder.build();
// 获取未解码的图片数据
        DataSource> dataSource = imagePipeline.fetchEncodedImage(imageRequest, context);
        dataSource.subscribe(new BaseDataSubscriber>() {
                                 @Override
                                 public void onNewResultImpl(DataSource> dataSource) {
                                     if (!dataSource.isFinished()) {
                                         return;
                                     }
                                     CloseableReference imageReference = dataSource.getResult();
                                     if (imageReference != null) {
                                         final CloseableReference closeableReference = imageReference.clone();
                                         try {
                                             PooledByteBuffer pooledByteBuffer = closeableReference.get();
                                             InputStream inputStream = new PooledByteBufferInputStream(pooledByteBuffer);
                                             OutputStream outputStream = new FileOutputStream(saveFile);
                                             if (FileUtil.saveFile(inputStream, outputStream) && imageListener != null) {
                                                 imageListener.onSuccess(saveFile);
                                             }
                                         } catch (Exception e) {
                                             if (imageListener != null) {
                                                 imageListener.onFail(e);
                                             }
                                             e.printStackTrace();
                                         } finally {
                                             imageReference.close();
                                             closeableReference.close();
                                         }
                                     }
                                 }

                                 @Override
                                 public void onProgressUpdate(DataSource> dataSource) {
                                     int progress = (int) (dataSource.getProgress() * 100);
                                     RingLog.d("fresco下载图片进度:" + progress);
                                 }

                                 @Override
                                 public void onFailureImpl(DataSource dataSource) {
                                     Throwable throwable = dataSource.getFailureCause();
                                     if (imageListener != null) {
                                         imageListener.onFail(throwable);
                                     }
                                 }
                             },
                Executors.newSingleThreadExecutor());
    }

你可能感兴趣的:(Android图片加载框架Fresco使用详解)