对于魏则西 事件。。。。。
原文地址:http://www.fresco-cn.org/docs/configure-image-pipeline.html#_
Image pipeline 负责完成加载图像,变成Android设备可呈现的形式所要做的每个事情。
大致流程如下:
既然本身就是一个图片加载组件,那么一图胜千言。
上图中,disk cache
实际包含了未解码的内存缓存在内,统一在一起只是为了逻辑稍微清楚一些。关于缓存,更多细节可以参考这里。
Image pipeline 可以从本地文件加载文件,也可以从网络。支持PNG,GIF,WebP, JPEG。
在3.0系统之前,Android是不支持WebP格式的。在4.1.2之前,扩展WebP格式是不支持的。 在Image pipeline的支持下,从2.3之后,都可以使用WebP格式。
对于大多数的应用,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!
否则仍旧是默认配置。
许多配置的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个线程池:
对于网络下载,你可以定制网络层的操作,具体参考:自定义网络层加载.
对于其他操作,如果要改变他们的行为,传入一个ExecutorSupplier即可。
内存缓存和未解码的内存缓存的配置由一个Supplier控制,这个Supplier返回一个MemoryCacheParams 对象用于内存状态控制。
你可使用Builder模式创建一个 DiskCacheConfig:
DiskCacheConfig diskCacheConfig = DiskCacheConfig.newBuilder()
.set....
.set....
.build()
// when building ImagePipelineConfig
.setMainDiskCacheConfig(diskCacheConfig)
如果你想统计缓存的命中率,你可以实现ImageCacheStatsTracker, 在这个类中,每个缓存时间都有回调通知,基于这些事件,可以实现缓存的计数和统计。
Bitmap缓存存储Bitmap
对象,这些Bitmap对象可以立刻用来显示或者用于后处理
在5.0以下系统,Bitmap缓存位于ashmem,这样Bitmap对象的创建和释放将不会引发GC,更少的GC会使你的APP运行得更加流畅。
5.0及其以上系统,相比之下,内存管理有了很大改进,所以Bitmap缓存直接位于Java的heap上。
当应用在后台运行时,该内存会被清空。
这个缓存存储的是原始压缩格式的图片。从该缓存取到的图片在使用之前,需要先进行解码。
如果有调整大小,旋转,或者WebP编码转换工作需要完成,这些工作会在解码之前进行。
APP在后台时,这个缓存同样会被清空。
和未解码的内存缓存相似,文件缓存存储的是未解码的原始压缩格式的图片,在使用之前同样需要经过解码等处理。
和内存缓存不一样,APP在后台时,内容是不会被清空的。即使关机也不会。用户可以随时用系统的设置菜单中进行清空缓存操作。
你可以使用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)
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的高级用法,大部分的应用使用Drawees 和image pipeline打交道就好了。
直接使用Image pipeline是较为有挑战的事情,这意味着要维护图片的内存使用。Drawees 会根据各种情况确定图片是否需要在内存缓存中,在需要时加载,在不需要时移除。直接使用的话,你需要自己完成这些逻辑。
Image pipeline返回的是一个CloseableReference对象。在这些对象不需要时,Drawees会调用.close()
方法。如果你的应用不使用Drawees,那你需要自己完成这个事情。
Java的GC机制会在Bitmap不使用时,清理掉Bitmap。但要GC时总是太迟了,另外GC是很昂贵的开销。GC大对象也会带来性能问题,尤其是在5.0以下系统。
首先创建一个image request. 然后传递给 ImagePipeline:
ImagePipeline imagePipeline = Fresco.getImagePipeline();
DataSource<CloseableReference<CloseableImage>>
dataSource = imagePipeline.fetchDecodedImage(imageRequest);
关于如果接收数据,请参考数据源 章节。
如果你不保持图片原始格式,不执行解码,使用fetchEncodedImage
即可:
DataSource<CloseableReference<PooledByteBuffer>>
dataSource = imagePipeline.fetchEncodedImage(imageRequest);
不像其他缓存,如果图片在内存缓存中有的话,可以在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).
如果你请求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)
为了正确地使用它,请按以下步骤进行操作:
我们创建一个引用,但我们传递给了一个调用者,调用者将持有这个引用。
CloseableReference<Val> foo() {
Val val;
return CloseableReference.of(val);
}
创建了一个引用,但是没有传递给其他调用者,在结束时,需要关闭。
void gee() {
CloseableReference<Val> ref = foo();
try {
haa(ref);
} finally {
ref.close();
}
}
finally
中最适合做此类事情了。
作为一个参数传递,调用者持有这个引用,在下面的函数体中,不能关闭引用。
void haa(CloseableReference<?> ref) {
Log.println("Haa: " + ref.get());
}
如果调用了 .close()
, 调用者尝试调用 .get()
时,会抛出IllegalStateException
在类中使用:
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过的引用。
}