android 图片加载库(4)- Fresco

上一篇说完 Glide 之后,我们现在来说下 Fresco,从使用感受上来看,使用 Fresco 比 Glide 要简便不少,但是 Fresco 需要使用单独的图片控件,系统原生的就不行了,代码侵入性太强。从性能上来说 Fresco 很强大,尤其内存性能强大,但是 Fresco 包太 5M,Glide 的包才 900K

Fresco 支持很多功能:

  • 占位图
  • 每种占位图都支持独立的缩放模式
  • 进度图 :样式是提供一张图,然后旋转,可以指定旋转一周的时间
  • 重试 :重试的支持很棒,可以设置重试图,重试4次失败,显示失败图
  • 淡入淡出动画:这里支持的很好,没 Glide 这么多事
  • 支持圆角,圆形显示
  • 支持边框
  • 支持遮蔽图层 : 叠加图
  • 支持打底图层:就是背景图
  • 支持按压时的显示指定图片
  • 支持渐进式显示:不过需要 JPEG 图片按照渐进式编码才行
  • 支持 GIF, 静态动态 WebP 的显示
  • 支持自动调整图片方向,按照当前的屏幕方向
  • 支持预加载,可以指定预加载到 File 还是 memory ,这对于我们展示大图的列表,尤其是像电商的详情页,漫画来说是有尤为重要的。
  • 支持自定义的 overlay 图层,这样我们可以很方便的加标签上去
  • 支持进度条,但是 Fresco 的默认实现很一般,只是在图片那地步的一个蓝色的进度条,要实现更多必须自定义 drawable
Fresco 的不足:

  • 不支持 wrap_content ,必须显示的指定 view 的长宽
  • 不会按照 view 的尺寸,动态调整图片宽高尺寸
  • res 资源只支持真正的图片资源,xml 的不行。若需要的话,可以设置为占位图,加载资源设为 null 即可
  • 支持多图请求,相当于 Glide 的缩略图,但是相比 Glide 的缩略图设置要差很多,多图请求不是并发,是先后顺序,不能设置原图为缩略图然后指定尺寸和缩略倍数
  • 使用特有的 view 来显示图片,侵入性强
  • 在 4.X 版本不兼容 SVG 矢量图,即便是按照 SVG 4.X 版本兼容写,把 SVG 图片包裹到 seletor 里面一样也是不行。

学习资料


Fresco 从15年出现开始,已经有很多优秀的学习资料了,跟着这些学习资料走就是最好的选择,当然不要忘了看完入门再去看看光放文档,中文的:Fresco官方中文文档

入门3部曲:

深入学习:

简单使用


上面我们看完入门文档后,这里我还是要记录下 Fresco 常用的使用和各种设置的。

引入依赖
implementation 'com.facebook.fresco:fresco:1.8.1'

若想支持 webp,gif 还需要添加相应的支持库

compile 'com.facebook.fresco:fresco:1.5.0'
//加载gif动图需添加此库
compile 'com.facebook.fresco:animated-gif:1.5.0'
//加载webp动图需添加此库
compile 'com.facebook.fresco:animated-webp:1.5.0'
//支持webp需添加此库
compile 'com.facebook.fresco:webpsupport:1.5.0'
//网络实现层使用okhttp3需添加此库
compile 'com.facebook.fresco:imagepipeline-okhttp3:1.5.0'
全局初始化
 Fresco.initialize(this);
加载图片
   image.setImageURI( "xxx" );
重试代码
  DraweeController controller = Fresco.newDraweeControllerBuilder()
                //加载的图片URI地址   
                .setUri("xxx")
                //设置点击重试是否开启
                .setTapToRetryEnabled(true)
                //设置旧的Controller
                .setOldController(image.getController())
                //构建
                .build();
        image.setController(controller);
xml 使用

这里简单写一下,全部属性去看下面的属性列表

 <com.facebook.drawee.view.SimpleDraweeView
            android:id="@+id/image_net"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginTop="10dp"
            app:actualImageScaleType="focusCrop"
            app:fadeDuration="300"
            app:failureImage="@drawable/icon_error"
            app:layout_constraintDimensionRatio="H,1:1"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:placeholderImage="@drawable/icon_place"
            app:placeholderImageScaleType="centerCrop"
            app:progressBarAutoRotateInterval="5000"
            app:progressBarImage="@drawable/icon_process"
            app:retryImage="@drawable/icon_retry"
            app:roundingBorderWidth="3dp"
            app:roundingBorderColor="@color/colorPrimary"
            app:roundAsCircle="true"/>
全部属性列表
XML属性 意义
fadeDuration 淡入淡出动画持续时间(单位:毫秒ms)
actualImageScaleType 实际图像的缩放类型
placeholderImage 占位图
placeholderImageScaleType 占位图的缩放类型
progressBarImage 进度图
progressBarImageScaleType 进度图的缩放类型
progressBarAutoRotateInterval 进度图自动旋转间隔时间(单位:毫秒ms)
failureImage 失败图
failureImageScaleType 失败图的缩放类型
retryImage 重试图
retryImageScaleType 重试图的缩放类型
backgroundImage 背景图
overlayImage 叠加图
pressedStateOverlayImage 按压状态下所显示的叠加图
roundAsCircle 设置为圆形图
roundedCornerRadius 圆角半径
roundTopLeft 左上角是否为圆角
roundTopRight 右上角是否为圆角
roundBottomLeft 左下角是否为圆角
roundBottomRight 右下角是否为圆角
roundingBorderWidth 圆形或者圆角图边框的宽度
roundingBorderColor 圆形或者圆角图边框的颜色
roundWithOverlayColor 圆形或者圆角图底下的叠加颜色(只能设置颜色)
viewAspectRatio 控件宽高比

缩放类型 focusCrop = centerCrop,这个是 Fresco 特有的,可以指定居中点的位置

缓存策略


Fresco 的图片缓存是非常值得我们去好好探寻一番的,Fresco 在 5.0 以下的内存性能是要比 Glide 强很多的,但是 5.0 之后就趋同了,都是用 BitmapFactory.Options.inBitmap 这个优化方案了,这毕竟是 google 官方的优化嘛,5.0 之前 google 做的太烂了,大家各自有不同的优化可以理解,但是到 5.0 之后 Google 的 inBitmap 这个 Bitmap 在堆内存的缓存池的优化做的还是不错的,所以大家又回到 google 的方案了。

Fresco 的网络缓存,磁盘缓存每什么说的,重点在 Bitmap 的内存缓存上

  • Glide 的方案:
    缓存 bitmap 原图和变换处理过后的 bitmap。
  • Fresco 的方案:
    2个缓存池,一个缓存 decode 解码钱的图片资源,一个缓存 bitmap 位图。

这2大框架,相比较起来,理论上还是 Fresco 要节省一些内存的,图片没 decode 解码就不是bitmap 对象,内存占用小很多,但是劣势是 CPU 工作量大一些,得益于 Fresco 优秀的线程池设计,这点不是问题,Fresco 可是维护了3个干不同类型活的线程池,具体的可以去看官方文档。但是实际上 Fresco 不支持根据 view 的大小动态调节图片尺寸,在实际使用中要是不优化这块的话 Fresco 的内存占用比 Glide 要多一点点的。这块优化内容下面我会说的,不要急。

下面是我在学习 Fresco 内存优化中的一些收获,内容比较杂,大家看看,也许能消除大家一些疑惑。

android 中内存,优化的一些知识看这里:

根据 android 的内存特性( android 对于 bitmap 的优化 ),Fresco 的内存管理是分上下2部分的,分割点就在于 5.0 系统对于 bitmap 的优化

  • 5.0 之前:
    android 5.0 之前,Fresco 把 Bitmap缓存放到 ashmem(匿名共享内存),ashmem 的内存空间在 app 进程虚拟机之外,不占用 app 虚拟机的内存简直是太爽啦。不仅这样,GC 对于Bitmap对象的创建和释放是有优化的,更少的GC会使你的APP运行得更加流畅。早先 ashmem 是通过 BitmapFactory.Optinons.inPurgeable = true 这个标签来标记的,但是这个 inPurgeable 标签有个很大的问题,就是在复用 ashmem 中的 Bitmap 空间时,对 Bitmap 的 decode 解码的工作是在 UI 线程执行的,可能会卡掉帧。随意 google 有出了一个新的标记 inBitmap ,但是这个标记直到 API 19 时才完善,很不给力,所以 Fresco 对于 5.0 一下系统的 ashmem 使用另起炉灶,自己实现,效果不错。这里面有很对学问呢,Ashmem 的内容可以看这2篇:
  • 5.0 之后:
    5.0 之后系统 Fresco 就没有使用 ashmem 了,而是跟着 google 自身对于 Bitmap 的优化走了。5.0 之后 Bitmap 缓存直接位于 Java 的 heap 上,Bitmap 会复用堆内存的 Bitmap 对象,理论上 Java 的 heap 上只有当前屏幕正在显示的 Bitmap,不过据说这种优化策略效果也就那么回事(其实还是之前的 inBitmap)。
  • Glide与Fresco缓存机制对比可以看下面的文章:

官方文档的描述:


Snip20180303_4.png

所以呢,Fresco 的内存缓存管理做的还是不错的,View 在离开显示区域时会触动回收bitmap 资源的,这点对比 Glide 监听页面生命周期回收资源的方式,我觉得要好上不少,这种方式才是紧密贴合 5.0 之后 Google 对 bitmap 的优化思路的,java heap 堆内存层面的高度复用。

线程池


Fresco 的线程池是必须要说一下的,下面内容来自官方文档:

Image pipeline 默认有3个线程池:

  • 3个线程用于网络下载
  • 2个线程用于磁盘操作: 本地文件的读取,磁盘缓存操作。
  • 2个线程用于CPU相关的操作: 解码,转换,以及后处理等后台操作。

渐进式显示


什么是渐进式显示,之前我也不知道,但是看到效果后就知道了,原来一直在我们身边


顺序式

渐进式

顺序式我们不说,这里我们来说说渐进式,Fresco 支持渐进式,渐进式除了需要 Fresco 库的支持外,最总要的图片是要按照渐进式编码做的,这点网上有很多,大伙搜索一下,我看到的是 渐进式需要 SRGB 编码。

我们可以在 Fresco 初始化时操作的 ImagePipelineConfig 类中,设置自定义的渐进式解码类,也可以使用默认的 SimpleProgressiveJpegConfig

ProgressiveJpegConfig pjpegConfig = 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);
  }
}

ImagePipelineConfig config = ImagePipelineConfig.newBuilder()
    .setProgressiveJpegConfig(pjpeg)
    .build();

文档提示我们,必须显式地在加载时设置,才允许渐进式JPEG图片加载

Uri uri;
ImageRequest request = ImageRequestBuilder
    .newBuilderWithSource(uri)
    .setProgressiveRenderingEnabled(true)
    .build();
PipelineDraweeController controller = Fresco.newControllerBuilder()
    .setImageRequest(requests)
    .setOldController(mSimpleDraweeView.getController())
    .build();

mSimpleDraweeView.setController(controller);

资料来源:

渐进式编码资料:

Fresco API 深入使用


自古以来,看一个开源库,无非以下几步:

  • 一看:核心性能。一个库毕竟是拿来用的,性能才是第一位的
  • 二看:API 易用度。性能是比上下的,API 易用度是比同水平的,库好用不好用,也是我们选择一个库非常重要的点。性能+易用度 - 决定我们用不用这个库
  • 三看: 框架结构。通过官方文档一般就可以把框架结构看个大概
  • 四看:核心算法。从第三看开始,就是去深入体会,学习这个库了,不了解框架结构,你怎么找出核心算法呢。
  • 五看:源码。做到这一步,并且源码都能看懂的都至少是高级开发了,一般资深开发就都会仔细看源码了。看源码也是一大学问啊。

只有知道了 Fresco 的功能代码结构,我们才好继续深入的使用 Fresco 这个库,一个优秀的库必然是有很多可以深入使用研究的功能和操作的。这里我分析不了源码,没那么高水平,通过官方文档,简单写下功能分层的几个核心类。自己的水平还是差很多啊,要不然至少框架结构的 UML 类图至少是能画出来的。

Fresco 核心功能对象:

  • ImagePipeline
    负责完成加载图片,变换图片操作,我们可见叫他管道
  • ImagePipelineConfig
    用来设置全局配置,在初始化 Fresco 时添加
  • ImageRequest
    图片请求对象,封装有所有请求的参数
  • DataSource
    数据源,本质就是一个 Observer 被观察者对象,我们可以在其身上观察者
  • DraweeHierarchy
    图层管理器,管理 SimpleDraweeView 这个 UI 控件的所有显示设置
  • DraweeController
    Fresco 的流程控制器,可以接受多个请求对象以实现缩略图加载

全局配置

以下列出了所有可配置的选项,当然了一般我们是不会配置这么多选项的


ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
    .setBitmapMemoryCacheParamsSupplier(bitmapCacheParamsSupplier)
    .setCacheKeyFactory(cacheKeyFactory)
    .setDownsampleEnabled(true)
    .setEncodedMemoryCacheParamsSupplier(encodedCacheParamsSupplier)
    .setExecutorSupplier(executorSupplier)
    .setImageCacheStatsTracker(imageCacheStatsTracker)
    .setMainDiskCacheConfig(mainDiskCacheConfig)
    .setMemoryTrimmableRegistry(memoryTrimmableRegistry)
    .setNetworkFetchProducer(networkFetchProducer)
    .setPoolFactory(poolFactory)
    .setProgressiveJpegConfig(progressiveJpegConfig)
    .setRequestListeners(requestListeners)
    .setSmallImageDiskCacheConfig(smallImageDiskCacheConfig)
    .build();
Fresco.initialize(context, config);

DraweeHierarchy

同一个 DraweeHierarchy 不能设置给多个 view, 也就是说 DraweeHierarchy 对象是我们不要去复用他

 GenericDraweeHierarchy draweeHierarchy = new GenericDraweeHierarchyBuilder(getResources())
                .setFadeDuration(300)
                .setPlaceholderImage(R.drawable.icon_place)
                .setFailureImage(R.drawable.icon_error)
                .setRetryImage(R.drawable.icon_retry)
                .build();

mSimpleDraweeView.setHierarchy(draweeHierarchy );

DraweeController

这个不多说了,API 很简单了,每什么特别需要说的点

ImageRequest imageRequest = ImageRequest.fromUri(uri);
imageRequest.setAutoRotateEnabled(true)

DraweeController controller = Fresco.newDraweeControllerBuilder()
    .setUri(uri)
    .setImageRequest(request)
    .setAutoPlayAnimations(true)
    .setTapToRetryEnabled(true)
    .setOldController(mSimpleDraweeView.getController())
    .setControllerListener(listener)
    .build();

mSimpleDraweeView.setController(controller);

缩略图

Fresco 的缩略图支持比 Glide 要差不少,本质是请求2个图片地址

Uri lowResUri, highResUri;
DraweeController controller = Fresco.newDraweeControllerBuilder()
    .setLowResImageRequest(ImageRequest.fromUri(lowResUri))
    .setImageRequest(ImageRequest.fromUri(highResUri))
    .setOldController(mSimpleDraweeView.getController())
    .build();
mSimpleDraweeView.setController(controller);

ImagePipeline 管道的直接使用

ImagePipeline 管道是 Fresco 的 核心,从数据的加载,换粗你的管理,到图片的变化处理都是由 ImagePipeline 来管理的。Fresco 支持我们直接获取 ImagePipeline 对象,那么我们就可以使用 ImagePipeline 来做很多事了。

  1. 判断一个资源是否在缓存中
// 获取管道对象
ImagePipeline imagePipeline = Fresco.getImagePipeline();
Uri uri = Uri.parse("");
// 判断是否在内存中有缓存
boolean inBitmapMemoryCache = imagePipeline.isInBitmapMemoryCache(uri);
可以判断内存,磁盘缓存

可以看到,不光内存缓存,我们还可以判断磁盘是不是缓存过了,而且可以接受多种类型的数据 - Uri ,ImageRequest ,而且考虑到访问磁盘是 IO 操作,提供了同步和异步方法。

  1. 删除某个 URI 的缓存
ImagePipeline imagePipeline = Fresco.getImagePipeline();
Uri uri;
imagePipeline.evictFromMemoryCache(uri);
imagePipeline.evictFromDiskCache(uri);

// combines above two lines
imagePipeline.evictFromCache(uri);

当然你要是清楚全部缓存也是可以的,但是官方不推荐这么做

ImagePipeline imagePipeline = Fresco.getImagePipeline();
imagePipeline.clearMemoryCaches();
imagePipeline.clearDiskCaches();

// combines above two lines
imagePipeline.clearCaches();
  1. 预加载

预加载这东西具体怎么用,在哪用就得看各位看官的需求了,我举个例子,漫画 app 这样的需要显示特大图片的,应该预加载下一页的内容。

预加载到磁盘缓存:

imagePipeline.prefetchToDiskCache(imageRequest, callerContext);

预加载到内存缓存:

imagePipeline.prefetchToBitmapCache(imageRequest, callerContext);

取消预加载:

DataSource<Void> prefetchDataSource = imagePipeline.prefetchTo...;
prefetchDataSource.close();
  1. 获取 bitmap 缓存对象
DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(imageRequest, this);
        dataSource.subscribe(new BaseBitmapDataSubscriber() {

            @Override
            protected void onNewResultImpl(@Nullable Bitmap bitmap) {
                // 处理显示
            }

            @Override
            protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
                // 失败
            }
        }, UiThreadImmediateExecutorService.getInstance());

Fresco 并不能直接返回 Bitmap 对象,只能给我们一个数据源,让我们去注册一个观察者去处理 Bitmap 对象,Bitmap 对象的生命周期不能超出这个方法,这个方法结束后这个 Bitmap 对象就会被回收掉。

dataSource.subscribe 方法的第二个参数是 Fresco 的进程调度器:

  • UI 进程
UiThreadImmediateExecutorService.getInstance()
  • 当前进程
CallerThreadExecutor.getInstance();
  1. 获取 File 缓存对象
 Boolean result = imagePipeline.isInDiskCache(imageRequest).getResult();
        if (result) {
            CacheKey cacheKey = DefaultCacheKeyFactory.getInstance().getEncodedCacheKey(imageRequest, this);
            BinaryResource resource = ImagePipelineFactory.getInstance().getMainFileCache().getResource(cacheKey);
            File file = ((FileBinaryResource) resource).getFile();
        }
0.jpeg

Fresco 非侵入式设计


Fresco 要求我们使用独有的 SimpleDraweeView ,对于我们的工程来说,替换 imageview 为 SimpleDraweeView 是一个大工程。使用一个新的库,但是会大量修改原有的代码这就是侵入行,不能使用 imageview 对我们来说,很多时候都是非常不便的,SimpleDraweeView 毕竟不是 imageview 这种 google 原生的view 组件,一些 android 系统对原生图片组件的优化和功能,SimpleDraweeView 是无法支持的。那么有什么办法解决吗,目前还没有太好的方案,我看了很多文章,现在有3种思路:

Fresco 图片自适应显示


Fresco 的 SimpleDraweeView 不能使用 warpContent ,这是很让人沮丧的一点,在显示大图,长图时这是一个麻烦。目前有2种处理手法:

  • 加监听,图片下载后根据原图长宽比和 view 的宽来计算 view 的高是多少,一般布局中,显示图片的 view 的宽我们是能够确定的
  • 服务器返回图片宽高,然后根据这个数据来设置(主推)

这2中方法还是主推第二种,服务器返回给我们目标图片的宽高,我们计算之后再去设置 imageview 的宽高,这样我们在 imageview 设置图片之前走一次 layout 比 imageview 设置图片之后再去走一次 layout ,在性能上要好不少,这是没有 bitmap 来参与,布局,绘制要快很多。这点大家使用淘宝时应该有体会,有的商品详情里面图很大,很长,这时淘宝会加载一个和目标图片登场的 item 进来。

手段一:加载图片获取宽高后,去修改 view 高度

给 simpleDraweeView 的 DraweeController 添加一个 ControllerListener 监听器。原文地址:Fresco进阶 图片错位 宽高自适应

/** 
    * 通过imageWidth 的宽度,自动适应高度 
    * * @param simpleDraweeView view 
    * * @param imagePath  Uri 
    * * @param imageWidth width 
    */  
   public static void setControllerListener(final SimpleDraweeView simpleDraweeView, String imagePath, final int imageWidth) {  
       final ViewGroup.LayoutParams layoutParams = simpleDraweeView.getLayoutParams();  
       ControllerListener controllerListener = new BaseControllerListener<ImageInfo>() {  
           @Override  
           public void onFinalImageSet(String id, @Nullable ImageInfo imageInfo, @Nullable Animatable anim) {  
               if (imageInfo == null) {  
                   return;  
               }  
               int height = imageInfo.getHeight();  
               int width = imageInfo.getWidth();  
               layoutParams.width = imageWidth;  
               layoutParams.height = (int) ((float) (imageWidth * height) / (float) width);  
               simpleDraweeView.setLayoutParams(layoutParams);  
           }  
  
           @Override  
           public void onIntermediateImageSet(String id, @Nullable ImageInfo imageInfo) {  
               Log.d("TAG", "Intermediate image received");  
           }  
  
           @Override  
           public void onFailure(String id, Throwable throwable) {  
               throwable.printStackTrace();  
           }  
       };  
       DraweeController controller = Fresco.newDraweeControllerBuilder().setControllerListener(controllerListener).setUri(Uri.parse(imagePath)).build();  
       simpleDraweeView.setController(controller);  
   }  

手段二:从服务器获取图片宽高,再修改 view 高度

Uri uri = "file:///mnt/sdcard/MyApp/myfile.jpg";
int width = 50, height = 50;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
    .setResizeOptions(new ResizeOptions(width, height))
    .build();
PipelineDraweeController controller = Fresco.newDraweeControllerBuilder()
    .setOldController(mDraweeView.getController())
    .setImageRequest(request)
    .build();
mSimpleDraweeView.setController(controller);

推荐这种手段,现在各大图片云可以支持返回图片宽高,所以这种做法现在成本也不是很高了,而且还是 Fresco 官方推荐的做法。官方文档:wrap_content的限制

Fresco 官方文档截图

优化加载大图


这部分内容是承接上面 Fresco 图片自适应显示 来说的,上面的部分侧重如何确定加载大图时 view 的宽高,这部分侧重我们如何在加载大图时如何优化显示性能,减少内容消耗,这2部分其实是一块内容

首先最好的办法是我们先加载小兔,缩录图,在显示图片详情的单独页面时再去显示原始的大图。七牛云挺不错,在图片请求中可以带上需要的宽和高

但是要注意,不管服务器能不能返回缩略图,所存储的原图都不应该太大,有时图片太大,甚至都无法下载下来(报504之类的错误)

那么若是只能拿到原图或大图,fresco怎么优化显示,Fresco 默认提供了3种缩小图片的策略:

  • Scaling
    画布操作,通常是由硬件加速的。图片实际大小保持不变,它只不过在绘制时被放大或缩小.使用时需要配置缩放类型fresco:actualImageScaleType,具体类型名与Imageview的ScaleType几乎一样.
  • Resizing
    是一种软件执行的管道操作。它返回一张新的,尺寸不同的图片,也就是说直接改变bitmap的大小,可惜是单独使用时,只支持jpg,当然,结合Downsampling使用时,可以支持除gif以为的所有常见图片,包括webp.
  • Downsampling
    同样是软件实现的管道操作。它不是创建一张新的图片,而是在解码时改变图片的大小。 同样是软件实现的管道操作。它不是创建一张新的图片,而是在解码时改变图片的大小。类似于android中的BitmapFactory在decodefile时的inSampleSize,都是指定一个采样率,默认是关闭的,如果开启,那么需要结合Resizing来使用.

综上,要缩小内存占用,以及减少cpu计算量,减少卡顿,应该是Downsampling结合Resizing来使用.其中Downsampling是在Fresco初始化时开启,而Resizing则是通过构建ImageRequest时通过制定宽高来实现,所以可以定制每一张或每一类图片的宽高. 示例代码如下:


初始化 Fresco

设置图片加载请求数据

还有一种思路,就是学 Glide 呗,每次请求我们按照 view 的 size rezise 呗

public void setImageSrc(final SimpleDraweeView draweeView, Uri uri, int width, int height) {
    ImageRequestBuilder builder = ImageRequestBuilder.newBuilderWithSource(uri);
    if(width > 0 && height > 0){
        builder.setResizeOptions(new ResizeOptions(width, height));
    }
    ImageRequest request = builder.build();
    PipelineDraweeController controller = (PipelineDraweeController) Fresco.newDraweeControllerBuilder()
            .setImageRequest(request)
            .setTapToRetryEnabled(true)
            .setOldController(draweeView.getController())
            .build();
    draweeView.setController(controller);

高斯模糊


高斯模糊是app里设置一些背景效果 常用到的手段.在fresco中,可以通过postprocessor来实现,也可以自己拿到bitmap后将bitmap模糊化后设置到ImageView或SimpleDraweeView(这个不建议,会消除掉SimpleDraweeView的层级结构,变成单纯的ImageView)上.
推荐开源库: BlurPostprocessor

列表滚动时的优化


在列表视图滚动时,不加载图片,等滚动停止后再开始加载图片,提升列表视图的滚动流畅度。

    // 需要暂停网络请求时调用
    public static void pause(){
        Fresco.getImagePipeline().pause();
    }

    // 需要恢复网络请求时调用
    public static void resume(){
        Fresco.getImagePipeline().resume();
    }
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
    super.onScrollStateChanged(recyclerView, newState);
    switch (newState) {
        case RecyclerView.SCROLL_STATE_IDLE://停止滑动
            if (Fresco.getImagePipeline().isPaused())
                Fresco.getImagePipeline().resume();
            break;
        case RecyclerView.SCROLL_STATE_DRAGGING:
            if (preScrollState == RecyclerView.SCROLL_STATE_SETTLING) {
                //触摸滑动不需要加载
                Fresco.getImagePipeline().pause();
            } else {
                //触摸滑动需要加载
                if (Fresco.getImagePipeline().isPaused())
                    Fresco.getImagePipeline().resume();
            }
            break;
        case RecyclerView.SCROLL_STATE_SETTLING://惯性滑动
            Fresco.getImagePipeline().pause();
            break;
    }
    preScrollState = newState;
}

自定义进度图

Fresco 支持自定义进度图,我们需要自己实现一个 Progress 的 Drawable ,核心就是 onLevelChange 这个方法,下面贴一个自定义的进度图

import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;

import com.facebook.drawee.drawable.DrawableUtils;

public class ImageLoadingDrawable extends Drawable {

    private Paint mRingBackgroundPaint;
    private int mRingBackgroundColor;
    // 画圆环的画笔
    private Paint mRingPaint;
    // 圆环颜色
    private int mRingColor;
    // 半径
    private float mRadius;
    // 圆环半径
    private float mRingRadius;
    // 圆环宽度
    private float mStrokeWidth;
    // 圆心x坐标
    private int mXCenter;
    // 圆心y坐标
    private int mYCenter;
    // 总进度
    private int mTotalProgress = 10000;
    // 当前进度
    private int mProgress;

    public ImageLoadingDrawable(){
        initAttrs();
    }

    private void initAttrs() {
        mRadius = 160;
        mStrokeWidth = 4;
        mRingBackgroundColor = 0xFFadadad;
        mRingColor = 0xFF0EB6D2;
        mRingRadius = mRadius + mStrokeWidth / 2;
        initVariable();
    }

    private void initVariable() {
        mRingBackgroundPaint = new Paint();
        mRingBackgroundPaint.setAntiAlias(true);
        mRingBackgroundPaint.setColor(mRingBackgroundColor);
        mRingBackgroundPaint.setStyle(Paint.Style.STROKE);
        mRingBackgroundPaint.setStrokeWidth(mStrokeWidth);

        mRingPaint = new Paint();
        mRingPaint.setAntiAlias(true);
        mRingPaint.setColor(mRingColor);
        mRingPaint.setStyle(Paint.Style.STROKE);
        mRingPaint.setStrokeWidth(mStrokeWidth);
    }

    @Override
    public void draw(Canvas canvas) {
        drawBar(canvas,mTotalProgress,mRingBackgroundPaint);
        drawBar(canvas,mProgress,mRingPaint);
    }

    private void drawBar(Canvas canvas, int level, Paint paint) {
        if (level > 0 ) {
            Rect bound= getBounds();
            mXCenter = bound.centerX();
            mYCenter = bound.centerY();
            RectF oval = new RectF();
            oval.left = (mXCenter - mRingRadius);
            oval.top = (mYCenter - mRingRadius);
            oval.right = mRingRadius * 2 + (mXCenter - mRingRadius);
            oval.bottom = mRingRadius * 2 + (mYCenter - mRingRadius);
            canvas.drawArc(oval, -90, ((float) level / mTotalProgress) * 360, false, paint); //
        }
    }

    @Override
    protected boolean onLevelChange(int level) {
        mProgress = level;
        if(level > 0 && level < 10000) {
            invalidateSelf();
            return true;
        }else {
            return false;
        }
    }

    @Override
    public void setAlpha(int alpha) {
        mRingPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mRingPaint.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        return DrawableUtils.getOpacityFromColor(this.mRingPaint.getColor());
    }
}

使用的话就是设置进 DraweetHierarchy 图层管理器中

image.getHierarchy().setProgressBarImage(new ImageLoadingDrawable());

Fresco 开源扩展库


  • drawee-text-view
    bilibili开源的借助fresco加载图片的 图文混排的 textView
  • BlurPostprocessor
    Fresco 的开源后处理库,包含圆角,圆形,高斯模糊等
  • PhotoDraweeView
    手势操作,放大,缩小,双击放大
  • subsampling-scale-image-view
    它采用的是BitmapRegionDecoder 子编码的方式,分段加载显示超长图,拒绝OOM,而且支持支持支持:双击放大,单击返回,手动放大等,目前只能加载本地,可以下下来用缓存

PhotoDraweeView 手势库说明


这个库有一点得说一下,demo 里的 PhotoDraweeView 宽高给 match 之后就可以了,但我写的时候,发现必须在图片加载之后把图片的宽高更新近去 PhotoDraweeView 才能正常支持手势

       String uri = "res:///" + R.drawable.panda1;

        PipelineDraweeControllerBuilder builder = Fresco.newDraweeControllerBuilder();
        builder.setUri(uri);
        builder.setOldController(photoDraweeView.getController());
        builder.setControllerListener(new BaseControllerListener<ImageInfo>() {
            @Override
            public void onFinalImageSet(String id, @Nullable ImageInfo imageInfo, @Nullable Animatable animatable) {
                photoDraweeView.update(imageInfo.getWidth(), imageInfo.getHeight());
            }
        });

        photoDraweeView.setController(builder.build());

后来看别人的实现,发现 PhotoDraweeView 有专门设置资源的方法,使用 Fresco 的 control 不好用了。

  photoDraweeView.setPhotoUri(Uri.parse(uri));

subsampling-scale-image-view 超长图加载库


这个库我每试出来库描述的那种按页加载的情况,不过看了 heap 堆内存的使用量,的确是要少不少的,加载大图的确比较合适。我测试的时候,1080*11776 的图是一起出来的,我下滑的时候不会再去请求数据了,只是使用 子编码优化超大图的内存占用量,大概可以优化到原图的60%。这个控件有一点不好的是只能加载 file 文件,需要我们自己去监听一下下载完成后,手动获取 disk 磁盘缓存

public File getFile(Uri uri, ImagePipeline imagePipeline) {
        ImageRequest imageRequest = ImageRequest.fromUri(uri);
        CacheKey encodedCacheKey = imagePipeline.getCacheKeyFactory().getEncodedCacheKey(imageRequest, this);

        BinaryResource resource = ImagePipelineFactory.getInstance()
                .getMainFileCache().getResource(encodedCacheKey);
        return ((FileBinaryResource) resource).getFile();
    }

        ImagePipeline pipeline = Fresco.getImagePipeline();

        if (pipeline.isInDiskCacheSync(uri)) {
            photoDraweeView.setImage(ImageSource.uri(getFile(uri, pipeline).getAbsolutePath()));
        } else {
            ImageRequest imageRequest = ImageRequest.fromUri(uri);
            DataSource<CloseableReference<CloseableImage>> dataSource = pipeline.fetchDecodedImage(imageRequest, this);
            dataSource.subscribe(new BaseBitmapDataSubscriber() {
                @Override
                protected void onNewResultImpl(@Nullable Bitmap bitmap) {
                    if (pipeline.isInDiskCacheSync(uri)) {
                        photoDraweeView.setImage(ImageSource.uri(getFile(uri, pipeline).getAbsolutePath()));
                    }
                }

                @Override
                protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {

                }
            }, UiThreadImmediateExecutorService.getInstance());
        }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容