你所不知道的fresco使用集锦

作者: 皮球二二 | 来源:发表于2017-06-22 15:26 被阅读7043次

在最近一次项目开发中,我使用了fresco作为图片加载引擎框架。fresco功能非常强大,但是在开发过程中我遇到很多深坑,所以特意写一篇文章来记录一下自己遇到的问题,以便于方便大家日后使用
本文对应的代码在github上,代码是用kotlin写的,在学习的过程中顺带体验一下kotlin的强大之处

环境搭建

compile 'com.facebook.fresco:fresco:1.3.0'
// 支持webp
compile 'com.facebook.fresco:webpsupport:1.3.0'
// gif加载使用
compile 'com.facebook.fresco:animated-gif:1.3.0'
// WebP(静态图+动图)加载使用
compile 'com.facebook.fresco:animated-webp:1.3.0'

基本概念

我们先简单了解下fresco中的重要类
DraweeView:继承自View,一般情况下我们使用SimpleDraweeView类进行图片加载。关于其自定义属性稍后我们介绍
DraweeHierarchy:渲染图片内容的类,我们可以通过它在java代码里设置DraweeView的属性
DraweeController:负责框架底层的图片加载的类
Image Pipeline:(图形管道)完成图片的获取。不管是通过网络、本地文件、content provider还是本地资源,它都把图片压缩并缓存到磁盘,并且把内存作为第二缓存存储着解码后的图片

在XML中进行属性配置

Fresco有如下几种图层

/* 占位图层,即默认情况下显示的图层 */
private final int mPlaceholderImageIndex;
/* 进度条图层,即加载过程中的圆形或者线性进度条 */
private final int mProgressBarImageIndex;
/* 目标显示图层,即加载完成之后显示的图层 */
private final int mActualImageIndex;
/* 重试图层,即加载失败重试时显示的图层 */
private final int mRetryImageIndex;
/* 失败图层,即加载失败时显示的图层 */
private final int mFailureImageIndex;
/* 控制覆盖图层,即遮挡在最上方的图层 */
private final int mControllerOverlayIndex;

围绕着这6个图层,我们的SimpleDraweeView布局一般包含如下主要元素

<com.facebook.drawee.view.SimpleDraweeView xmlns:fresco="http://schemas.android.com/apk/res-auto"
        android:id="@+id/image_fresco2"
        android:layout_width="100dip"
        android:layout_height="wrap_content"
        fresco:actualImageScaleType="centerCrop"
        fresco:fadeDuration="3000"
        fresco:failureImage="@mipmap/ic_launcher"
        fresco:failureImageScaleType="centerCrop"
        fresco:placeholderImage="@mipmap/ic_launcher"
        fresco:placeholderImageScaleType="centerCrop"
        fresco:progressBarAutoRotateInterval="1000"
        fresco:progressBarImage="@drawable/ani_rotate"
        fresco:progressBarImageScaleType="centerCrop"
        fresco:retryImage="@mipmap/ic_launcher"
        fresco:retryImageScaleType="centerCrop"
        fresco:backgroundImage="@mipmap/ic_launcher"
        fresco:overlayImage="@mipmap/ic_launcher"
        fresco:pressedStateOverlayImage="@mipmap/ic_launcher"
        fresco:roundAsCircle="false"
        fresco:roundedCornerRadius="5dip"
        fresco:roundTopLeft="true"
        fresco:roundTopRight="true"
        fresco:roundBottomLeft="true"
        fresco:roundBottomRight="true"
        fresco:roundWithOverlayColor="@color/colorAccent"
        fresco:roundingBorderWidth="2dip"
        fresco:roundingBorderColor="@color/colorPrimary"
        fresco:viewAspectRatio="1"/>
属性 作用说明
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 设置宽高比

这里我就说一下scaleType。与ImageView相比,GenericDraweeView多了一个focusCrop这个属性。这个属性与centerCrop的区别在于前者可以自由定义聚焦点而后者的聚焦点是固定的。DraweeView显示时会尽量以此聚焦点为中心。 聚焦点是以相对坐标形式展现的,比如 (0f, 0f) 是左上角对齐显示,(1f, 1f) 是右下角对齐。这就使得聚焦点位置和具体尺寸无关,这是非常实用的。 如果将聚焦点设置为(0.5f, 0.5f) ,那么它和centerCrop是等价的。

如果要使用此缩放模式,首先在 XML 中指定缩放模式:

fresco:actualImageScaleType="focusCrop"

然后在Java代码中,给你的图片指定聚焦点:

image_fresco2!!.hierarchy.setActualImageFocusPoint(PointF(1f, 1f))

其他属性请参考源码部分

Fresco自定义属性位置

这里有几个注意的地方需要提醒一下大家

  1. SimpleDraweeView的宽高不能同时为wrap_content,需要使用match_parent或者一个固定值。除非你使用viewAspectRatio设置比例,在这种条件下宽、高中的其中一方可以是wrap_content
  2. 使用SimpleDraweeView的时候,facebook官方建议不要再使用ImageView的任何属性,如setImageResource、setBackground、setScaleType等这些ImageView中有但是View中没有的属性

在JAVA代码中实现XML中的属性配置

如果你对xml中的自定义属性已经很熟悉的话,再让你用java实现一遍应该是一件轻而易举的事情了
这里简单的从源码角度来看一下动态设置的流程
SimpleDraweeView是继承自GenericDraweeView

public class SimpleDraweeView extends GenericDraweeView

而GenericDraweeView中有一个重要的方法inflateHierarchy,GenericDraweeView的每一个构造方法都执行了这个方法,这个方法就是将xml中定义的图层信息转换成GenericDraweeHierarchyBuilder,最终使用setHierarchy设置到GenericDraweeView上

protected void inflateHierarchy(Context context, @Nullable AttributeSet attrs) {
    GenericDraweeHierarchyBuilder builder = GenericDraweeHierarchyInflater.inflateBuilder(context, attrs);
    this.setAspectRatio(builder.getDesiredAspectRatio());
    this.setHierarchy(builder.build());
}

现在就很明显了,我们只要设置这个hierarchy即可

    /**
     * 设置各种图像的层级
     */
    private fun setNormalHierarchy(context: Context) : GenericDraweeHierarchy {
        val hierarchy: GenericDraweeHierarchy = this.hierarchy
        // 系统默认横向线性进度条ProgressBarDrawable
//        val drawable: Drawable = ContextCompat.getDrawable(context, R.drawable.ani_rotate)
        if (mySimpleDraweeViewParams!!.progressBarImage!=null) {
            hierarchy.setProgressBarImage(AutoRotateDrawable(mySimpleDraweeViewParams!!.progressBarImage, 1000))
        }

        // 配置圆角图片
        var roundingParams: RoundingParams = RoundingParams.fromCornersRadius(30f)

        hierarchy.fadeDuration= mySimpleDraweeViewParams!!.fadeDuration
        hierarchy.roundingParams = roundingParams
        hierarchy.setOverlayImage(mySimpleDraweeViewParams!!.overlayImageDrawable)
        hierarchy.actualImageScaleType=mySimpleDraweeViewParams!!.scaleType
        hierarchy.setPlaceholderImage(mySimpleDraweeViewParams!!.placeHolderDrawable, mySimpleDraweeViewParams!!.scaleType)
        hierarchy.setFailureImage(mySimpleDraweeViewParams!!.placeHolderDrawable, mySimpleDraweeViewParams!!.scaleType)
        hierarchy.setRetryImage(mySimpleDraweeViewParams!!.placeHolderDrawable, mySimpleDraweeViewParams!!.scaleType)
        return hierarchy
    }

最终同样是

SimpleDraweeView.setHierarchy(setNormalHierarchy(context))

配置ImagePipelineConfig

刚才说了ImagePipeline在负责图片加载过程中是一个相当重要的角色,来看看它的工作流程

  1. 检查内存缓存,如有,返回
  2. 后台线程开始后续工作
  3. 检查是否在未解码内存缓存中。如有,解码,变换,返回,然后缓存到内存缓存中。
  4. 检查是否在文件缓存中,如果有,变换,返回。缓存到未解码缓存和内存缓存中。
  5. 从网络或者本地加载。加载完成后,解码,变换,返回。存到各个缓存中。

完成这些工作需要一定的策略,这就需要我们通过自定义ImagePipelineConfig接口完成

加载默认的默认配置就这一句话即可

Fresco.initialize(this)

下面来看看如何进行自定义配置

  1. 定义常量参数
companion object {
        // 最大可用内存
        val maxHeapSize: Long = Runtime.getRuntime().maxMemory()
        // 缓存Disk小图片文件夹名称
        val smallDiskCacheName: String = "smallDiskCacheConfig"
        // 缓存Disk普通图片文件夹名称
        val normalDiskCacheName: String = "normalDiskCacheConfig"
        // 缓存Disk文件夹大小
        val diskCacheSize: Int = 100 * ByteConstants.MB
        // 低硬盘空间下缓存Disk文件夹大小
        val lowDiskCacheSize: Int = 50 * ByteConstants.MB
        // 非常低硬盘空间下缓存Disk文件夹大小
        val veryLowDiskCacheSize: Int = 20 * ByteConstants.MB
}
  1. 实现ImagePipelineConfig接口
fun getDefaultImagePipelineConfig(context: Context) : ImagePipelineConfig {
}

看看具体的配置

var imagePipelineConfig: ImagePipelineConfig.Builder = ImagePipelineConfig.newBuilder(context)
                    .setProgressiveJpegConfig(SimpleProgressiveJpegConfig()) // 渐进式配置
                    .setBitmapsConfig(Bitmap.Config.RGB_565) // 没有透明图片显示要求,设置为RGB_565,减少内存开销
                    .setBitmapMemoryCacheParamsSupplier(supplierMemoryCacheParams) // 配置缓存策略
                    .setSmallImageDiskCacheConfig(smallDiskCacheConfig) // 小图片Disk缓存策略
                    .setMainDiskCacheConfig(normalDiskCacheConfig) // 基本图片Disk缓存策略
                    .setMemoryTrimmableRegistry(NoOpMemoryTrimmableRegistry.getInstance()) // 注册内存调用器,在需要回收内存的时候进行回收
                    .setResizeAndRotateEnabledForNetwork(true) // 对网络图片进行resize处理,减少内存消耗
                    .setDownsampleEnabled(true) // 必须和ImageRequest的ResizeOptions一起使用,作用就是在图片解码时根据ResizeOptions所设的宽高的像素进行解码,这样解码出来可以得到一个更小的Bitmap。

乍一看好多配置,我来稍微解释下:
ImagePipelineConfig:Fresco支持使用OKHTTP作为网络加载引擎,文中演示的是使用系统自身的网络加载引擎。如果使用OKhttp网络库进行加载,那么需要额外导入

compile 'com.facebook.fresco:imagepipeline-okhttp3:1.3.0'

同时对初始化ImagePipelineConfig的时候使用OkHttpImagePipelineConfigFactory

OkHttpImagePipelineConfigFactory.newBuilder(context, OkHttpClient())

setProgressiveJpegConfig:用来设置渐进式,稍后我们细说
setBitmapsConfig:在RGB_565的条件下过滤alpha通道,图片消耗内存量会降低,进一步降低OOM的风险
setBitmapMemoryCacheParamsSupplier:配置缓存策略,主要用来配置fresco相关条件下可用最大内存数以及文件数量。这个我们得通过代码去了解

val memmoryCacheParams = MemoryCacheParams(
                    maxHeapSize.toInt()/4, // 可用最大内存数,以字节为单位
                    Int.MAX_VALUE, // 内存中允许的最多图片数量
                    maxHeapSize.toInt()/4, // 内存中准备清理但是尚未删除的总图片所可用的最大内存数,以字节为单位
                    Int.MAX_VALUE, // 内存中准备清除的图片最大数量
                    Int.MAX_VALUE) // 内存中单图片的最大大小

设置完对象之后就放置在Supplier中

val supplierMemoryCacheParams: Supplier<MemoryCacheParams> = Supplier { return@Supplier memmoryCacheParams }

setSmallImageDiskCacheConfig:小图片Disk缓存策略,这里主要是设置磁盘存储的位置以及文件夹相关信息、可用空间大小等信息

val smallDiskCacheConfig = DiskCacheConfig.newBuilder(context)
                    .setBaseDirectoryPath(context.applicationContext.cacheDir) // 设置缓存图片本地根目录路径
                    .setBaseDirectoryName(smallDiskCacheName) // 设置缓存Disk文件夹名称
                    .setMaxCacheSize(diskCacheSize.toLong()) // 设置缓存Disk文件夹大小
                    .setMaxCacheSizeOnLowDiskSpace(lowDiskCacheSize.toLong()) // 设置低硬盘空间下缓存Disk文件夹大小
                    .setMaxCacheSizeOnVeryLowDiskSpace(veryLowDiskCacheSize.toLong()) // 设置非常低硬盘空间下缓存Disk文件夹大小
                    .build()

setMainDiskCacheConfig:默认情况下使用的是该配置,这里选择与小图片Disk采用相同的缓存策略配置,但是为什么需要配置2份呢?我们知道在实际运行中相关文件夹的读写操作会非常的频繁。由于配置了可用空间的容量,如果只有一个DiskCache,一旦大文件进来,势必会造成多数小文件被移除,所以分开配置是一个很不错的选择。至于什么是大图片什么是小图片,Fresco提供了2种方式让你加载图片时候的自由选择。

public static enum CacheChoice {
        SMALL,
        DEFAULT;

        private CacheChoice() {
        }
    }

在配置ImageRequest的时候进行设置

var imageRequestBuilder: ImageRequestBuilder = ImageRequestBuilder.newBuilderWithSource(url)
imageRequestBuilder.cacheChoice = mySimpleDraweeViewParams!!.cacheChoice

setMemoryTrimmableRegistry:此为默认配置项。注册内存调用器,在需要回收内存的时候进行回收
setResizeAndRotateEnabledForNetwork:在设置ImageRequest的时候允许其进行resize处理,减少内存消耗,也同样起到降低OOM的风险
setDownsampleEnabled:必须和ImageRequest的ResizeOptions一起使用,也是起到降低OOM的风险

显示

设置完图片的基本信息之后我们就回到配置Controller部分,先是配置imageRequest

private fun getImageRequest(url: Uri) : ImageRequest {
    var imageRequestBuilder: ImageRequestBuilder = ImageRequestBuilder.newBuilderWithSource(url)
    imageRequestBuilder.cacheChoice = mySimpleDraweeViewParams!!.cacheChoice // 图片选型,大图片还是小图片
    imageRequestBuilder.lowestPermittedRequestLevel = mySimpleDraweeViewParams!!.requestLevel // 最低允许从哪层缓存中取数据
    imageRequestBuilder.isLocalThumbnailPreviewsEnabled = true // 图片请求会在访问本地图片时先返回一个缩略图
    imageRequestBuilder.isProgressiveRenderingEnabled = mySimpleDraweeViewParams!!.progressiveRenderingEnabled // 渐进式
    imageRequestBuilder.resizeOptions = ResizeOptions(measuredWidth, measuredHeight) // 图片按照所设定的宽高解码
    imageRequestBuilder.rotationOptions = RotationOptions.forceRotation(mySimpleDraweeViewParams!!.rotate) // 图片旋转角度
    return imageRequestBuilder.build()
}

随后设置一些监听事件

var pipelineDraweeControllerBuilder: PipelineDraweeControllerBuilder=Fresco.newDraweeControllerBuilder()
pipelineDraweeControllerBuilder.autoPlayAnimations = mySimpleDraweeViewParams!!.autoPlayAnimation
pipelineDraweeControllerBuilder.controllerListener = mySimpleDraweeViewParams!!.controllerListener
pipelineDraweeControllerBuilder.imageRequest=getImageRequest(url)

使用的时候没什么问题

image_fresco3!!.hierarchy = setNormalHierarchy(this)
var pipelineDraweeControllerBuilder: PipelineDraweeControllerBuilder=Fresco.newDraweeControllerBuilder()
pipelineDraweeControllerBuilder.autoPlayAnimations = mySimpleDraweeViewParams!!.autoPlayAnimation
pipelineDraweeControllerBuilder.tapToRetryEnabled = true
pipelineDraweeControllerBuilder.controllerListener = mySimpleDraweeViewParams!!.controllerListener
pipelineDraweeControllerBuilder.imageRequest=getImageRequest(url)
image_fresco3!!.setController(pipelineDraweeControllerBuilder.build())

注意这边只有设置tapToRetryEnabled为true,才会出现点击重试的图层,并且重试超过4次之后,就将显示失败的图层

进度条的制作

进度条目前有3种样式选择

  1. 系统默认的线性进度条
hierarchy.setProgressBarImage(ProgressBarDrawable())
  1. 任意一张图片,使用旋转动画
<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/load_progress_8"
    android:pivotX="50%"
    android:pivotY="50%">
</animated-rotate>

直接使用该xml

val drawable: Drawable = ContextCompat.getDrawable(context, R.drawable.ani_rotate)
hierarchy.setProgressBarImage(AutoRotateDrawable(drawable, 1000))
  1. 自定义Drawable。通过onLevelChange回调方法去修改当前的画布
class LoadingProgressDrawable() : Drawable() {

    val images: IntArray = intArrayOf(R.drawable.load_progress_0,
            R.drawable.load_progress_1,
            R.drawable.load_progress_2,
            R.drawable.load_progress_3,
            R.drawable.load_progress_4,
            R.drawable.load_progress_5,
            R.drawable.load_progress_6,
            R.drawable.load_progress_7,
            R.drawable.load_progress_8,
            R.drawable.load_progress_9,
            R.drawable.load_progress_10,
            R.drawable.load_progress_11,
            R.drawable.load_progress_12)

    var mLevel: Int = 0

    var context: Context? = null

    var options: BitmapFactory.Options? = null

    var paint: Paint? = null

    constructor(context: Context) : this() {
        this.context = context
        options = BitmapFactory.Options()
        paint = Paint()
        paint!!.isAntiAlias = true
    }

    override fun draw(p0: Canvas?) {
        val bmp: Bitmap = BitmapFactory.decodeResource(context!!.resources, images[getIndex()], options)
        val imageWidth = bmp.width
        val imageHeight = bmp.height
        val drawableWidth = bounds.width()
        val drawableHeight = bounds.height()
        val left = (drawableWidth-imageWidth)/2
        val top = (drawableHeight-imageHeight)/2
        p0!!.drawBitmap(bmp, left.toFloat(), top.toFloat(), paint!!)
    }

    override fun setAlpha(p0: Int) {
        paint!!.alpha = p0
    }

    override fun getOpacity(): Int {
        return PixelFormat.TRANSPARENT
    }

    override fun setColorFilter(p0: ColorFilter?) {
        paint!!.setColorFilter(p0)
    }

    override fun onLevelChange(level: Int): Boolean {
        this.mLevel = level
        invalidateSelf()
        return true
    }

    fun getIndex(): Int {
        var index: Int = mLevel/1000
        if (index<0) {
            index=0
        }
        if (index>images.size-1) {
            index=images.size-1
        }
        return index
    }
}

这个其实没什么好说的

渐进式图片加载

渐进式图片是从模糊到清楚的一种加载模式。fresco仅支持文件类型为JPEG的网络图片,因为本地图片均一次性解码完成,所以本地图片不需要使用渐进式。在fresco中,你可以设置一个清晰度标准,使其在达到这个标准之前一直以占位图显示。
这个标准是通过实现ProgressiveJpegConfig接口完成

class ProgressiveJpegConfigClass : ProgressiveJpegConfig {
    override fun getNextScanNumberToDecode(p0: Int): Int {
        return p0+2
    }

    override fun getQualityInfo(p0: Int): QualityInfo {
        val isGoodEnough = p0 >= 5
        return ImmutableQualityInfo.of(p0, isGoodEnough, false)
    }
}

设置完成之后将其放置在ImagePinelineConfig中,跟随fresco一起初始化

var imagePipelineConfig: ImagePipelineConfig.Builder = ImagePipelineConfig.newBuilder(context)
                    .setProgressiveJpegConfig(ProgressiveJpegConfigClass())

getNextScanNumberToDecode: 返回下一个需要解码的扫描次数
getQualityInfo: 确定多少个扫描次数之后的图片才能开始显示
当然如果你想要偷懒,fresco也提供了默认的渐进式加载方案类——SimpleProgressiveJpegConfig,免去你手写的麻烦
最后只要给SimpleDraweeView设置一下渐进式就行了

imageRequestBuilder.isProgressiveRenderingEnabled = true

来看看渐进式的演变过程


渐进式1
渐进式2
渐进式3

图片加载事件监听

private class DefaultBaseControllerListener : BaseControllerListener<ImageInfo>() {
    // 图片加载成功时触发的方法
    override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
        super.onFinalImageSet(id, imageInfo, animatable)
        // 下载成功显示图片的宽高
        println("width:${imageInfo!!.width}  height:${imageInfo!!.height}")
     }

    // 加载渐进式图片时回调的方法
    override fun onIntermediateImageSet(id: String?, imageInfo: ImageInfo?) {
        super.onIntermediateImageSet(id, imageInfo)
    }

    // 图片加载失败时回调的方法
    override fun onFailure(id: String?, throwable: Throwable?) {
        super.onFailure(id, throwable)
    }
}

将这个监听事件放置在PipelineDraweeControllerBuilder的controllerListener中

var pipelineDraweeControllerBuilder: PipelineDraweeControllerBuilder=Fresco.newDraweeControllerBuilder()
pipelineDraweeControllerBuilder.controllerListener = mySimpleDraweeViewParams!!.controllerListener

对图片的额外处理工作

有时候,我们想对加载完成的图片进行渲染操作,虽然在刚才的onFinalImageSet可以拿到加载完成的bitmap,但是用这个方法肯定是不适合的,幸好fresco提供了后处理器Postprocessor来实现这个功能。下面这个例子就是用后处理器去实现简单的放大剪裁功能

class CutProcess(val mBeginXPercent: Float, val mBeginYPercent: Float, val mCutWidthPercent: Float, val mCutHeightPercent: Float) : BasePostprocessor() {

    override fun process(sourceBitmap: Bitmap?, bitmapFactory: PlatformBitmapFactory?): CloseableReference<Bitmap>? {
        val width = sourceBitmap!!.width
        val height = sourceBitmap!!.height
        val beginX = width*mBeginXPercent
        val beginY = height*mBeginYPercent
        val endX = width*mCutWidthPercent
        val endY = height*mCutHeightPercent
        val bmp: CloseableReference<Bitmap> = bitmapFactory!!.createBitmap(scale(sourceBitmap), beginX.toInt(), beginY.toInt(), endX.toInt(), endY.toInt())
        return CloseableReference.cloneOrNull(bmp)
    }

    fun scale(sourceBitmap: Bitmap?): Bitmap {
        val matrix: Matrix = Matrix()
        matrix.postScale(1.1f, 1.1f, 0.5f, 0.5f)
        return Bitmap.createBitmap(sourceBitmap!!, 0, 0, sourceBitmap!!.width, sourceBitmap!!.height, matrix, true)
    }
}

使用的时候直接在ImageRequest中添加

val imageRequest: ImageRequest=ImageRequestBuilder
                    .newBuilderWithSource(Uri.parse("res:///"+R.mipmap.rotate))
                    .setRotationOptions(RotationOptions.forceRotation(0))
                    .setPostprocessor(CutProcess(0f, 0f, 0.5f, 0.5f)).build()

这里额外教大家一个小技巧。刚才我们是通过createBitmap进行图片剪裁的,其实android还提供一个BitmapRegionDecoder类,他可以让我们解码一张图片的某个矩形区域

fun getBitmapArray(bitmap: Bitmap, quality: Int) : ByteArray {
    var os: ByteArrayOutputStream = ByteArrayOutputStream()
    bitmap.compress(Bitmap.CompressFormat.JPEG, quality, os)
    return os.toByteArray()
}

/**
  * 获取rect范围内的Bitmap
  */
fun getDecodeRegion(bytes: ByteArray, width: Int, height: Int) : Bitmap {
    val decodeRegion: BitmapRegionDecoder = BitmapRegionDecoder.newInstance(bytes, 0, bytes.size, true)
    val rect = Rect(0, 0, width, height)
    return decodeRegion.decodeRegion(rect, null)
}

这样可以直接获取某一个rect的图片

val bmp: CloseableReference<Bitmap> = bitmapFactory!!.createBitmap(CommonUtils.getDecodeRegion(CommonUtils.getBitmapArray(sourceBitmap, 100), sourceBitmap.width/2, sourceBitmap.height/2))

支持图片类型

类型 SCHEME
远程图片 http://, https://
本地文件 file://
Content provider content://
asset目录下的资源 asset://
res目录下的资源 res://
Uri中指定图片数据 data:mime/type;base64,数据类型必须符合 rfc2397规定 (仅支持 UTF-8)

对动画事件的控制

之前我们在pipelineDraweeControllerBuilder设置过autoPlayAnimations,让fresco自动处理这个播放事件。那怎么样才能由我们自己来控制这个动画的开始与停止呢?
开看看之前的下载监听器,在下载完成的回调中,有一个Animatable属性,可以通过这个属性操作这个动画

private class DefaultBaseControllerListener : BaseControllerListener<ImageInfo>() {
        override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?)
}

但是这个属性如果要在外部类使用的话,难道还要再申明一个全局变量吗?fresco当然没有这么傻,我们可以通过pipelineDraweeController拿到这个Animatable,这样就可对动画进行灵活的控制了

image_fresco2!!.controller.animatable

多图请求及图片复用

先来根据场景来说说需求吧

  1. 有时候为了让图片加载的更快一点,后台特地为我们准备了不同尺寸的图片。这样我们就优先显示低分辨率的图,然后再显示高分辨率的图,这个放到fresco里面怎么处理呢?很简单,还是在pipelineDraweeControllerBuilder配置setLowResImageRequest属性,将小图的ImageRequest也加一下即可
val draweeController: DraweeController=Fresco.newDraweeControllerBuilder()
    .setLowResImageRequest(ImageRequest.fromUri(Uri.parse("res:///"+R.mipmap.rotate)))
    .setImageRequest(imageRequest).setAutoPlayAnimations(true)
    .setOldController(image_fresco3!!.controller).build()
image_fresco3!!.controller=draweeController
  1. 缩略图预览
    如果本地的JPEG图有EXIF的缩略图,pipeline可以立刻返回这个缩略图。DraweeView会先显示缩略图然后再显示完整的清晰大图。这个功能有限制,仅支持本地URI,并且是JPEG图片格式。这个也很简单,直接在imageRequest里面配置一下setLocalThumbnailPreviewsEnabled(true)即可
val imageRequest: ImageRequest=ImageRequestBuilder
    .newBuilderWithSource(Uri.parse("file:///storage/emulated/0/DCIM/Camera/20170529_163007.jpg"))
    .setLocalThumbnailPreviewsEnabled(true)
  1. 如果是社交类app,一般具有发图的功能。这样就会出现本地图片与网络图片可能共存的情况。理想的流程当然是优先加载本地图片,如果本地图片不存在的时候再去加载网络图片。但是如果你每次调用都加这个判断的话,或许代码又比较累赘,幸好fresco又帮了我们大忙。又是我们无敌的PipelineDraweeControllerBuilder提供了setFirstAvailableImageRequests方法,这里面的ImageRequest,谁快谁就优先加载
val requests: Array<ImageRequest> = arrayOf(ImageRequest.fromUri(Uri.parse("res:///"+R.mipmap.rotate)), imageRequest)
val draweeController: DraweeController=Fresco.newDraweeControllerBuilder()
   .setFirstAvailableImageRequests(requests)
   .setOldController(image_fresco3!!.controller).build()
image_fresco3!!.controller=draweeController

仅获取图片

通过BaseBitmapDataSubscriber回调得到加载成功后的图片

var dataSource: DataSource<CloseableReference<CloseableImage>> =
                Fresco.getImagePipeline().fetchDecodedImage(ImageRequest.fromUri("xxx.jpg"), this)
dataSource.subscribe(CustomerBitmapDataSubscriber(), CallerThreadExecutor.getInstance())
class CustomerBitmapDataSubscriber : BaseBitmapDataSubscriber() {
    override fun onFailureImpl(p0: DataSource<CloseableReference<CloseableImage>>?) {

    }

    override fun onNewResultImpl(p0: Bitmap?) {
        println("获取成功")
    }
}

有一点要注意的是,由于这个bitmap同样存在在Fresco的缓存中,所以可能会很快被系统回收掉,需要自己另行复制一份自行保存

清除缓存

同之前加载逻辑一致,缓存的清理也是分为内存跟磁盘2个方向走。
我们之前没有配置过CacheKeyFactory,所以可以直接使用evictFromXXX来删除指定的url。如果你自己配置过了,那你就需要通过ImageRequest才能删除

Fresco.getImagePipeline().evictFromMemoryCache(url)
Fresco.getImagePipeline().evictFromDiskCache(url)
Fresco.getImagePipeline().evictFromCache(url)

最后是清除全部的缓存

Fresco.getImagePipeline().clearMemoryCaches()
Fresco.getImagePipeline().clearDiskCaches()
Fresco.getImagePipeline().clearCaches()

关于OOM

不当的使用Fresco会频繁的导致OOM的发生,至少我是遇到过了,我这里给大家2个建议:

  1. 请给ImageRequest默认配置上setResizeOptions(resizeOptions)属性,这个已经反复在本文中说明很多次了。对于照片墙等含有大量图片的页面,必须要对图片的大小做限制;网络图片可以通过服务端来对图片的尺寸、质量、图片类型做处理后再返给客户端,但是对于手机本地的图片,就只能通过setResizeOptions来有效降低内存缓存的开销。
  2. 在低内存的情况下或者退出多图页面的情况下,手动释放内存缓存:

参考文章

Fresco-Facebook的图片加载框架的使用

相关文章

网友评论

  • 请叫我大苏:膜拜,特别棒
  • 关山明月:有个问题,当使用fresco加载网络图片时,加载失败后,在华为手机(如荣耀8)上并不显示加载失败的图片,设置controller后,断点调试时发现并不调用onFailure方法,其他手机如小米等都正常,然后如果网络图片加载失败了会显示一块黑色的,在我的RecycleView列表里,更是直接将整个页面透过去了,Item设置背景颜色或背景图片都没作用,都透过去了 :joy:
    皮球二二:@关山明月 都是安卓7.0的吧
    关山明月:@r17171709 honor8 和P10
    皮球二二:@关山明月 这是什么版本的系统,我华为mate8好像没这个问题
  • 才兄说:可以的,对比几个图片框架,感觉glide 比较简单…
    Hidetag:@天鬼 不能同意更多。fresco 太重了,同替换成 Glide 。
    才兄说:Fresco 开c调用性能确实有所提高,但是作为开发者,我更看重解耦,简单易用,Frosco 这方面就感觉不是很好,曾经把项目的图片框架全部从Frosco 换成glide 也是这个原因,个人观点,最终看团队及开发者喜好了
    皮球二二: @天鬼 fresco不是图片处理层在c里面吗,我就看中这点

本文标题:你所不知道的fresco使用集锦

本文链接:https://www.haomeiwen.com/subject/hexnqxtx.html