前言
Fresco
是一个强大的系统用于在
Android
应用中展示图片,它能够从网络、本地存储和本地资源中加载图片。它拥有三级缓存
,Fresco
在显示方面是用了
Drawees
,可以显示占位符,直到
图片加载完成。
本教程内容来源于
fresco
中文网
英文官网
Fresco
:
http://frescolib.org/
更新日期
更新内容
2015-04-01
Fresco
中文版发布
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
支持
>
本教程内容来源于:
http://fresco-cn.org
Fresco
官网:
http://frescolib.org/
更新日期
更新内容
2015-04-08
Fresco
中文版发布
1
配置和使用
下载
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
,
负责图片的显示。
一般情况下,使用
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
注意,只有图片资源才能使用在
Image pipeline
中,比如
(PNG)
。其他资源类型,比如字符串,或者
XML Drawable
在
Image pipeline
中没有意义。所以加载的资源不支持这些类型。
像
ShapeDrawable
这样声明在
XML
中的
drawable
可能引起困惑。注意到这毕竟不是图片,如果想把这样的
drawable
作为图像显示。
那么把这个
drawable
设置为占位图,然后把
URI
设置为
null
。
2
DRAWEE
指南
在
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
:
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
默认缩放类型
:
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
的
各种效果配置
,其中一些是支持缩放类型的。
可用的缩放类型
类型
描述
center
居中,无缩放
centerCrop
保持宽高比缩小或放大,使得两边都大于或等于显示边界。居中显示。
focusCrop
同
centerCrop,
但居中点不是中点,而是指定的某个点
centerInside
使两边都在显示边界内,居中显示。
如果图尺寸大于显示边界,则保持长宽比缩小图片。
fitCenter
保持宽高比,缩小或者放大,使得图片完全显示在显示边界内。居中显示
fitStart
同上。但不居中,和显示边界左上对齐
fitEnd
同
fitCenter
,
但不居中,和显示边界右下对齐
fitXY
不保存宽高比,填充满显示边界
none
如要使用
tile mode
显示
,
需要设置为
none
这些缩放类型和
Android
ImageView
支持的缩放类型几乎一样
.
唯一不支持的缩放类型是
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
2.
圆角
-
设置
roundedCornerRadius
设置圆角时,支持
4
个角不同的半径。
XML
中无法配置,但可在
Java
代码中配置。
设置圆角
可使用以下两种方式
:
1.
默认使用一个
shader
绘制圆角,但是仅仅占位图所要显示的图有圆角效果。失败示意图和重下载示意图无圆角效果。
2.
叠加一个
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
。
2.
那么
1
将不会解码,下载完成
4
个扫描时,解码一次。下个解码为扫描数为
6
3.
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
的访问。
如果有可用动画的话,可对动画进行灵活的控制
:
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.
从服务器下载小一些的图片
2.
显示时缩放图片
3.
调整图片尺寸大小
对于一个图片,如果服务器支持不同尺寸的缩略图,那么每次下载都选择尺寸最匹配的图片,这个不仅节省数据流量也节约本地储存和
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.
检查内存缓存,有如,立刻返回。这个操作是实时的。
2.
检查未解码的图片缓存,如有,解码并返回。
3.
检查磁盘缓存,如果有加载,解码,返回。
4.
下载或者加载本地文件。调整大小和旋转(如有),解码并返回。对于网络图来说,这一套流程下来是最耗时的。
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
就会丢失,也就无法取到
image pipeline
的任何图像了。
使用
DraweeView
时,请不要使用任何
ImageView
的属性
在后续的版本中,
DraweeView
会直接从
View
派生。任何属于
ImageView
但是不属于
View
的方法都会被移除。
3
IMAGE PIPELINE
指南
Image Pipeline
介绍
Image pipeline
负责完成加载图像,变成
Android
设备可呈现的形式所要做的每个事情。
大致流程如下
:
1.
检查内存缓存,如有,返回
2.
后台线程开始后续工作
3.
检查是否在未解码内存缓存中。如有,解码,变换,返回,然后缓存到内存缓存中。
4.
检查是否在文件缓存中,如果有,变换,返回。缓存到未解码缓存和内存缓存中。
5.
从网络或者本地加载。加载完成后,解码,变换,返回。存到各个缓存中。
既然本身就是一个图片加载组件,那么一图胜千言。
图片
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
个线程用于网络下载
2.
两个线程用于磁盘操作
:
本地文件的读取,磁盘缓存操作。
3.
两个线程用于
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
,
有些相似,都是异步计算的结果。
不同点在于,数据源对于一个调用会返回一系列结果,
Future
只返回一个。
提交一个
Image request
之后,
Image pipeline
返回一个数据源。从中获取数据需要使用数据订阅者
(DataSubscriber).
当你仅仅需要
Bitmap
如果你请求
Image pipeline
仅仅是为了获取一个
Bitmap
,
对象。你可以利用简单易用的
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
函数范围之外的任何变量。订阅者执行完操作之后,
image pipeline
会回收这个
bitmap
,释放内存。在这个函数范围内再次使用这个
Bitmap
对象进行绘制将会导致
IllegalStateException
。
通用的解决方案
如果你就是想维持对这个
Bitmap
对象的引用,你不能维持纯
Bitmap
对象的引用,可以利用
可关闭的引用
(closeable references)
和
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
的东西。
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
过的引用。
}
4
第三方类库
自定义网络加载
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+"
}
配置
Image pipeline
这时也有一些不同,不再使用
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
和配置
image pipeline
的特性,使用其他
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
配合使用
依照
源码
作为例子,其他
Image Loader
也是可以和
Drawee
配合使用的,但是没有我们还没有
Drawee
和其他
Image loader
的配合使用的补充实现。
更多信息请访问
http://wiki.jikexueyuan.com/project/fresco/




