Fresco初识

本文详细介绍了Android图像库Fresco的使用,包括添加依赖、初始化、设置图片加载逻辑,以及认识Fresco的Drawees和Image Pipeline两大部分。文章强调了Fresco在图片管理中的内存优化,如自动缓存、释放等,并讨论了自定义View时的注意事项,如处理attach/detach事件、触摸事件等。此外,还深入解析了Image Pipeline的工作流程和缓存策略,包括三级缓存的管理。文章最后提到了一些使用Fresco时的常见问题和陷阱,帮助开发者避免潜在问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下内容均来自Fresco中文网站,网址为http://fresco-cn.org/
本文仅用于个人整理笔记,如涉及侵权,请原作者联系我。

一、使用Fresco

  1. 添加依赖
dependencies {
  compile 'com.facebook.fresco:fresco:0.12.0'
  compile 'com.facebook.fresco:imagepipeline-okhttp3:0.12.0'
  // 在 API < 14 上的机器支持 WebP 时,需要添加
  compile 'com.facebook.fresco:animated-base-support:0.12.0'

  // 支持 GIF 动图,需要添加
  compile 'com.facebook.fresco:animated-gif:0.12.0'

  // 支持 WebP (静态图+动图),需要添加
  compile 'com.facebook.fresco:animated-webp:0.12.0'
  compile 'com.facebook.fresco:webpsupport:0.12.0'

  // 仅支持 WebP 静态图,需要添加
  compile 'com.facebook.fresco:webpsupport:0.12.0'
}

2.初始化Fresco

在Application中调用Fresco.initialize方法即可。

3.在清单文件中使用指定的Application,并声明网络请求的权限。
4.写布局文件。

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    //注意:在布局文件中我们需要添加命名空间:
    xmlns:fresco="http://schemas.android.com/apk/res-auto"
    android:layout_height="match_parent"
    android:layout_width="match_parent">

  //引用Fresco的专用控件:)
  <com.facebook.drawee.view.SimpleDraweeView
    android:id="@+id/my_image_view"
    android:layout_width="130dp"
    android:layout_height="130dp"
    fresco:placeholderImage="@drawable/my_drawable"
  />  

注意:SimpleDraweeView不可以使用wrap_content设置宽高属性

5.Java代码中添加加载图片逻辑

简单的显示一张图片,只需要设置图片加载路径即可。

Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/logo.png");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);

这样我们就可以简单的显示一张从网络上加载的图片了。在这个过程中,Fresco还替我们完成了下面的事情:
①下载图片
②缓存 图片
③图片不再显示时,从内存中移除
④显示占位图知道图片加载完成


二、认识Fresco

Fresco包含Drawees、The Image Pipeline两个部分。我们先了解一下他们。
1.Drawees

Drawees负责图片的呈现,由三个元素组成,分别是DraweeView、DraweeHierarchy和DraweeController。下面用一张图来表示三者之间的关系。

这里写图片描述

(1) 在布局文件中使用Drawees

 <com.facebook.drawee.view.SimpleDraweeView
  android:id="@+id/my_image_view"
  android:layout_width="20dp"
  android:layout_height="20dp"
  //图片加载渐进的时长
  fresco:fadeDuration="300"
  //图片的缩放模式
  fresco:actualImageScaleType="focusCrop"
  //占位图
  fresco:placeholderImage="@color/wait_color"
  //占位图的缩放模式
  fresco:placeholderImageScaleType="fitCenter"
  //加载失败时显示的图片
  fresco:failureImage="@drawable/error"
  //加载失败时图片的缩放模式
  fresco:failureImageScaleType="centerInside"
  //重试图片
  fresco:retryImage="@drawable/retrying"
  //重试图片的缩放模式
  fresco:retryImageScaleType="centerCrop"
  //进度条图片
  fresco:progressBarImage="@drawable/progress_bar"
  //进度条图片缩放模式
  fresco:progressBarImageScaleType="centerInside"
  //progressBar自动旋转时长,这个属性在Java代码中设置不起作用,原因不清楚
  fresco:progressBarAutoRotateInterval="1000"
  //背景图片
  fresco:backgroundImage="@color/blue"
  //叠加图
  fresco:overlayImage="@drawable/watermark"
  //按压状态下的叠加图
  fresco:pressedStateOverlayImage="@color/red"
  //是否显示为圆型图片
  fresco:roundAsCircle="false"
  //圆角矩形的角度
  fresco:roundedCornerRadius="1dp"
  //左上角是否为圆角
  fresco:roundTopLeft="true"
  //右上角是否为圆角
  fresco:roundTopRight="false"
  //左下角是否为圆角
  fresco:roundBottomLeft="false"
  //右下角是否为圆角
  fresco:roundBottomRight="true"
  //设置圆角时为背景设置指定的颜色    
  fresco:roundWithOverlayColor="@color/corner_color"
  //圆角边框宽度
  fresco:roundingBorderWidth="2dp"
  //圆角边框颜色
  fresco:roundingBorderColor="@color/border_color"
/>

① 关于宽高属性值的说明

★ Fresco强制设置宽高值,如果没有在xml中声明宽高值,则无法正确加载图像。
★ Drawees不支持wrap_content属性。所下载的图像可能和占位图尺寸不一致,如果设置errorImage或reTryImage的话,这些图的尺寸也可能和加载的图像尺寸不一致,这种情况下,使用wrap_content的话,view将会重新layout,改变大小和位置,从而导致界面跳跃的情况。了解更多,请查看wrap_content的限制
★ 有一种情况下,我们可以任性一下,使用wrap_content,那就是当我们希望显示的图片拥有固定的宽高比时,我们可以在宽高有一个固定的情况下,为另一个设置wrap_content,并设置二者的固定比例,也可以在Java代码中指定显示比例。如:

    ```
    //xml中设置宽高
    <com.facebook.drawee.view.SimpleDraweeView
        android:id="@+id/my_image_view"
        android:layout_width="20dp"
        android:layout_height="wrap_content"
        fresco:viewAspectRatio="1.33"
        <!-- other attributes -->

    ```
    ```
    //在Java代码中设置宽高比
    mSimpleDraweeView.setAspectRatio(1.33f);
    ```

(2) 在Java代码中使用Drawees

设置或更改要显示的图片简单的方法:

mSimpleDraweeView.setImageURI(uri);

如果需要对加载显示的图片做更多的控制和定制,可以使用ControllerBuilder,关于ControllerBuildr后面会说到。

自定义DraweeHierarchy,当xml布局中设置不能满足我们的需要时,可以通过自定义DraweeHierarchy来实现。我们可以创建一个GenericDraweeHierarchyBuilder来设置Drawees的属性,然后将这个builder设置给DraweeView即可。

List<Drawable> backgroundsList;
List<Drawable> overlaysList;
GenericDraweeHierarchyBuilder builder =new GenericDraweeHierarchyBuilder(getResources());
GenericDraweeHierarchy hierarchy = builder
        .setFadeDuration(300)
        .setPlaceholderImage(new MyCustomDrawable())
        .setBackgrounds(backgroundList)
        .setOverlays(overlaysList)
        .build();
mSimpleDraweeView.setHierarchy(hierarchy);

注意,对于同一个view,不可以多次调用setHierarchy,即使这个view是可回收的,因为创建DraweeHierarchy是一个较为耗时的操作,应该被多次利用。如果要改变显示的图片可使用setController或setImageURI。

注意:
当xml布局设置的方式和Java代码中设置的方式同时使用时,默认采用Java代码设置的方式,即xml中设置的属性将不起作用。
两种方式都会在运行时创建一个builder,只是xml方式创建的是一个默认的,而Java代码创建的使我们自定义的。
这两种方式必须使用一种,否则在加载gif时,将会无法显示动画效果
在Java代码中设置progressBarAutoRotateInterval属性不起作用
在Java代码中设置图片缩放形式不要使用setScaleType或scaleType方法来设置,Drawees不支持。
除了需要加载的图像是真正必须设置的属性,其他的图像都是可选设置的。要显示的图像可以来自多个地方。所需加载的图像实际上是DraweeController 的一个属性,而不是 DraweeHierarchy 的属性。
要显示进度条,最简单的办法就是在构建hierarchy时使用ProgressBarDrawable,如.setProgressBarImage(new ProgressBarDrawable()),这样就可以在Drawee底部出现一个深蓝色的矩形进度条。如果想精确显示加载进度,需要重写Drawable.onLevelChange()
⑧ 关于Drawee支持的缩放类型

基本与ImageView支持的缩放类型一样,唯一不支持的缩放类型是matrix。Fresco提供了focusCrop作为补充,通常这个使用效果更佳。focusCrop同centerCrop,但居中点不是中点,而是指定的某个点。centerCrop缩放模式会保持长宽比,放大或缩小图片,填充满显示边界,居中显示,这种缩放模式在通常情况下很有用,但是对于人脸等图片时,一味地居中显示,可能会裁剪掉一些有用的信息。所以,如果可以设置某个点为居中点,可以更好的满足我们的需求。

⑨ 关于对动画的支持

Fresco支持GIF和WebP格式的动画图片。
我们可以通过在创建DraweeController时调用.setAutoPlayAnimations(true)方法来设置动画自动播放,也可以在图片加载成功时控制动画播放。注意:目前还不支持动画的postprocessors处理。

(3)自定义view

有一些时候,DraweeViews并不能满足我们的需求,在展示图片的时候,我们可能还需要展示一些其他的内容,或者支持一些其他的操作。在同一个View里,我们可能会想显示一张或者多张图。
在自定义View中,Fresco 提供了两个类来负责图片的展现:
① DraweeHolder 单图情况下用。

DraweeHolder 是持有 DraweeHierarchy 的一个类,它和 DraweeController 紧密相关。它允许你在自定义View中也能够使用所有Drawee的特性! 你只需要通过 mDraweeHolder.getTopLevelDrawable() 就能够获取Drawable。你需要知道 Android Drawable 需要一些特殊照顾,所以请仔细阅读下面的内容。

② MultiDraweeHolder 多图情况下用。

MultiDraweeHolder 仅仅是 DraweeHolder 的一个列表。

Android 呈现View对象,只有View对象才能得到一些系统事件的通知。DraweeViews处理这些事件通知,高效地管理内存。使用DraweeHolder时,你需要自己实现这几个方法。

① 处理 attach/detach 事件

如果没按照以下步骤实现的话,很可能会引起内存泄露。当图片不再在View上显示时,比如滑动时View滑动到屏幕外,或者不再绘制,图片就不应该再存在在内存中。Drawees 监听这些事情,并负责释放内存。当图片又需要显示时,重新加载。这些在DraweeView中是自动的,但是在自定义View中,需要我们自己去操作,如下:

DraweeHolder mDraweeHolder;
@Override
public void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    mDraweeHolder.onDetach();
}
@Override
public void onStartTemporaryDetach() {
    super.onStartTemporaryDetach();
    mDraweeHolder.onDetach();
}
@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    mDraweeHolder.onAttach();
}
@Override
public void onFinishTemporaryDetach() {
    super.onFinishTemporaryDetach();
    mDraweeHolder.onAttach();
}

在View获取到 attach/detach 时间时通知 Holder 是一件非常重要的事情!如果 Holder 错过了一个attach事件,那么图片很可能不会被正常加载,因为它认为此时不是加载图片的时机。同理如果 Holder 错过了一个detach事件,那么不需要显示的图片依然会被保留在内存中,因为它认为此时图片仍然显示。

② 处理触摸事件
如果你启用了点击重新加载,在自定义View中,需要这样:

@Override
public boolean onTouchEvent(MotionEvent event) {
    return mDraweeHolder.onTouchEvent(event) || super.onTouchEvent(event);
}

③ 自定义onDraw
必须调用:

Drawable drawable = mDraweeHolder.getTopLevelDrawable();
drawable.setBounds(省略);
//省略
drawable.draw(canvas);

否则图片将不会出现。

不要向下转换这个Drawable
不要变换这个Drawable
如果你需要对Canvas做变换,请确认你在 invalidate 的时候正确刷新了图片所在区域。

④ 其他该做的
设置Drawable.Callback

// 当设置Holder时,不要忘记将顶层Drawable的Callback设置为它。
mDraweeHolder =省略
mDraweeHolder.getTopLevelDrawable().setCallback(this);
// 当不使用Holder时,不要忘记将顶层Drawable的Callback设置为null。
mDraweeHolder.getTopLevelDrawable().setCallback(null);
mDraweeHolder = 省略

重写 verifyDrawable:

@Override
protected boolean verifyDrawable(Drawable who) {
    if (who == mDraweeHolder.getTopLevelDrawable()) {
        return true;
    }
    // 对其他Drawable的验证逻辑
}

确保 invalidateDrawable 处理了图片占用的那块区域。如果你在图片显示前对Canvas应用了一些变换逻辑,那么invalidate 的时候需要考虑到这些变换。最简单的就是参考 Android ImageView 的做法.

初始化 View 和 DraweeHolder

① 构造函数
步骤:
★ 重写所有构造函数,共 3 个
★ 在每个构造函数中调用同等签名的父类构造函数,和一个私有的 init 方法。
★ 在 init 方法中执行初始化操作。

即不要在构造函数中用 this 来调用另外一个构造。这样可以保证,不管调用哪个构造,都可以正确地执行初始化流程。holder 在 init 方法中被创建。

② 创建 Holder
如果有可能,只在 View 创建时,创建 Drawees。创建 DraweeHierarchy 开销较大,最好只做一次。

class CustomView extends View {
      DraweeHolder<GenericDraweeHierarchy> mDraweeHolder;

      // constructors following above pattern

      private void init() {
        GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources());
          .set...
          .set...
          .build();
        mDraweeHolder = DraweeHolder.create(hierarchy, context);
      }
    }

③ 设置要显示的图片
使用 controller builder 创建DraweeController,然后调用holder的 setController 方法,而不是设置给自定义 View。

DraweeController controller = Fresco.newControllerBuilder()
        .setUri(uri)
        .setOldController(mDraweeHolder.getController())
        .build();
    mDraweeHolder.setController(controller);

④ MultiDraweeHolder
和 DraweeHolder 相比,MultiDraweeHolder 有 add, remove, clear
等方法可以操作 Drawees。如下:

MultiDraweeHolder<GenericDraweeHierarchy> mMultiDraweeHolder;

    private void init() {
      GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources());
        .set...
        .build();
      mMultiDraweeHolder = new MultiDraweeHolder<GenericDraweeHierarchy>();
      mMultiDraweeHolder.add(new DraweeHolder<GenericDraweeHierarchy>(hierarchy, context));
      // repeat for more hierarchies
    }

同样,也需要处理系统事件,设置声音等等,就像处理单个DraweeHolder那样。

2.The Image Pipeline

Fresco 的 Image Pipeline 负责图片的获取和管理。图片可以来自远程服务器,本地文件,或者Content Provider,本地资源。压缩后的文件缓存在本地存储中,Bitmap数据缓存在内存中。

在5.0系统以下,Image Pipeline 使用 pinned purgeables 将Bitmap数据避开Java堆内存,存在ashmem中。这要求图片不使用时,要显式地释放内存。
SimpleDraweeView自动处理了这个释放过程,所以没有特殊情况,尽量使用SimpleDraweeView,在特殊的场合,如果有需要,也可以直接控制Image Pipeline。

在Android 3.0系统之前,Android是不支持WebP格式的。在4.1.2之前,扩展WebP格式是不支持的。
在Image pipeline的支持下,从2.3之后,都可以使用WebP格式。

① ImagePipeline的工作流程:
★ 先检查内存缓存中是否有图片,如果有,返回该图片;
★ 后台线程开始后续工作;
★ 检查是否在未解码内存缓存中,如果有,解码、变换、返回,然后缓存到内存缓存中;
★ 检查是否在磁盘缓存中,如果有,变换,返回,缓存到未解码缓存和内存缓存中。
★ 从网络或者本地加载,加载完成后,解码、变换、返回,存到各个缓存中。

不要蒙,下面会对Fresco支持的缓存进行介绍:)

② 配置ImagePipeline
★ 初始化Fresco时,我们可能没有主观的配置ImagePipeline,但是Fresco会使用一个默认的ImagePipeline设置。
★ 当我们需要进一步配置应用时,可以使用ImagePipelineConfig来设置。下面列出了ImagePipeline的所有配置选项,我们不需要对所有选项都进行设置。

ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
            .setBitmapMemoryCacheParamsSupplier(bitmapCacheParamsSupplier)
            .setCacheKeyFactory(cacheKeyFactory)
            .setDownsampleEnabled(true)
            .setWebpSupportEnabled(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);

设置完ImagePipelineConfig 后,需要将它传递给Fresco.initialize方法,这样新设置的选项才能被引用。

③ 直接使用ImagePipeline(ImagePIPeline的高级用法)
直接使用Image pipeline意味着要维护图片的内存使用。Drawees会根据各种情况确定图片是否需要在内存缓存中,在需要时加载,在不需要时移除。直接使用的话,你需要自己完成这些逻辑。
Image pipeline返回的是一个CloseableReference对象。在这些对象不需要时,Drawees会调用.close()方法。如果你的应用不使用Drawees,那你需要自己完成这个事情。
Java的GC机制会在Bitmap不使用时,清理掉Bitmap。但要GC时总是太迟了,另外GC是很昂贵的开销。GC大对象也会带来性能问题,尤其是在5.0以下系统。
调用Pipeline 首先需要创建一个image request,然后传递给ImagePipeline。

    ImagePipeline imagePipeline = Fresco.getImagePipeline();
    DataSource<CloseableReference<CloseableImage>>
        dataSource = imagePipeline.fetchDecodedImage(imageRequest, callerContext);

如果想要保持图片的原始格式,不执行解码,可以直接使用fetchEncodedImage。

DataSource<CloseableReference<PooledByteBuffer>>
dataSource = imagePipeline.fetchEncodedImage(imageRequest, callerContext);

如果图片在bitmap内存缓存中,可以直接在UI线程中取出来。

        DataSource<CloseableReference<CloseableImage>> dataSource =
            imagePipeline.fetchImageFromBitmapCache(imageRequest, callerContext);
        try {
          CloseableReference<CloseableImage> imageReference = dataSource.getResult();
          if (imageReference != null) {
            try {
              // Do something with the image, but do not keep the reference to it!
              // The image may get recycled as soon as the reference gets closed below.
              // If you need to keep a reference to the image, read the following sections.
            } finally {
              CloseableReference.closeSafely(imageReference);
            }
          } else {
            // cache miss

          }
        } 
        //千万不要省略finally代码块
        finally {
          dataSource.close();
        }

预加载图片可减少用户等待的时间,如果预加载的图片用户没有真正呈现给用户,那么就浪费了用户的流量,电量,内存等资源了。大多数应用,并不需要预加载。需要注意的是,你可以预加载图片到磁盘、内存缓存中,但是它们在并不会被立即解码(除非马上要显示),这样会节省一部分CPU操作。

        //预加载到磁盘缓存
        imagePipeline.prefetchToDiskCache(imageRequest, callerContext);
        //预加载到内存缓存
        imagePipeline.prefetchToBitmapCache(imageRequest, callerContext);
        //取消预加载
        DataSource<Void> prefetchDataSource = imagePipeline.prefetchTo...;
        prefetchDataSource.close();

取消一个预加载已经完成的数据源是无效的,不过如果你一定要这么做,也不会发生问题。

你会发现所有的ImagePipeline获取时都会加上一个callerContext,它的类型是Object。你可以把他堪称一个上下文设计模式。它主要的目的是为了让ImageRequest能够有更多扩展用途(比如打Log)。这个对象可以被所有ImagePipeline中实现的Producer获取到。

④ Fresco使用的线程池
ImagePipeline默认使用3个线程池:
★ 3个线程用于网络下载;
★ 2个线程用于磁盘操作
★ 2个线程用于CPU相关的操作:解码、转换以及后处理等后台操作。
对于网络下载,我们可以自己决定是否使用三方类库进行网络加载。ImagePipeline默认使用的是HttpURLConnection,我们可以自己设置使用OKhttp三方网络框架。使用OKHttp需要进行一下配置:
For OkHttp2:

        dependencies {
          // your project's other dependencies
          compile "com.facebook.fresco:imagepipeline-okhttp:0.12.0+"
        }

For OkHttp3:

        dependencies {
          // your project's other dependencies
          compile "com.facebook.fresco:imagepipeline-okhttp3:0.12.0+"
        }

配置与OKHttp搭配的ImagePipeline使用的是OkHttpImagePipelineConfigFactory,如下:

        Context context;
        OkHttpClient okHttpClient; // build on your own
        ImagePipelineConfig config = OkHttpImagePipelineConfigFactory
            .newBuilder(context, okHttpClient)
            . // other setters
            . // setNetworkFetcher is already called for you
            .build();
        Fresco.initialize(context, config);

关于session和cookie,传给okHttpClient需要处理服务器的安全校验工作(可以通过interceptor处理)。参考这里可以处理自定义网络库可能发生的cookie相关问题。
在配置ImagePipeline时,把producer传递给Imagepipeline。

ImagePipelineConfig config = ImagePipelineConfig.newBuilder()
    .setNetworkFetcher(myNetworkFetcher);
    . other setters
    .build();
Fresco.initialize(context, config);

对于其他操作,如果要改变其行为,传入一个ExecutorSupplier即可。
ExecutorSupplier接口的实现负责提供被ImagePipeline的不同部分使用的执行者。

⑤ Fresco支持的内存策略
★ APP在运行时会监测系统的内存事件,这件事可以交给Fresco来处理。
★ 最简单的就是重写Activity.onTrimMemory函数或者使用ComponentCallbacks2。
★ 需要实现一个MemoryTrimmableRegistry,它持有一个MemoryTrimmable的集合。Fresco的缓存就在它们中间。当接受到一个系统的内存事件时,可以调用MemoryTrimmable的对应方法来释放资源。

⑥ 配置Fresco的内存缓存
内存缓存和未解码的内存缓存的配置由一个Supplier控制,这个Supplier返回一个MemoryCacheParams 对象用于内存状态控制。

⑦ 配置Fresco的磁盘缓存
可使用Builder模式创建一个 DiskCacheConfig:

    DiskCacheConfig diskCacheConfig = DiskCacheConfig.newBuilder()
       .set....
       .set....
       .build()

    // 配置磁盘缓存
    .setMainDiskCacheConfig(diskCacheConfig)

⑧ 缓存统计
如果你想统计缓存的命中率,你可以实现ImageCacheStatsTracker, 在这个类中,每个缓存事件都有回调通知,基于这些事件,可以实现缓存的计数和统计。

3. 终于到了缓存的介绍了:

(1) 三级缓存
① Bitmap缓存

用于存储bitmap对象,这些对象可以立刻用来显示或用于后处理。

5.0以上系统,较之前有了很大改进,所以bitmap缓存直接位于Java的heap上。

当应用后台运行时,bitmap内存会被清空。

② 未解码图片的内存缓存

用于存储的是原始压缩格式的图片。从该缓存取到的图片在使用之前需要先进行解码。
如果有调整大小、旋转或者WebP编码转换工作需要完成,这些工作会在解码之前进行。

③ 磁盘缓存

用于存储未解码的原始压缩格式的图片, 在使用之前同样需要经过解码等处理。APP后台运行时,该缓存的内容不会被清空,即使关机也不会被清空,用户可以随时用系统的设置菜单中进行清空缓存操作。

(2)查找一个bitmap是否被缓存
使用ImagePipeline检查bitmap是否在bitmap内存缓存中。

    ImagePipeline imagePipeline = Fresco.getImagePipeline();
    Uri uri;
    boolean inMemoryCache = imagePipeline.isInBitmapMemoryCache(uri);

查询内存缓存的操作是同步的,查询磁盘缓存的操作是异步的。因为查询磁盘缓存的操作可以使用另一个线程操作。

    DataSource<Boolean> inDiskCacheSource = imagePipeline.isInDiskCache(uri);
    DataSubscriber<Boolean> subscriber = new BaseDataSubscriber<Boolean>() {
        @Override
        protected void onNewResultImpl(DataSource<Boolean> dataSource) {
          if (!dataSource.isFinished()) {
            return;
          }
          boolean isInCache = dataSource.getResult();
          // your code here
        }
      };
    inDiskCacheSource.subscribe(subscriber, executor);

注意:上述API的前提是使用默认的CacheKeyFactory,如果使用自定义的CacheKeyFactory,可能需要传递ImageRequest参数。

(3)清除缓存中的一条url
ImagePipeline现有函数可以根据URI删除缓存。

    ImagePipeline imagePipeline = Fresco.getImagePipeline();
    Uri uri;
    imagePipeline.evictFromMemoryCache(uri);
    imagePipeline.evictFromDiskCache(uri);

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

注意:同上面一样,这个API也是假设使用的默认CacheKeyFactory,如果自定义CacheKeyFactory,需要使用evictFromDiskCache(ImageRequest)。

(4) 清除缓存

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

// combines above two lines
imagePipeline.clearCaches();

(5)用一个还是两个磁盘缓存
如果要使用2个缓存,在配置image pipeline 时调用 setMainDiskCacheConfig 和 setSmallImageDiskCacheConfig 方法即可。
大部分的应用有一个磁盘缓存就够了,但是在一些情况下,你可能需要两个缓存。比如你也许想把小文件放在一个缓存中,大文件放在另外一个文件中,这样小文件就不会因大文件的频繁变动而被从缓存中移除。
至于什么是小文件,这个由应用来区分,在创建image request, 设置 ImageType 即可:

ImageRequest request = ImageRequest.newBuilderWithSourceUri(uri).setImageType(ImageType.SMALL)

如果仅仅需要一个缓存,那么不调用setSmallImageDiskCacheConfig即可。Image pipeline 默认会使用同一个缓存,同时ImageType也会被忽略。

(6)内存用量的缩减
Fresco的缓存实现了DiskTrimmable 或者 MemoryTrimmable 接口。这两个接口负责从各自的缓存中移除内容。
在应用中,可以给Image pipeline配置上实现了DiskTrimmableRegistry 和 MemoryTrimmableRegistry 接口的对象。
实现了这两个接口的对象保持着一个列表,列表中的各个元素在内存不够时,缩减各自的内存用量。

4.数据源与数据订阅者
数据源和Android中的Future有些相似,都是异步计算的结果。不同的是数据源对于一个调用会返回一系列的结果,Future只会返回一个。
提交一个ImageRequest后,ImagePipeline会返回一个结果。从这个结果中获取数据需要使用数据订阅者(DataSubscriber)

数据订阅者(DataSubscriber)是一个接收数据的接口,我们可以使用它来取消获取数据的请求、进行获取数据成功后的操作、进行获取数据失败后的操作以及更新获取数据的进度。

当订阅一个数据源时,必须制定一个执行器(Executor)去执行。它主要是为了让我们在制定的线程调度机制上执行 Runnable。Fresco提供了一些Executors,你在使用它们的时候需要注意:
① 如果想要在回调中进行UI操作,我们需要使用UiThreadImmediateExecutorService.getInstance(),因为Android只允许在UI线程中进行UI操作;
② 如果回调中不涉及UI的简单操作,我们要使用CallerThreadExecutor.getInstance()。这个 Executor 会在调用者线程中执行回调。这个回调的执行线程是得不到保证的,所以需要谨慎使用这个Executor。重申一遍,只有少量工作、没有UI操作的回调才适合在这个Executor中操作。
③ 如果需要做一些复杂、耗时的操作,但不涉及UI(如数据库读写、文件IO),就不可以用上面两个Executor。需要开启一个后台Executor,可以参考DefaultExecutorSupplier.forBackgroundTasks。

从数据源中获取结果
这里展示了从数据源中获取目标类型为T的CloseableReference的例子,注意此时获取结果对象仅仅在onNewResultImpl有使用意义。当回调结束之后,这个对象就不能被使用了!(如果你希望保持这个对象并在外部使用,请继续往下看)

        DataSource<CloseableReference<T>> dataSource = 省略;

            DataSubscriber<CloseableReference<T>> dataSubscriber =
                new BaseDataSubscriber<CloseableReference<T>>() {
                  @Override
                  protected void onNewResultImpl(
                      DataSource<CloseableReference<T>> dataSource) {
                    if (!dataSource.isFinished()) {
                      return;
                    }
                    CloseableReference<T> ref = dataSource.getResult();
                    if (ref != null) {
                      try {
                        T result = ref.get();
                        ...
                      } finally {
                        CloseableReference.closeSafely(ref);
                      }
                    }
                  }

                  @Override
                  protected void onFailureImpl(DataSource<CloseableReference<T>> dataSource) {
                    Throwable t = dataSource.getFailureCause();
                    // handle failure
                  }
                };

            dataSource.subscribe(dataSubscriber, executor);

保持数据源结果的引用
上面的例子中我们在回调执行完就将对象释放了。如果你需要保持这个对象有效,那么你可以不在这里close它,参照如下例子:

        DataSource<CloseableReference<T>> dataSource =省略;

            DataSubscriber<CloseableReference<T>> dataSubscriber =
                new BaseDataSubscriber<CloseableReference<T>>() {
                  @Override
                  protected void onNewResultImpl(
                      DataSource<CloseableReference<T>> dataSource) {
                    if (!dataSource.isFinished()) {
                      return;
                    }
                    mRef = dataSource.getResult();
                    T result = mRef.get();
                    省略
                  }

                  @Override
                  protected void onFailureImpl(DataSource<CloseableReference<T>> dataSource) {
                    Throwable t = dataSource.getFailureCause();
                    // handle failure
                  }
                };

            dataSource.subscribe(dataSubscriber, executor);

注意:CloseableReference对象必须在使用完之后对其回收,否则会造成内存泄漏!

        CloseableReference.closeSafely(mRef);
        mRef = null;

获取未解码的图片

        DataSource<CloseableReference<PooledByteBuffer>> dataSource =
                mImagePipeline.fetchEncodedImage(imageRequest, CALLER_CONTEXT);

Image pipeline 使用 PooledByteBuffer 来存储未解码的图片。 此处我们继续使用上面的目标类型 T来举个例子, 通过创建InputStream 来读取图片字节流:

        InputStream is = new PooledByteBufferInputStream(result);
              try {
                ImageFormat imageFormat = ImageFormatChecker.getImageFormat(is);
                Files.copy(is, path);
              } catch (省略) {
                省略
              } finally {
                Closeables.closeQuietly(is);
              }

获取已解码的图片

DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(imageRequest, callerContext);

Image pipeline CloseableImage 来承载已解码的图片。下面例子描述如何从CloseableImage拿出一个Bitmap对象:

        CloseableImage image = ref.get();
            if (image instanceof CloseableBitmap) {
              // do something with the bitmap
              Bitmap bitmap = (CloseableBitmap image).getUnderlyingBitmap();
              省略
            }

如果向ImagePipeline请求一个Bitmap, 可以使用 BaseBitmapDataSubscriber:

    dataSource.subscribe(new BaseBitmapDataSubscriber() {
        @Override
        public void onNewResultImpl(@Nullable Bitmap bitmap) {
          // 你可以直接在这里使用Bitmap,没有别的限制要求,也不需要回收
        }

        @Override
        public void onFailureImpl(DataSource dataSource) {
        }
      },
      executor);

注意,这里有一些要求!
★ 这个数据源无法用来获取动图。
★ 你无法在onNewResultImpl之外的地方使用这个Bitmap。原因是BaseBitmapDataSubscriber数据源的获取结束之后,image pipeline就会回收这个Bitmap。如果你此时再用它来显示,会报IllegalStateException!
★ 当然你可以将它传给Android的通知栏或者[RemoveView]remote view。Android系统会在共享内存区域保存一份Bitmap的拷贝,Fresco的回收不会影响它。
如果这些要求导致你不能使用BaseBitmapDataSubscriber,你可以使用上述的其他数据源。

5.Webp支持
Android 系统在 4.0 版本中添加入了 WebP 的支持,并在 4.2.1 版本中加强了它:
4.0+ (Ice Cream Sandwich): 基础的 WebP 支持
4.2.1+ (Jelly Beam MR1): 支持带透明度与无损的 WebP
Fresco 默认使用系统的 WebP 方案来加载它。
但同时,Fresco 能够让你在更老的版本中使用 WebP,所以如果你想要在 Android 2.3 版本的设备上使用 WebP, 你需要做的就是在工程中添加一个 webpsupport的依赖:

    dependencies {
      // your app's other dependencies
      compile 'com.facebook.fresco:webpsupport:0.12.0'
    }

###三、常见问题汇总
(1) 重复的边界。设置图片显示为圆角,Fresco在处理方式上还存在缺陷。

当使用BITMAP_ONLY(默认)模式时的限制:
▲并非所有的图片分支部分都可以实现圆角,目前只有占位图片和实际图片可以实现圆角,我们正在努力为背景图片实现圆角功能。
▲只有BitmapDrawable 和 ColorDrawable类的图片可以实现圆角。我们目前不支持包括NinePatchDrawable和 ShapeDrawable在内的其他类型图片。(无论他们是在XML或是程序中声明的)
▲动画不能被圆角。
▲由于Android的BitmapShader的限制,当一个图片不能覆盖全部的View的时候,边缘部分会被重复显示,而非留白。对这种情况可以使用不同的缩放类型(比如centerCrop)来保证图片覆盖了全部的View。
▲OVERLAY_COLOR模式没有上述限制,但由于这个模式使用在图片上覆盖一个纯色图层的方式来模拟圆角效果,因此只有在图标背景是静止的并且与图层同色的情况下才能获得较好的效果。
▲Drawee 内部实现了一个CLIPPING模式。但由于有些Canvas的实现并不支持路径剪裁(Path Clipping),这个模式被禁用了且不对外开放。并且由于路径剪裁不支持反锯齿,会导致圆角的边缘呈现像素化的效果。
总之,如果生成临时bitmap的方法,所有的上述问题都可以避免。但是这个方法并不被支持因为这会导致很严重的内存问题。
综上所述,在 Android 中实现圆角效果,没有一个绝对好的方案,你必须在上述的方案中进行选择。

(2)图片没有加载
可以从 image pipeline 打出的日志来查看原因。这里提供一些通常会导致问题的原因:
文件不可用。无效的路径、链接会导致这种情况。
★ 判断网络链接是否有效,你可以尝试在浏览器中打开它,看看是否图片会被加载。若图片依然加载不出来,那么这不是Fresco的问题。
★ 判断本地文件是否有效,你可以通过下面这段代码来校验:

FileInputStream fis = new FileInputStream(new File(localUri.getPath()));如果这里抛出了异常,那么这不是Fresco的问题,可能是你的其他代码导致的。有可能是没有获取到SD卡读取权限、路径不合法、文件不存在等。
OOM 。加载特别特别大的图片时最容易导致这种情况。如果你加载的图片比承载的View明显大出太多,那你应该考虑将它Resize一下。
Bitmap太大导致无法绘制。Android 无法绘制长或宽大于2048像素的图片。这是由OpenGL渲染系统限制的,如果它超过了这个界限,Fresco会对它进行Resize。

四、通过Logcat来判断原因

在加载图片时会出现各种各样的原因导致加载失败。 在使用Fresco的时候,最直接的方式就是查看 image pipeline 打出的VERBOSE级别日志。

启动日志。默认情况下Fresco是关闭日志输出的,你可以配置image pipeline让它启动.

    Set<RequestListener> requestListeners = new HashSet<>();
    requestListeners.add(new RequestLoggingListener());
    ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
       // other setters
       .setRequestListeners(requestListeners)
       .build();
    Fresco.initialize(context, config);
    FLog.setMinimumLoggingLevel(FLog.VERBOSE);

查看日志
你可以通过下面这条shell命令来查看Fresco日志:

adb logcat -v threadtime | grep -iE 'LoggingListener|AbstractDraweeController|BufferedDiskCache'

它的输出为如下格式:

    08-12 09:11:14.791 6690 6690 V unknown:AbstractDraweeController: controller 28ebe0eb 0 -> 1: initialize
    08-12 09:11:14.791 6690 6690 V unknown:AbstractDraweeController: controller 28ebe0eb 1: onDetach
    08-12 09:11:14.791 6690 6690 V unknown:AbstractDraweeController: controller 28ebe0eb 1: setHierarchy: null
    08-12 09:11:14.791 6690 6690 V unknown:AbstractDraweeController: controller 28ebe0eb 1: setHierarchy: com.facebook.drawee.generic.GenericDraweeHierarchy@2bb88e4
    08-12 09:11:14.791 6690 6690 V unknown:AbstractDraweeController: controller 28ebe0eb 1: onAttach: request needs submit
    08-12 09:11:14.791 6690 6690 V unknown:PipelineDraweeController: controller 28ebe0eb: getDataSource
    08-12 09:11:14.791 6690 6690 V unknown:RequestLoggingListener: time 11201791: onRequestSubmit: {requestId: 1, callerContext: null, isPrefetch: false}
    08-12 09:11:14.792 6690 6690 V unknown:RequestLoggingListener: time 11201791: onProducerStart: {requestId: 1, producer: BitmapMemoryCacheGetProducer}
    08-12 09:11:14.792 6690 6690 V unknown:RequestLoggingListener: time 11201792: onProducerFinishWithSuccess: {requestId: 1, producer: BitmapMemoryCacheGetProducer, elapsedTime: 1 ms, extraMap: {cached_value_found=false}}
    08-12 09:11:14.792 6690 6690 V unknown:RequestLoggingListener: time 11201792: onProducerStart: {requestId: 1, producer: BackgroundThreadHandoffProducer}
    08-12 09:11:14.792 6690 6690 V unknown:AbstractDraweeController: controller 28ebe0eb 1: submitRequest: dataSource: 36e95857
    08-12 09:11:14.792 6690 6734 V unknown:RequestLoggingListener: time 11201792: onProducerFinishWithSuccess: {requestId: 1, producer: BackgroundThreadHandoffProducer, elapsedTime: 0 ms, extraMap: null}
    08-12 09:11:14.792 6690 6734 V unknown:RequestLoggingListener: time 11201792: onProducerStart: {requestId: 1, producer: BitmapMemoryCacheProducer}
    08-12 09:11:14.792 6690 6734 V unknown:RequestLoggingListener: time 11201792: onProducerFinishWithSuccess: {requestId: 1, producer: BitmapMemoryCacheProducer, elapsedTime: 0 ms, extraMap: {cached_value_found=false}}
    08-12 09:11:14.792 6690 6734 V unknown:RequestLoggingListener: time 11201792: onProducerStart: {requestId: 1, producer: EncodedMemoryCacheProducer}
    08-12 09:11:14.792 6690 6734 V unknown:RequestLoggingListener: time 11201792: onProducerFinishWithSuccess: {requestId: 1, producer: EncodedMemoryCacheProducer, elapsedTime: 0 ms, extraMap: {cached_value_found=false}}
    08-12 09:11:14.792 6690 6734 V unknown:RequestLoggingListener: time 11201792: onProducerStart: {requestId: 1, producer: DiskCacheProducer}
    08-12 09:11:14.792 6690 6735 V unknown:BufferedDiskCache: Did not find image for http://www.example.com/image.jpg in staging area
    08-12 09:11:14.793 6690 6735 V unknown:BufferedDiskCache: Disk cache read for http://www.example.com/image.jpg
    08-12 09:11:14.793 6690 6735 V unknown:BufferedDiskCache: Disk cache miss for http://www.example.com/image.jpg
    08-12 09:11:14.793 6690 6735 V unknown:RequestLoggingListener: time 11201793: onProducerFinishWithSuccess: {requestId: 1, producer: DiskCacheProducer, elapsedTime: 1 ms, extraMap: {cached_value_found=false}}
    08-12 09:11:14.793 6690 6735 V unknown:RequestLoggingListener: time 11201793: onProducerStart: {requestId: 1, producer: NetworkFetchProducer}
    08-12 09:11:15.161 6690 7358 V unknown:RequestLoggingListener: time 11202161: onProducerFinishWithSuccess: {requestId: 1, producer: NetworkFetchProducer, elapsedTime: 368 ms, extraMap: null}
    08-12 09:11:15.162 6690 6742 V unknown:BufferedDiskCache: About to write to disk-cache for key http://www.example.com/image.jpg
    08-12 09:11:15.162 6690 6734 V unknown:RequestLoggingListener: time 11202162: onProducerStart: {requestId: 1, producer: DecodeProducer}
    08-12 09:11:15.163 6690 6742 V unknown:BufferedDiskCache: Successful disk-cache write for key http://www.example.com/image.jpg
    08-12 09:11:15.169 6690 6734 V unknown:RequestLoggingListener: time 11202169: onProducerFinishWithSuccess: {requestId: 1, producer: DecodeProducer, elapsedTime: 7 ms, extraMap: {hasGoodQuality=true, queueTime=0, bitmapSize=600x400, isFinal=true}}
    08-12 09:11:15.169 6690 6734 V unknown:RequestLoggingListener: time 11202169: onRequestSuccess: {requestId: 1, elapsedTime: 378 ms}
    08-12 09:11:15.184 6690 6690 V unknown:AbstractDraweeController: controller 28ebe0eb 1: set_final_result @ onNewResult: image: CloseableReference 2fd41bb0

在这个示例中,我们发现名为28ebe0eb的 DraweeView 向名为36e95857的 DataSource 进行了图像请求。首先,图片没有在内存缓存中找到,也没有在磁盘缓存中找到,最后去网络上下载图片。下载成功后,图片被解码,之后请求结束。最后数据源通知 controller 图片就绪,显示图片(set_final_result)。

五、一些陷阱

1.不要使用 ScrollViews
如果你想要在一个长的图片列表中滑动,你应该使用 RecyclerView,ListView,或 GridView。这三者都会在你滑动时不断重用子视图。Fresco 的 view 会接收系统事件,使它们能正确管理内存。
ScrollView 不会这样做。因此,Fresco view 不会被告知它们是否在屏幕上显示,并保持图片内存占用直到你的 Fragment 或 Activity 停止。你的 App 将会面临更大的 OOM 风险。
2.不要向下转换
不要试图把Fresco返回的一些对象进行向下转化,这也许会带来一些对象操作上的便利,但是也许在后续的版本中,你会遇到一些因为向下转换特性丢失导致的难以处理的问题。
3.不要使用getTopLevelDrawable
DraweeHierarchy.getTopLevelDrawable() 仅仅 应该在DraweeViews中用,除了自定义View(指Drawees中的自定义view)中,其他应用代码建议连碰都不要碰这个。在自定义view中,也千万不要将返回值向下转换,也许下个版本,我们会更改这个返回值类型。
4.不要复用 DraweeHierarchies
永远不要把 DraweeHierarchy 通过 DraweeView.setHierarchy 设置给不同的View。DraweeHierarchy 是由一系列 Drawable 组成的。在 Android 中, Drawable 不能被多个 View 共享。
5.不要在多个DraweeHierarchy中使用同一个Drawable
原因同上。不过你可以在占位图、重试图、错误图中使用相同的资源ID,Android 实际会创建不同的 Drawable。 如果你使用GenericDraweeHierarchyBuilder,那么需要调用Resources.getDrawable来通过资源获取图片。不过请不要只调用一次然后将结果传给不同的Hierarchy!
6.不要直接控制 hierarchy
不要直接使用 SettableDraweeHierarchy 方法(reset,setImage,…)。它们应该仅由 controller 使用。不要使用setControllerOverlay来设置一个覆盖图,这个方法只能给 controller 调用。如果你需要显示覆盖图,可以参考Drawee的各种效果配置。
7.不要直接给 DraweeView 设置图片
目前 DraweeView 直接继承于 ImageView,因此它有 setImageBitmap,
setImageDrawable 等方法。如果利用这些方法直接设置一张图片,内部的 DraweeHierarchy 就会丢失,也就无法取到image
pipeline 的任何图像了。
8.使用 DraweeView 时,请不要使用任何 ImageView 的属性
在后续的版本中,DraweeView 会直接从 View 派生。任何属于 ImageView 但是不属于 View 的方法都会被移除。
**9.listView使用Fresco加载图片时可能会出现卡顿或图片为纯黑色的问题,这时我们可以使用Fresco提供的resize功能,显示图片的缩略图即可。这里感谢 stepalone大神 提供的解决方案:)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值