第 4 章 第三方类库
最后更新于:2022-04-01 02:24:11
本文内容包括:
[TOC]
## 自定义网络加载
Image pipeline 默认使用 HttpURLConnection 。应用可以根据自己需求使用不同的网络库。
### OkHttp
OkHttp 是一个流行的开源网络请求库。Imagepipeline有一个使用OkHttp替换掉了Android默认的网络请求的补充。
如果需要使用OkHttp,不要使用这个[下载](#)页面的gradle依赖配置,应该使用下面的依赖配置
~~~groovy
dependencies {
// your project's other dependencies
compile: "com.facebook.fresco:drawee:0.1.0+"
compile: "com.facebook.fresco:imagepipeline-okhttp:0.1.0+"
}
~~~
配置Imagepipeline这时也有一些不同,不再使用`ImagePipelineConfig.newBuilder`,而是使用`OkHttpImagePipelineConfigFactory`:
~~~java
Context context;
OkHttpClient okHttpClient; // build on your own
ImagePipelineConfig config = OkHttpImagePipelineConfigFactory
.newBuilder(context, okHttpClient)
. // other setters
. // setNetworkFetchProducer is already called for you
.build();
Fresco.initialize(context, config);
~~~
### 使用自定的网络层
For complete control on how the networking layer should behave, you can provide one for your app. You must subclass 为了完全控制网络层的行为,你可以自定义网络层。继承 NetworkFetchProducer , 这个类包含了网络通信。
你也可以选择性地继承 NfpRequestState , 这个类是请求时的数据结构描述。
默认的 `HttpURLConnection` 可以作为一个参考.
在配置 Image pipeline 时,把producer传递给Image pipeline。
~~~java
ImagePipelineConfig config = ImagePipelineConfig.newBuilder()
.setNetworkFetchProducer(myNetworkFetchProducer);
. // other setters
.build();
Fresco.initialize(context, config);
~~~
## 使用其他的Image Loader
Drawee 并不是吊死在特定的一种图片加载机制上,它同样适用于其他 image loader。
不过有一些特性,只有Fresco image pipeline才有。前面的提到的需要使用 ImageRequest 和配置 imagepipeline的特性,使用其他image loader时都有可能不起作用。
### Drawee 和 Volley ImageLoader配合使用
我们有一个Drawee使用Volley的 ImageLoader 的补充实现。
我们仅仅对那些已经深度使用Volley ImageLoader的应用推荐这个组合。
同样地,如要使用,使用下面的依赖,而不是[下载](#)页面给出的依赖:
~~~groovy
dependencies {
// your project's other dependencies
compile: "com.facebook.fresco:drawee-volley:0.1.0+"
}
~~~
### 初始化Volley ImageLoader
这时,不需要再调用`Fresco.initialize`了,需要的是初始化Volley。
~~~java
Context context;
ImageLoader imageLoader; // build yourself
VolleyDraweeControllerBuilderSupplier mControllerBuilderSupplier
= new VolleyDraweeControllerBuilderSupplier(context, imageLoader);
SimpleDraweeView.initialize(mControllerBuilderSupplier);
~~~
不要让 `VolleyDraweeControllerBuilderSupplier`离开作用域,你需要它来创建DraweeController,除非你只使用`SimpleDraweeView.setImageURI`。
### DraweeControllers 和 Volley ImageLoader 配合使用
不是调用`Fresco.newControllerBuilder`, 而是:
~~~java
VolleyController controller = mControllerBuilderSupplier
.newControllerBuilder()
. // setters
.build();
mSimpleDraweeView.setController(controller);
~~~
### Drawee 和其他Image Loader 配合使用
依照[源码](https://github.com/facebook/fresco/tree/master/drawee-backends/drawee-volley/src/main/java/com/facebook/drawee/backends/volley) 作为例子,其他Image Loader也是可以和Drawee配合使用的,但是没有我们还没有Drawee和其他Image loader的配合使用的补充实现。
第 3 章 IMAGE PIPELINE 指南
最后更新于:2022-04-01 02:24:08
[TOC]
## Image Pipeline介绍
Image pipeline 负责完成加载图像,变成Android设备可呈现的形式所要做的每个事情。
大致流程如下:
1. 检查内存缓存,如有,返回
1. 后台线程开始后续工作
1. 检查是否在未解码内存缓存中。如有,解码,变换,返回,然后缓存到内存缓存中。
1. 检查是否在文件缓存中,如果有,变换,返回。缓存到未解码缓存和内存缓存中。
1. 从网络或者本地加载。加载完成后,解码,变换,返回。存到各个缓存中。
既然本身就是一个图片加载组件,那么一图胜千言。
![Image Pipeline Diagram](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2015-08-17_55d18d2697768.png)
**图片 3.1** 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。
以下是一个示例配置,列出了所有可配置的选项。几乎没有应用是需要以下这所有的配置的,列出来仅仅是为了作为参考。
~~~java
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每次都返回同一个对象。
~~~java
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个线程用于网络下载
1. 两个线程用于磁盘操作: 本地文件的读取,磁盘缓存操作。
1. 两个线程用于CPU相关的操作: 解码,转换,以及后处理等后台操作。
对于网络下载,你可以定制网络层的操作,具体参考:[自定义网络层加载](#).
对于其他操作,如果要改变他们的行为,传入一个 ExecutorSupplier 即可。
#### 内存缓存的配置
内存缓存和未解码的内存缓存的配置由一个Supplier控制,这个Supplier返回一个 MemoryCacheParams
#### 配置磁盘缓存
你可使用Builder模式创建一个 DiskCacheConfig:
~~~java
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在后台时,内容是不会被清空的。即使关机也不会。用户可以随时用系统的设置菜单中进行清空缓存操作。
### 用一个文件还是两个文件缓存?
如果要使用2个缓存,在[配置image pipeline](#) 时调用 `setMainDiskCacheConfig` 和 `setSmallImageDiskCacheConfig` 方法即可。
大部分的应用有一个文件缓存就够了,但是在一些情况下,你可能需要两个缓存。比如你也许想把小文件放在一个缓存中,大文件放在另外一个文件中,这样小文件就不会因大文件的频繁变动而被从缓存中移除。
至于什么是小文件,这个由应用来区分,在[创建image request](#), 设置 ImageType 即可:
~~~java
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:`
~~~java
ImagePipeline imagePipeline = Fresco.getImagePipeline();
DataSource<CloseableReference<CloseableImage>>
dataSource = imagePipeline.fetchDecodedImage(imageRequest);
~~~
关于如果接收数据,请参考[数据源](#) 章节。
### 忽略解码
如果你不保持图片原始格式,不执行解码,使用`fetchEncodedImage`即可:
~~~java
DataSource<CloseableReference<PooledByteBuffer>>
dataSource = imagePipeline.fetchEncodedImage(imageRequest);
~~~
### 从Bitmap缓存中立刻取到结果
不像其他缓存,如果图片在内存缓存中有的话,可以在UI线程立刻拿到结果。
~~~java
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 提供两种预加载方式。
预加载到文件缓存:
~~~java
imagePipeline.prefetchToDiskCache(imageRequest);
~~~
预加载到内存缓存:
~~~java
imagePipeline.prefetchToBitmapCache(imageRequest);
~~~
## 数据源和数据订阅者
数据源和 [Future](http://developer.android.com/reference/java/util/concurrent/Future.html), 有些相似,都是异步计算的结果。
不同点在于,数据源对于一个调用会返回一系列结果,Future只返回一个。
提交一个Image request之后,Imagepipeline返回一个数据源。从中获取数据需要使用数据订阅者(DataSubscriber).
### 当你仅仅需要Bitmap
如果你请求Image pipeline仅仅是为了获取一个 [Bitmap](http://developer.android.com/reference/android/graphics/Bitmap.html), 对象。你可以利用简单易用的 BaseBitmapDataSubscriber:
~~~java
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`函数范围之外的任何变量。订阅者执行完操作之后,imagepipeline会回收这个bitmap,释放内存。在这个函数范围内再次使用这个Bitmap对象进行绘制将会导致`IllegalStateException`。
### 通用的解决方案
如果你就是想维持对这个Bitmap对象的引用,你不能维持纯Bitmap对象的引用,可以利用[可关闭的引用(closeablereferences)](#) 和 BaseDataSubscriber:
~~~java
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](http://www.boost.org/doc/libs/1_57_0/libs/smart_ptr/smart_ptr.htm) 的东西。
Fresco的解决方案是: 可关闭的引用(CloseableReference)
为了正确地使用它,请按以下步骤进行操作:
### 1. 调用者拥有这个引用
我们创建一个引用,但我们传递给了一个调用者,调用者将持有这个引用。
~~~java
CloseableReference<Val> foo() {
Val val;
return CloseableReference.of(val);
}
~~~
### 2. 持有者在离开作用域之前,需要关闭引用
创建了一个引用,但是没有传递给其他调用者,在结束时,需要关闭。
~~~java
void gee() {
CloseableReference<Val> ref = foo();
try {
haa(ref);
} finally {
ref.close();
}
}
~~~
`finally` 中最适合做此类事情了。
### 3. 除了引用的持有者,闲杂人等**不得**关闭引用
作为一个参数传递,调用者持有这个引用,在下面的函数体中,不能关闭引用。
~~~java
void haa(CloseableReference<?> ref) {
Log.println("Haa: " + ref.get());
}
~~~
如果调用了 `.close()`, 调用者尝试调用 `.get()`时,会抛出`IllegalStateException`
### 4. 在赋值给变量前,先进行clone
在类中使用:
~~~java
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
~~~
在内部中使用:
~~~java
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过的引用。
}
~~~
第 2 章 DRAWEE 指南
最后更新于:2022-04-01 02:24:06
[TOC]
### 在XML中使用Drawees
Drawees 具有极大的可定制性。
下面的例子给出了可以配置的各种选项:
~~~xml
<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"
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"
/>
~~~
##### 必须设置layout_width和layout_height
如果没有在XML中声明这两个属性,将无法正确加载图像。
##### wrap_content
*Drawees 不支持 `wrap_content` 属性。*
所下载的图像可能和占位图尺寸不一致,如果设置出错图或者重试图的话,这些图的尺寸也可能和所下载的图尺寸不一致。
如果大小不一致,图像下载完之后,假设如果是`wrap_content`,View将会重新layout,改变大小和位置。这将会导致界面跳跃。
##### 固定宽高比
只有希望显示的固定宽高比时,可以使用`wrap_content`。
如果希望显示的图片保持一定宽高比例,如果 4:3,则在XML中:
~~~xml
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="20dp"
android:layout_height="wrap_content"
<!-- other attributes -->
~~~
然后在代码中指定显示比例:
~~~java
mSimpleDraweeView.setAspectRatio(1.33f);
~~~
### 在JAVA代码中使用Drawees
#### 设置或更改要显示的图片
~~~
mSimpleDraweeView.setImageURI(uri);
~~~
如果要更加复杂的配置,可使用[ControllerBuilder](#);
#### 自定义显示图
一般情况下,在[XML设置显示效果即可](#), 如果想更多定制化,可以这样:
创建一个 builder 然后设置给 DraweeView:
~~~java
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`。
#### 修改 DraweeHierarchy
DraweeHierarchy 的一些属性可以在运行时改变。
要改变这些属性,首先获取一个引用:
~~~java
GenericDraweeHierarchy hierarchy = mSimpleDraweeView.getHierarchy();
~~~
##### 修改占位图
修改占位图为资源id:
~~~java
hierarchy.setPlaceholderImage(R.drawable.placeholderId);
~~~
或者修改为一个 [Drawable](http://developer.android.com/reference/android/graphics/drawable/Drawable.html):
~~~java
Drawable drawable;
// 创建一个drawable
hierarchy.setPlaceholderImage(drawable);
~~~
##### 修改显示的图像
修改[缩放类型](#):
~~~java
hierarchy.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_INSIDE);
~~~
当然,如果修改为 `focusCrop,` 需要指定一个居中点:
~~~java
hierarchy.setActualImageFocusPoint(point);
~~~
或者设置一个color filter:
~~~java
ColorFilter filter;
// 创建filter
hierarchy.setActualImageColorFilter(filter);
~~~
##### 圆角
All of the [rounding related params](#), except the rounding method, can be modified. You get a `RoundingParams` object from the hierarchy, modify it, and set it back again:
除了圆角显示方式(原来为圆角的不能修改为圆圈,反之亦然),其他圆角相关的呈现参数, [具体参见这里](#) 是可以动态修改的。
如下: 获取DraweeHierarchy的圆角显示参数,修改圆角半径为10。
~~~java
RoundingParams roundingParams = hierarchy.getRoundingParams();
roundingParams.setCornersRadius(10);
hierarchy.setRoundingParams(roundingParams);
~~~
### Drawee的各种效果配置
### 内容导航
- [定义](#)
- [设置要加载的图片](#)
- [占位图](#)
- [加载失败时的占位图](#)
- [点击重新加载](#)
- [显示一个进度条](#)
- [Backgrounds](#)
- [Overlays](#)
- [Pressed State Overlay](#)
### 定义
本页说明如何设置实现不同的图片呈现效果。
除了要加载的图片,其他各个设置都可以在xml中指定。在xml中指定的时候,可以是 `drawable/`下的资源,也可以颜色。
在Java 代码中也可以指定。如果需要 [通过程序设定](#) 的话会接触到这个类: GenericDraweeHierarchyBuilder
通过代码设置是,设置的值可以是资源id,也可以是 Drawable 的子类。
创建完 GenericDraweeHierarchy 之后,也可以通过该类的相关方法,重新设置一些效果。
大多数的用户呈现不同效果的drawables都是可以[缩放的](#).
### 设置要加载的图
除了需要加载的图片是真正必须的,其他的都是可选的。如前所述,图片可以来自多个地方。
所需加载的图片实际是DraweeController的一个属性,而不是 DraweeHierarchy 的属性。
可使用`setImageURI`方法或者通过设置 DraweeController 来进行设置。
对于要加载的图片,除了可以设置缩放类型外,DraweeHierarchy 还公开出一些其他方法用来控制显示效果:
- focus point (居中焦点, 用于 focusCrop 缩放模式)
- color filter
默认的缩放类型是: `centerCrop`
### 占位图(Placeholder)
在调用`setController` 或者 `setImageURI` 之后,占位图开始显示,直到图片加载完成。
对于渐进式格式的JPEG图片,占位图会显示直到满足已加载的图片解析度到达设定值。
XML 中属性值: `placeholderImage`
Hierarchy builder中的方法: `setPlaceholderImage`
Hierarchy method: `setPlaceholderImage`
默认值: a transparent [ColorDrawable](http://developer.android.com/reference/android/graphics/drawable/ColorDrawable.html)
默认缩放类型: `centerInside`
### 设置加载失败占位图
如果URI是无效的,或者下载过程中网络不可用,将会导致加载失败。当加载图片出错时,你可以设置一个出错提示图片。
XML 中属性值: `failureImage`
Hierarchy builder中的方法: `setFailureImage`
默认值: The placeholder image
默认缩放类型: `centerInside`
### 点击重新加载图
在加载失败时,可以设置点击重新加载。这时提供一个图片,加载失败时,会显示这个图片(而不是失败提示图片),提示用户点击重试。
在[ControllerBuilder](#) 中如下设置:
~~~
.setTapToRetryEnabled(true)
~~~
加载失败时,image pipeline 会重试四次;如果还是加载失败,则显示加载失败提示图片。
XML 中属性值: `retryImage`
Hierarchy builder中的方法: `setRetryImage`
默认值: The placeholder image
默认缩放类型: `centerInside`
### 显示一个进度条
设置一个进度条图片,提示用户正在加载。目前,进度条仅仅是提示正在loading,和加载进度无关。
XML 中属性值: `progressBarImage`
Hierarchy builder中的方法: `setProgressBarImage`
默认值: None
默认缩放类型: `centerInside`
### 背景
背景图会最先绘制,在XML中只可以指定一个背景图,但是在JAVA代码中,可以指定多个背景图。
当指定一个背景图列表的时候,列表中的第一项会被首先绘制,绘制在最下层,然后依次往上绘制。
背景图片不支持缩放类型,会被强制到`Drawee`尺寸大小。
XML 中属性值: `backgroundImage`
Hierarchy builder中的方法: `setBackground,``setBackgrounds`
默认值: None
默认缩放类型: N/A
### 设置叠加图(Overlay)
叠加图会最后被绘制。
和背景图一样,XML中只可以指定一个,如果想指定多个,可以通过JAVA代码实现。
当指定的叠加图是一个列表的时候,列表第一个元素会被先绘制,最后一个元素最后被绘制到最上层。
同样的,不支持各种缩放类型。
XML 中属性值: `overlayImage`
Hierarchy builder中的方法: `setOverlay,``setOverlays`
默认值: None
默认缩放类型: N/A
### 设置按压状态下的叠加图
同样不支持缩放,用户按压DraweeView时呈现。
XML 中属性值: `pressedStateOverlayImage`
Hierarchy builder中的方法: `setPressedStateOverlay`
默认值: None
默认缩放类型: N/A
### 缩放
对于 Drawee 的[各种效果配置](#),其中一些是支持缩放类型的。
#### 可用的缩放类型
<table><thead><tr><th>类型</th> <th>描述</th></tr></thead><tbody><tr><td>center</td> <td>居中,无缩放</td></tr><tr><td>centerCrop</td> <td>保持宽高比缩小或放大,使得两边都大于或等于显示边界。居中显示。</td></tr><tr><td><a class="internal" href="#focusCrop">focusCrop</a></td> <td>同centerCrop, 但居中点不是中点,而是指定的某个点</td></tr><tr><td>centerInside</td> <td>使两边都在显示边界内,居中显示。<br/>如果图尺寸大于显示边界,则保持长宽比缩小图片。</td></tr><tr><td>fitCenter</td> <td>保持宽高比,缩小或者放大,使得图片完全显示在显示边界内。居中显示</td></tr><tr><td>fitStart</td> <td>同上。但不居中,和显示边界左上对齐</td></tr><tr><td>fitEnd</td> <td>同fitCenter, 但不居中,和显示边界右下对齐</td></tr><tr><td>fitXY</td> <td>不保存宽高比,填充满显示边界</td></tr><tr><td><a class="internal" href="#none">none</a></td> <td>如要使用tile mode显示, 需要设置为none</td></tr></tbody></table>
这些缩放类型和Android [ImageView](http://developer.android.com/reference/android/widget/ImageView.ScaleType.html) 支持的缩放类型几乎一样.
唯一不支持的缩放类型是`matrix.` Fresco 提供了`focusCrop` 作为补充。通常这个缩放效果更佳。
#### focusCrop
`centerCrop`缩放模式会保持长宽比,缩放图片,填充满显示边界,居中显示。这个缩放模式在通常情况下很有用。
但是对于人脸等图片时,一味地居中显示,这个模式可能会裁剪掉一些有用的信息。
以人脸图片为例,借助一些类库,我们可以识别出人脸所在位置。如果可以设置以人脸位置居中裁剪显示,那么效果会好很多。
Fresco的focusCrop缩放模式正是为此而设计。只要提供一个居中聚焦点,显示时就会**尽量**以此点为中心。
居中点是以相对方式给出的,比如(0.5f, 0.5f)就是居中显示,(0f, 0f)就是左上对齐显示。
如果要使用此缩放模式,首先指定缩放模式。在XML:
~~~xml
fresco:actualImageScaleType="focusCrop"
~~~
在Java代码中
~~~java
PointF focusPoint;
// your app populates the focus point
mSimpleDraweeView
.getHierarchy()
.setActualImageFocusPoint(focusPoint);
~~~
#### none
如果你要使用tile mode进行显示,那么需要将scale type 设置为none.
### 圆角和圆圈
Drawee 轻松支持圆角显示,并且显示圆角时,并不复制和修改Bitmap对象,那样太耗费内存。
#### 圆角
圆角实际有2中呈现方式:
1. 圆圈 - 设置`roundAsCircle`为true
1. 圆角 - 设置`roundedCornerRadius`
设置圆角时,支持4个角不同的半径。XML中无法配置,但可在Java代码中配置。
#### 设置圆角
可使用以下两种方式:
1. 默认使用一个shader绘制圆角,但是仅仅占位图所要显示的图有圆角效果。失败示意图和重下载示意图无圆角效果。
1. 叠加一个`solid color`来绘制圆角。但是背景需要固定成指定的颜色。在XML中指定 `roundWithOverlayColor`, 或者通过调用`setOverlayColor`来完成此设定。
#### XML中配置
`SimpleDraweeView` 支持如下几种圆角配置:
~~~xml
<com.facebook.drawee.view.SimpleDraweeView
...
fresco:roundedCornerRadius="5dp"
fresco:roundBottomLeft="false"
fresco:roundBottomRight="false"
fresco:roundWithOverlayColor="@color/blue"
fresco:roundingBorderWidth="1dp"
fresco:roundingBorderColor="@color/red"
~~~
#### 代码中配置
在创建 DraweeHierarchy 时,可以给`GenericDraweeHierarchyBuilder`指定一个 RoundingParams 用来绘制圆角效果。
~~~java
RoundingParams roundingParams = RoundingParams.fromCornersRadius(7f);
roundingParams.setOverlayColor(R.color.green);
// 或用 fromCornersRadii 以及 asCircle 方法
genericDraweeHierarchyBuilder
.setRoundingParams(roundingParams);
~~~
你也可以在运行时,改变圆角效果
~~~java
RoundingParams roundingParams =
mSimpleDraweeView.getHierarchy().getRoundingParams();
roundingParams.setBorder(R.color.red, 1.0);
roundingParams.setRoundAsCircle(true);
mSimpleDraweeView.getHierarchy().setRoundingParams(roundingParams);
~~~
> 在运行时,不能改变呈现方式: 原本是圆角,不能改为圆圈。
### 使用ControllerBuilder
`SimpleDraweeView` 有两个方法可以设置所要加载显示图片,简单的方法就是`setImageURI`。
如果你需要对加载显示的图片做更多的控制和定制,那就需要用到 DraweeController,本页说明如何使用。
#### DraweeController
首先,创建一个DraweeController, 然后传递图片加载请求给 PipelineDraweeControllerBuilder
随后,你可以控制controller的其他选项了:
~~~java
ControllerListener listener = new BaseControllerListener() {...}
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setTapToRetryEnabled(true)
.setOldController(mSimpleDraweeView.getController())
.setControllerListener(listener)
.build();
mSimpleDraweeView.setController(controller);
~~~
在指定一个新的controller的时候,使用`setOldController`,这可节省不必要的内存分配。
#### 自定义图片加载请求
在更进一步的用法中,你需要给Image pipeline 发送一个ImageRequest。下面是一个图片加载后,使用后处理器(postprocessor) 进行图片后处理的例子.
~~~java
Uri uri;
Postprocessor myPostprocessor = new Postprocessor() { ... }
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setPostprocessor(myPostprocessor)
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getController())
// 其他设置
.build();
~~~
### 渐进式JPEG图
*注意: 本页提及的API仅是初步设计,后续可能变动*
Fresco 支持渐进式的网络JPEG图。在开始加载之后,图会从模糊到清晰渐渐呈现。
你可以设置一个清晰度标准,在未达到这个清晰度之前,会一直显示占位图。
渐进式JPEG图仅仅支持网络图。
##### 初始化
[配置Image pipeline时](#) 需要传递一个 ProgressiveJpegConfig 的实例。
这个实例需要完成两个事情:1. 返回下一个需要解码的扫描次数2. 确定多少个扫描次数之后的图片才能开始显示。
下面的实例中,为了实现节省CPU,并不是每个扫描都进行解码。
注意:
- 每次解码完之后,调用`getNextScanNumberToDecode`, 等待扫描值大于返回值,才有可能进行解码。
假设,随着下载的进行,下载完的扫描序列如下: `1, 4, 5, 10`。那么:
1. 首次调用`getNextScanNumberToDecode`返回为2, 因为初始时,解码的扫描数为0。
1. 那么1将不会解码,下载完成4个扫描时,解码一次。下个解码为扫描数为6
1. 5不会解码,10才会解码
~~~java
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();
~~~
除了自己实现ProgressiveJpegConfig, 也可以直接使用 SimpleProgressiveJpegConfig
##### At Request Time
目前,我们必须显式地在加载时,允许渐进式JPEG图片加载。
~~~java
Uri uri;
ImageRequest request = ImageRequestBuilder
.newBuilderWithSource(uri)
.setProgressiveRenderingEnabled(true)
.build();
PipelineDraweeController controller = Fresco.newControllerBuilder()
.setImageRequest(requests)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
~~~
我们希望在后续的版本中,在`setImageURI`方法中可以直接支持渐进式图片加载。
### 动画图(gif)
Fresco 支持GIF和WebP 格式图片;支持WebP 格式的动画图也支持(包括扩展WebP 格式),支持2.3及其以后那些没有原生WebP支持的系统。
#### 设置动画图自动播放
如果你希望图片下载完之后自动播放,同时,当View从屏幕移除时,停止播放,只需要在[image request](#) 中简单设置,如下:
~~~java
Uri uri;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setAutoPlayAnimation(true)
. // other setters
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
. // other setters
.build();
mSimpleDraweeView.setController(controller);
~~~
#### 手动控制动画图播放
也许,你希望在图片加载完之后,手动控制动画的播放,那么这样做:
~~~java
ControllerListener controllerListener = new BaseControllerListener() {
@Override
public void onFinalImageSet(
String id,
@Nullable ImageInfo imageInfo,
@Nullable Animatable anim) {
if (anim != null) {
// 根据业务逻辑,在合适的时机播放动画。
}
};
Uri uri;
PipelineDraweeController controller = Fresco.newControllerBuilder()
.setControllerListener(controllerListener)
.setUri(uri);
// other setters
.build();
mSimpleDraweeView.setController(controller);
~~~
另外,controller提供对[Animatable](http://developer.android.com/reference/android/graphics/drawable/Animatable.html) 的访问。
如果有可用动画的话,可对动画进行灵活的控制:
~~~java
Animatable animation = mSimpleDraweeView.getController().getAnimatable();
if (animation != null) {
// 开始播放
animation.start();
// 一段时间之后,根据业务逻辑,停止播放
animation.stop();
}
~~~
### 多图请求及图片复用
多图请求需 [自定义ImageRequest](#).
#### 先显示低分辨率的图,然后是高分辨率的图
如果你要显示一张高分辨率的图,但是这张图下载比较耗时。你可以在下载前,先提供一张很快能下载完的小缩略图。这比一直显示占位图,用户体验会好很多。
这时,你可以设置两个图片的URI,一个是低分辨率的缩略图,一个是高分辨率的图。
~~~java
Uri lowResUri, highResUri;
PipelineDraweeController controller = Fresco.newControllerBuilder()
.setLowResImageRequest(ImageRequest.fromUri(lowResUri))
.setImageRequest(ImageRequest.fromUri(highResUri))
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
~~~
#### 缩略图预览
*本功能仅支持本地URI,并且是JPEG图片格式*
如果本地JPEG图,有EXIF的缩略图,image pipeline 会立刻返回一个缩略图。完整的清晰大图,在decode完之后再显示。
~~~java
Uri uri;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setLocalThumbnailPreviewsEnabled(true)
.build();
PipelineDraweeController controller = Fresco.newControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
~~~
#### 本地图片复用
大部分的时候,一个图片可能会对应有多个URI,比如:
- 拍照上传。本地图片较大,上传的图片较小。上传完成之后的图片,有一个url,如果要加载这个url,可直接加载本地图片。
- 本地已经有600x600尺寸的大图了,需要显示100x100的小图
对于一个URI,image pipeline 会依次检查内存,磁盘,如果没有从网络下载。
而对于一个图片的多个URI,image pipeline 会先检查他们是否在内存中。如果没有任何一个是在内存中的,会检查是否在本地存储中。如果也没有,才会执行网络下载。
但凡有任何一个检查发现在内存或者在本地存储中,都会进行复用。列表顺序就是要显示的图片的优先顺序。
使用时,创建一个image request 列表,然后传给ControllerBuilder:
~~~java
Uri uri1, uri2;
ImageRequest request = ImageRequest.fromUri(uri1);
ImageRequest request2 = ImageRequest.fromUri(uri2);
ImageRequest[] requests = { request1, request2 };
PipelineDraweeController controller = Fresco.newControllerBuilder()
.setFirstAvailableImageRequests(requests)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
~~~
### 监听下载事件
你也许想在图片下载完成或者下载失败之后,做一些其他事情。
图片是后台线程异步加载的,我们可以使用一个`ControllerListener`实现事件的监听。
_在监听事件回调时,无法修改图片,如果需要修改图片,可使用[后处理器(Postprocessor)](#)
~~~ ControllerListener controllerListener = new BaseControllerListener() {
@Override
public void onFinalImageSet(
String id,
@Nullable ImageInfo imageInfo,
@Nullable Animatable anim) {
if (imageInfo == null) {
return;
}
QualityInfo qualityInfo = imageInfo.getQualityInfo();
FLog.d("Final image received! " +
"Size %d x %d",
"Quality level %d, good enough: %s, full quality: %s",
imageInfo.getWidth(),
imageInfo.getHeight(),
qualityInfo.getQuality(),
qualityInfo.isOfGoodEnoughQuality(),
qualityInfo.isOfFullQuality());
}
@Override
public void onIntermediateImageSet(String id, @Nullable ImageInfo imageInfo) {
FLog.d("Intermediate image received");
}
@Override
public void onFailure(String id, Throwable throwable) {
FLog.e(getClass(), throwable, "Error loading %s", id)
}
};
Uri uri;
DraweeController controller = Fresco.newControllerBuilder()
.setControllerListener(controllerListener)
.setUri(uri);
// other setters
.build();
mSimpleDraweeView.setController(controller);
~~~
对所有的图片加载,`onFinalImageSet` 或者 `onFailure` 都会被触发。前者在成功时,后者在失败时。
如果允许呈现[渐进式JPEG](#),同时图片也是渐进式图片,`onIntermediateImageSet`会在每个扫描被解码后回调。具体图片的那个扫描会被解码,参见[渐进式JPEG图](#)
### 缩放和旋转图片
使用这个功能需要直接[创建 image request](#)。
### 缩放图片
#### 什么时候该修改图片尺寸
一般地,当所要显示的图片和显示区域大小不一致时,会按以下方式进行处理。
1. 从服务器下载小一些的图片
1. 显示时缩放图片
1. 调整图片尺寸大小
对于一个图片,如果服务器支持不同尺寸的缩略图,那么每次下载都选择尺寸最匹配的图片,这个不仅节省数据流量也节约本地储存和CPU。
如果服务器不支持,或者处理本地图片的话,第二个选择是[使用缩放类型](#)。缩放是用Androi内置的功能使图像和显示边界相符。在4.0之后,支持硬件加速。这在大部分情况下是最快,同时也是最高效的显示一张和显示边界大小相符的图片的方式。首先指定`layout_width`和`layout_width`为指定值,然后指定[缩放类型](#)
但当所要显示的图片比显示区域大许多的时候,不推荐这样做,缩放过程会导致大量的内存消耗。
这时,需要改变图片尺寸。
#### 修改图片尺寸
调整大小并不是修改原来的文件,而是在解码之前,在native内存中修改。
这个缩放方法,比Android内置的缩放范围更大。Android相机生成的照片一般尺寸都很大,需要调整大小之后才能被显示。
目前,仅仅支持JPEG格式的图片,同时,大部分的Android系统相机图片都是JPEG的。
如果要修改图片尺寸,创建`ImageRequest`时,提供一个 ResizeOptions:
~~~java
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);
~~~
### 自动旋转
如果看到的图片是侧着的,用户是难受的。许多设备会在JPEG文件的metadata中记录下照片的方向。如果你想图片呈现的方向和设备屏幕的方向一致,你可以简单地这样做到:
~~~java
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setAutoRotateEnabled(true)
.build();
// as above
~~~
### 修改图片
有时,我们想对从服务器下载,或者本地的图片做些修改,比如在某个坐标统一加个网格什么的。这时使用后处理器(Postprocessor)便可达到目的。
##### 例子:
给图片加个网格:
~~~java
Uri uri;
Postprocessor redMeshPostprocessor = new Postprocessor() {
@Override
public String getName() {
return "redMeshPostprocessor";
}
@Override
public void process(Bitmap bitmap) {
for (int x = 0; x < bitmap.getWidth(); x+=2) {
for (int y = 0; y < bitmap.getHeight(); y+=2) {
bitmap.setPixel(x, y, Color.RED);
}
}
}
}
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setPostprocessor(redMeshPostprocessor)
.build();
PipelineDraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getOldController())
// other setters as you need
.build();
mSimpleDraweeView.setController(controller);
~~~
##### 注意点
图片在进入后处理器(postprocessor)的图片是原图的一个完整拷贝,原来的图片不受修改的影响。在5.0以前的机器上,拷贝后的图片也在native内存中。
在开始一个图片显示时,即使是反复显示同一个图片,在每次进行显示时,都需要指定后处理器。
对于同一个图片,每次显示,可以使用不同的后处理器。
##### Repeated Postprocessors
如果想对同一个图片进行多次后处理,那么继承 BaseRepeatedPostprocessor 即可。该类有一个`update`方法,需要执行后处理时,调用该方法即可。
下面的例子展示了在运行时,后处理改变图片网格的颜色:
~~~java
public class MeshPostprocessor extends BaseRepeatedPostprocessor {
private int mColor = Color.TRANSPARENT;
public void setColor(int color) {
mColor = color;
update();
}
@Override
public String getName() {
return "meshPostprocessor";
}
@Override
public void process(Bitmap bitmap) {
for (int x = 0; x < bitmap.getWidth(); x+=2) {
for (int y = 0; y < bitmap.getHeight(); y+=2) {
bitmap.setPixel(x, y, mColor);
}
}
}
}
MeshPostprocessor meshPostprocessor = new MeshPostprocessor();
// setPostprocessor as in above example
// 改变颜色
meshPostprocessor.setColor(Color.RED);
meshPostprocessor.setColor(Color.BLUE);
~~~
每个image request, 仍旧只有一个`Postprocessor`,但是这个后处理器是状态相关了。
### 图片请求
如果你需要的`ImageRequest`仅仅是一个URI,那么`ImageRequest.fromURI`就足够了,在[多图请求及图片复用](#)中,有这样的用法。
否则,你需要`ImageRequestBuilder`来做更多的事情。
~~~java
Uri uri;
ImageDecodeOptions decodeOptions = ImageDecodeOptions.newBuilder()
.setBackgroundColor(Color.GREEN)
.build();
ImageRequest request = ImageRequestBuilder
.newBuilderWithSource(uri)
.setAutoRotateEnabled(true)
.setLocalThumbnailPreviewsEnabled(true)
.setLowestPermittedRequestLevel(RequestLevel.FULL_FETCH)
.setProgressiveRenderingEnabled(false)
.setResizeOptions(new ResizeOptions(width, height))
.build();
~~~
##### ImageRequest 的属性和成员
- `uri` - 唯一的必选的成员. 参考 [支持的URIs](#)
- `autoRotateEnabled` - 是否支持[自动旋转](#).
- `progressiveEnabled` - 是否支持[渐进式加载](#).
- `postprocessor` - [后处理器(postprocess)](#).
- `resizeOptions` - 图片缩放选项,用前请先阅读[缩放和旋转](#).
##### 最低请求级别
Image pipeline 加载图片时有一套明确的[请求流程](#)
1. 检查内存缓存,有如,立刻返回。这个操作是实时的。
1. 检查未解码的图片缓存,如有,解码并返回。
1. 检查磁盘缓存,如果有加载,解码,返回。
1. 下载或者加载本地文件。调整大小和旋转(如有),解码并返回。对于网络图来说,这一套流程下来是最耗时的。
`setLowestPermittedRequestLevel`允许设置一个最低请求级别,请求级别和上面对应地有以下几个取值:
- `BITMAP_MEMORY_CACHE`
- `ENCODED_MEMORY_CACHE`
- `DISK_CACHE`
- `FULL_FETCH`
如果你需要立即取到一个图片,或者在相对比较短时间内取到图片,否则就不显示的情况下,这非常有用。
### 自定义View
#### DraweeHolders
总有一些时候,`DraweeViews`是满足不了需求的,在展示图片的时候,我们还需要展示一些其他的内容,或者支持一些其他的操作。在同一个View里,我们可能会想显示一张或者多张图。
在自定义View中,Fresco 提供了两个类来负责图片的展现:
- `DraweeHolder` 单图情况下用。
- `MultiDraweeHolder` 多图情况下用。
#### 自定义View需要完成的事情
Android 呈现View对象,只有View对象才能得到一些系统事件的通知。`DraweeViews`处理这些事件通知,高效地管理内存。使用`DraweeHolder`时,你需要自己实现这几个方法。
##### 处理 attach/detach 事件
**如果没按照以下步骤实现的话,很可能会引起内存泄露**
当图片不再在View上显示时,比如滑动时View滑动到屏幕外,或者不再绘制,图片就不应该再存在在内存中。Drawees 监听这些事情,并负责释放内存。当图片又需要显示时,重新加载。
这些在`DraweeView`中是自动的,但是在自定义View中,需要我们自己去操作,如下:
~~~java
DraweeHolder mDraweeHolder;
@Override
public void onDetachedFromWindow() {
super.onDetachedToWindow();
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中,需要这样:
~~~java
@Override
public boolean onTouchEvent(MotionEvent event) {
return mDraweeHolder.onTouchEvent(event) || super.onTouchEvent(event);
}
~~~
##### 自定义onDraw
~~~java
Drawable drawable = mDraweeHolder.getHierarchy().getTopLevelDrawable();
drawable.setBounds(...);
~~~
否则图片将不会出现
- 不要向下转换这个Drawable
- 不要变换这个Drawable
##### 其他应该做的
- 重写 `verifyDrawable:`
~~~java
@Override
protected boolean verifyDrawable(Drawable who) {
if (who == mDraweeHolder.getHierarchy().getTopLevelDrawable()) {
return true;
}
// 对其他Drawable的验证逻辑
}
~~~
- 确保`invalidateDrawable` 处理了图片占用的那块区域。
#### 创建 DraweeHolder
这同样需要非常小心和细致
##### 构造函数
我们推荐如下实现构造函数:
- 重写3个构造函数
- 在每个构造函数中调用同等签名的父类构造函数,和一个私有的`init`方法。
- 在`init`方法中执行初始化操作。
即,不要在构造函数中用`this`来调用另外一个构造。
这样可以保证,不管调用哪个构造,都可以正确地执行初始化流程。然后在`init`方法中创建holder。
##### 创建 Holder
如果有可能,只在View创建时,创建Drawees。创建DraweeHierarchy开销较大,最好只做一次。
~~~java
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。
~~~java
DraweeController controller = Fresco.newControllerBuilder()
.setUri(uri)
.setOldController(mDraweeHolder.getController())
.build();
mDraweeHolder.setController(controller);
~~~
#### MultiDraweeHolder
和`DraweeHolder`相比,`MultiDraweeHolder`有 `add`, `remove`, `clear`等方法可以操作Drawees。如下:
~~~java
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`那样。
### 一些陷阱
##### 不要向下转换
不要试图把Fresco返回的一些对象进行向下转化,这也许会带来一些对象操作上的便利,但是也许在后续的版本中,你会遇到一些因为向下转换特性丢失导致的难以处理的问题。
##### 不要使用getTopLevelDrawable
`DraweeHierarchy.getTopLevelDrawable()`**仅仅** 应该在DraweeViews中用,除了定义View中,其他应用代码建议连碰都不要碰这个。
在自定义View中,也千万不要将返回值向下转换,也许下个版本,我们会更改这个返回值类型。
##### 不要复用 DraweeHierarchies
永远不要吧`DraweeHierarchy` 通过 `DraweeView.setHierarchy` 设置给不同的View。DraweeHierarchy是由一系列Drawable组成的。在Android中, Drawable不能被多个View共享。
##### 不要在多个DraweeHierarchy中使用同一个Drawable
原因同上。当时可以使用不同的资源ID。Android实际会创建不同的Drawable。
##### 不要直接给 `DraweeView` 设置图片。
目前 `DraweeView` 直接继承于ImageView,因此它有 `setImageBitmap`,`setImageDrawable` 等方法。
如果利用这些方法,直接设置一个图片。内部的`DraweeHierarchy`就会丢失,也就无法取到imagepipeline 的任何图像了。
##### 使用DraweeView时,请不要使用任何ImageView的属性
在后续的版本中,DraweeView会直接从View派生。任何属于ImageView但是不属于View的方法都会被移除。
第 1 章 配置和使用
最后更新于:2022-04-01 02:24:04
[TOC]
## 下载Fresco
类库发布到了Maven中央库:
### Gradle:
~~~groovy
dependencies {
compile 'com.facebook.fresco:fresco:0.1.0+'
}
~~~
### Maven:
~~~xml
<dependency>
<groupId>com.facebook.fresco</groupId>
<artifactId>fresco</artifactId>
<version>LATEST</version>
</dependency>
~~~
### Eclipse
~~~呵呵~~~
## 配置和开始使用
如果你仅仅是想简单下载一张网络图片,在下载完成之前,显示一张占位图,那么简单使用 SimpleDraweeView 即可。
在Application 初始化时:
~~~java
Fresco.initialize(context);~~~
在xml布局文件中, 加入命名空间:
~~~xml
<!-- 其他元素 -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto">
~~~
加入`SimpleDraweeView`:
~~~xml
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="20dp"
android:layout_height="20dp"
fresco:placeholderImage="@drawable/my_drawable"
/>
~~~
开始加载图片
~~~
draweeView.setImageURI("http://site.com/uri");
~~~
剩下的,Fresco会替你完成:
- 显示占位图直到加载完成;
- 下载图片;
- 缓存图片;
- 图片不再显示时,从内存中移除;
等等等等。
## 关键概念
### Drawees
Drawees 负责图片的呈现,包含几个组件,有点像MVC模式。
#### DraweeView
继承于 [View](http://developer.android.com/reference/android/view/View.html), 负责图片的显示。
一般情况下,使用`SimpleDraweeView` 即可. 简单的用法,在这个页面:[开始使用](#) 。
它支持很多自定义效果,参见这里: [自定义显示效果](#).
#### DraweeHierarchy
继承于 Drawable, 包含用于绘制的图像数据。MVC中的M。
如果你想在Java代码中自定义图片的展示,可以通过这类实现,具体的请参考这里: [在Java代码中自定义显示效果](#)
#### DraweeController
`DraweeController` 负责和 image loader 交互(默认是Fresco中 image pipeline),可以创建一个这个类的实例,来实现对所要显示的图片做更多的控制。
#### DraweeControllerBuilder
`DraweeControllers` 由 `DraweeControllerBuilder` 采用 Builder 模式创建,创建之后,不可修改。具体参见: [使用ControllerBuilder](#)。
#### Listeners
使用 ControllerListener 的一个场景就是设置一个 [Listener](#)监听图片的下载。
### Image Pipeline
Fresco 的 Image Pipeline 负责图片的获取和管理。图片可以来自远程服务器,本地文件,或者Content Provider,本地资源。压缩后的文件缓存在本地存储中,Bitmap数据缓存在内存中。
在5.0系统之后,Image Pipeline 使用`pinned purgeables*将Bitmap数据存在native 内存中。这要求图片不使用时,要显示地释放内存。
`SimpleDraweeView` 自动处理了这个释放过程,所以没有特殊情况,尽量使用`SimpleDraweeView`,在特殊的场合,如果有需要,也可以直接控制Image Pipeline。
## 支持的URIs
Fresco 支持许多URI格式。
特别注意:Fresco **不支持** 相对路径的URI. 所有的URI都必须是绝对路径,并且带上该URI的scheme。
如下:
| 类型 | Scheme | 示例 |
|-----|-----|-----|
| 远程图片 | `http://,``https://` | `HttpURLConnection` 或者参考 [使用其他网络加载方案](#) |
| 本地文件 | `file://` | `FileInputStream` |
| Content provider | `content://` | `ContentResolver` |
| asset目录下的资源 | `asset://` | `AssetManager` |
| res目录下的资源 | `res://` | `Resources.openRawResource` |
注意,只有图片资源才能使用在Imagepipeline中,比如(PNG)。其他资源类型,比如字符串,或者XML Drawable在Imagepipeline中没有意义。所以加载的资源不支持这些类型。
像ShapeDrawable这样声明在XML中的drawable可能引起困惑。注意到这毕竟不是图片,如果想把这样的drawable作为图像显示。
那么把这个drawable设置为占位图,然后把URI设置为null。
前言
最后更新于:2022-04-01 02:24:01
Fresco是一个强大的系统用于在Android应用中展示图片,它能够从网络、本地存储和本地资源中加载图片。它拥有三级缓存,Fresco在显示方面是用了Drawees,可以显示占位符,直到图片加载完成。
> 本教程内容来源于 [fresco 中文网](http://fresco-cn.org/)
> 英文官网Fresco:[http://frescolib.org/](http://frescolib.org/)
Fresco 是一个强大的系统用于在 Android 应用中展示图片,它能够从网络、本地存储和本地资源中加载图片。它拥有三级缓存,Fresco 在显示方面是用了 Drawees,可以显示占位符,直到图片加载完成。
Fresco 是一个强大的图片加载组件。
Fresco 中设计有一个叫做 image pipeline 的模块。它负责从网络,从本地文件系统,本地资源加载图片。为了最大限度节省空间和 CPU 时间,它含有 3 级缓存设计(2 级内存,1 级文件)。
Fresco 中设计有一个叫做 Drawees 模块,方便地显示 loading 图,当图片不再显示在屏幕上时,及时地释放内存和空间占用。
Fresco 支持 Android2.3(API level 9) 及其以上系统。
## 特性
### 内存管理
一个没有未压缩的图片,即 Android 中的 Bitmap,占用大量的内存。大的内存占用势必引发更加频繁的 GC。在 5.0 以下,GC 将会显著地引发界面卡顿。
在 5.0 以下系统,Fresco 将图片放到一个特别的内存区域。当然,在图片不显示的时候,占用的内存会自动被释放。这会使得 APP 更加流畅,减少因图片内存占用而引发的 OOM。
Fresco 在低端机器上表现一样出色,你再也不用因图片内存占用而思前想后。
### 图片的渐进式呈现
渐进式的 JPEG 图片格式已经流行数年了,渐进式图片格式先呈现大致的图片轮廓,然后随着图片下载的继续,呈现逐渐清晰的图片,这对于移动设备,尤其是慢网络有极大的利好,可带来更好的用户体验。
Android 本身的图片库不支持此格式,但是 Fresco 支持。使用时,和往常一样,仅仅需要提供一个图片的 URI 即可,剩下的事情,Fresco 会处理。
### Gif 图和 WebP 格式
是的,支持加载 Gif 图,支持 WebP 格式。
#### 图像的呈现
Fresco 的 Drawees 设计,带来一些有用的特性:
- 自定义居中焦点(对人脸等图片显示非常有帮助)
- 圆角图,当然圆圈也行。
- 下载失败之后,点击重现下载
- 自定义占位图,自定义overlay, 或者进度条
- 指定用户按压时的overlay
#### 图像的加载
Fresco 的 image pipeline 设计,允许用户在多方面控制图片的加载:
- 为同一个图片指定不同的远程路径,或者使用已经存在本地缓存中的图片
- 先显示一个低解析度的图片,等高清图下载完之后再显示高清图
- 加载完成回调通知
- 对于本地图,如有EXIF缩略图,在大图加载完成之前,可先显示缩略图
- 缩放或者旋转图片
- 处理已下载的图片
- WebP 支持