Image PipeLine指南

对于魏则西 事件。。。。。

原文地址:http://www.fresco-cn.org/docs/configure-image-pipeline.html#_

Image Pipeline介绍编辑和纠错

Image pipeline 负责完成加载图像,变成Android设备可呈现的形式所要做的每个事情。

大致流程如下:

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

既然本身就是一个图片加载组件,那么一图胜千言。

Image Pipeline Diagram

上图中,disk cache实际包含了未解码的内存缓存在内,统一在一起只是为了逻辑稍微清楚一些。关于缓存,更多细节可以参考这里

Image pipeline 可以从本地文件加载文件,也可以从网络。支持PNG,GIF,WebP, JPEG。

各个Android系统的WebP适配

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

配置Image Pipeline编辑和纠错

对于大多数的应用,Fresco的初始化,只需要以下一句代码:

Fresco.initialize(context);

对于那些需要更多进一步配置的应用,我们提供了ImagePipelineConfig

以下是一个示例配置,列出了所有可配置的选项。几乎没有应用是需要以下这所有的配置的,列出来仅仅是为了作为参考。

ImagePipelineConfig config = ImagePipelineConfig.newBuilder()
    .setBitmapMemoryCacheParamsSupplier(bitmapCacheParamsSupplier)
    .setCacheKeyFactory(cacheKeyFactory)
    .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! 否则仍旧是默认配置。

关于Supplier

许多配置的Builder都接受一个Supplier 类型的参数而不是一个配置的实例。

创建时也许有一些麻烦,但这带来更多的利好:这允许在运行时改变创建行为。以内存缓存为例,每隔5分钟就可检查一下Supplier,根据实际情况返回不同类型。

如果你需要动态改变参数,那就是用Supplier每次都返回同一个对象。

Supplier<X> xSupplier = new Supplier<X>() {
  public X get() {
    return new X(xparam1, xparam2...);
  }
);
// when creating image pipeline
.setXSupplier(xSupplier);

线程池

Image pipeline 默认有3个线程池:

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

对于网络下载,你可以定制网络层的操作,具体参考:自定义网络层加载.

对于其他操作,如果要改变他们的行为,传入一个ExecutorSupplier即可。

内存缓存的配置

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

配置磁盘缓存

你可使用Builder模式创建一个 DiskCacheConfig:

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

// when building ImagePipelineConfig
.setMainDiskCacheConfig(diskCacheConfig)

缓存统计

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

缓存编辑和纠错

三级缓存

1. Bitmap缓存

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

在5.0以下系统,Bitmap缓存位于ashmem,这样Bitmap对象的创建和释放将不会引发GC,更少的GC会使你的APP运行得更加流畅。

5.0及其以上系统,相比之下,内存管理有了很大改进,所以Bitmap缓存直接位于Java的heap上。

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

2. 未解码图片的内存缓存

这个缓存存储的是原始压缩格式的图片。从该缓存取到的图片在使用之前,需要先进行解码。

如果有调整大小,旋转,或者WebP编码转换工作需要完成,这些工作会在解码之前进行。

APP在后台时,这个缓存同样会被清空。

3. 文件缓存

和未解码的内存缓存相似,文件缓存存储的是未解码的原始压缩格式的图片,在使用之前同样需要经过解码等处理。

和内存缓存不一样,APP在后台时,内容是不会被清空的。即使关机也不会。用户可以随时用系统的设置菜单中进行清空缓存操作。

查找一个bitmap是否被缓存?

你可以使用ImagePipeline(http://frescolib.org/javadoc/reference/com/facebook/imagepipeline/core/ImagePipeline.html)检查bitmap是否在缓存中。

查询bitmap是否在内存缓存中的操作是同步的。 java ImagePipeline imagePipeline = Fresco.getImagePipeline(); Uri uri; boolean inMemoryCache = imagePipeline.isInBitmapMemoryCache(uri);

查询bitmap是否在文件缓存中的操作时异步的。因为这个操作可以使用另一个线程操作。你可以这样使用。 java 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。如果你自定义,你可能需要用把ImageRequest作为参数的方程,即imagePipeline.isInDiskCache(ImageRequest)imagePipeline.isInBitmapMemoryCache(ImageRequest)

清除缓存中的一条url

ImagePipeline现有函数可以删除缓存中的一条url。

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

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

如同上面一样,evictFromDiskCache(Uri)假定你使用的是默认的CacheKeyFactory。如果你自定义,请使用evictFromDiskCache(ImageRequest)

清除缓存

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

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

用一个文件还是两个文件缓存?

如果要使用2个缓存,在配置image pipeline 时调用 setMainDiskCacheConfig 和setSmallImageDiskCacheConfig 方法即可。

大部分的应用有一个文件缓存就够了,但是在一些情况下,你可能需要两个缓存。比如你也许想把小文件放在一个缓存中,大文件放在另外一个文件中,这样小文件就不会因大文件的频繁变动而被从缓存中移除。

至于什么是小文件,这个由应用来区分,在创建image request, 设置 ImageType 即可:

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

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

内存用量的缩减

在 配置Image pipeline 时,我们可以指定每个缓存最大的内存用量。但是有时我们可能会想缩小内存用量。比如应用中有其他数据需要占用内存,不得不把图片缓存清除或者减小 或者我们想检查看看手机是否已经内存不够了。

Fresco的缓存实现了DiskTrimmable 或者 MemoryTrimmable 接口。这两个接口负责从各自的缓存中移除内容。

在应用中,可以给Image pipeline配置上实现了DiskTrimmableRegistry 和MemoryTrimmableRegistry 接口的对象。

实现了这两个接口的对象保持着一个列表,列表中的各个元素在内存不够时,缩减各自的内存用量。

直接使用Image Pipeline编辑和纠错

本页介绍Image pipeline的高级用法,大部分的应用使用Drawees 和image pipeline打交道就好了。

直接使用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);

关于如果接收数据,请参考数据源 章节。

忽略解码

如果你不保持图片原始格式,不执行解码,使用fetchEncodedImage即可:

DataSource<CloseableReference<PooledByteBuffer>> 
    dataSource = imagePipeline.fetchEncodedImage(imageRequest);
从Bitmap缓存中立刻取到结果

不像其他缓存,如果图片在内存缓存中有的话,可以在UI线程立刻拿到结果。

DataSource<CloseableReference<CloseableImage>> dataSource =
    mImagePipeline.fetchImageFromBitmapCache(imageRequest);
CloseableReference<CloseableImage> imageReference;
try {
  imageReference = dataSource.getResult();
  if (imageReference != null) {
    CloseableImage image = imageReference.get();
    // do something with the image
  }
} finally {
  dataSource.close();
  CloseableReference.closeSafely(imageReference);
}

千万 不要 省略掉 finally 中的代码!

预加载图片

预加载图片可减少用户等待的时间,如果预加载的图片用户没有真正呈现给用户,那么就浪费了用户的流量,电量,内存等资源了。大多数应用,并不需要预加载。

Image pipeline 提供两种预加载方式。

预加载到文件缓存:

imagePipeline.prefetchToDiskCache(imageRequest);

预加载到内存缓存:

imagePipeline.prefetchToBitmapCache(imageRequest);

数据源和数据订阅者编辑和纠错

数据源 和 Future, 有些相似,都是异步计算的结果。

不同点在于,数据源对于一个调用会返回一系列结果,Future只返回一个。

提交一个Image request之后,Image pipeline返回一个数据源。从中获取数据需要使用数据订阅者(DataSubscriber).

当你仅仅需要Bitmap

如果你请求Image pipeline仅仅是为了获取一个 Bitmap, 对象。你可以利用简单易用的BaseBitmapDataSubscriber:

dataSource.subscribe(new BaseBitmapDataSubscriber() {
    @Override
    public void onNewResultImpl(@Nullable Bitmap bitmap) {
       // You can use the bitmap in only limited ways
      // No need to do any cleanup.
    }

    @Override
    public void onFailureImpl(DataSource dataSource) {
      // No cleanup required here.
    }
  });

看起来很简单,对吧。下面是一些小警告:

千万 不要 把bitmap复制给onNewResultImpl函数范围之外的任何变量。订阅者执行完操作之后,image pipeline 会回收这个bitmap,释放内存。在这个函数范围内再次使用这个Bitmap对象进行绘制将会导致IllegalStateException

通用的解决方案

如果你就是想维持对这个Bitmap对象的引用,你不能维持纯Bitmap对象的引用,可以利用可关闭的引用(closeable references) 和 BaseDataSubscriber:

DataSubscriber dataSubscriber =
    new BaseDataSubscriber<CloseableReference<CloseableImage>>() {
  @Override
  public void onNewResultImpl(
      DataSource<CloseableReference<CloseableImage>> dataSource) {

    if (!dataSource.isFinished()) {
      FLog.v("Not yet finished - this is just another progressive scan.");
    }  

    CloseableReference<CloseableImage> imageReference = dataSource.getResult();
    if (imageReference != null) {
      try {
        CloseableImage image = imageReference.get();
        // do something with the image
      } finally {
        imageReference.close();
      }
    }
  }
  @Override
  public void onFailureImpl(DataSource dataSource) {
    Throwable throwable = dataSource.getFailureCause();
    // handle failure
  }
};

dataSource.subscribe(dataSubscriber, executor);

这样,只要遵守可关闭的引用使用规则,你就可以把这个CloseableReference复制给其他变量了。

可关闭的引用编辑和纠错

本页内容仅为高级使用作参考

大部分的应用,直接使用Drawees就好了,不用考虑关闭的事情了。

Java带有垃圾收集功能,许多开发者习惯于不自觉地创建一大堆乱七八糟的对象,并且想当然地认为他们会从内存中想当然地消失。

在5.0系统之前,这样的做法对于操作Bitmap是极其糟糕的。Bitmap占用了大量的内存,大量的内存申请和释放引发频繁的GC,使得界面卡顿不已。

Bitmap 是Java中为数不多的能让Java开发者想念或者羡慕C++以及C++众多的指针库,比如Boost 的东西。

Fresco的解决方案是: 可关闭的引用(CloseableReference)

为了正确地使用它,请按以下步骤进行操作:

1. 调用者拥有这个引用

我们创建一个引用,但我们传递给了一个调用者,调用者将持有这个引用。

CloseableReference<Val> foo() {
  Val val;
  return CloseableReference.of(val);
}
2. 持有者在离开作用域之前,需要关闭引用

创建了一个引用,但是没有传递给其他调用者,在结束时,需要关闭。

void gee() {
  CloseableReference<Val> ref = foo();
  try {
    haa(ref);
  } finally {
    ref.close();
  }
}

finally 中最适合做此类事情了。

3. 除了引用的持有者,闲杂人等不得关闭引用

作为一个参数传递,调用者持有这个引用,在下面的函数体中,不能关闭引用。

void haa(CloseableReference<?> ref) {
  Log.println("Haa: " + ref.get());
}

如果调用了 .close(), 调用者尝试调用 .get()时,会抛出IllegalStateException

4. 在赋值给变量前,先进行clone

在类中使用:

class MyClass {
  CloseableReference<Val> myValRef;

  void mmm(CloseableReference<Val> ref) {
    myValRef = ref.clone();
  };
  // caller can now safely close its copy as we made our own clone.

  void close() {
    CloseableReference.closeSafely(myValRef);
  }
}
// MyClass的调用者需要关闭myValRef

在内部中使用:

void haa(CloseableReference<?> ref) {
  final CloseableReference<?> refClone = ref.clone();
  executor.submit(new Runnable() {
    public void run() {
      try {
        Log.println("Haa Async: " + refClone.get());
      } finally {
        refClone.close();
      }
    }
  });
  // 当前函数域内可安全关闭,闭包内为已经clone过的引用。
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值