Fresco进阶用法
Fresco 之 DataSource
https://www.fresco-cn.org/docs/datasources-datasubscribers.html
静态图 DataSource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void getShareBitmapFromDataSource(Context context, final String url, final ShareFrescoListener listener) {
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url))
.setProgressiveRenderingEnabled(true)
.build();
DataSource<CloseableReference<CloseableImage>> dataSource = Fresco
.getImagePipeline()
.fetchDecodedImage(imageRequest, context);
dataSource.subscribe(new BaseBitmapDataSubscriber() {
@Override
public void onNewResultImpl(@Nullable final Bitmap bitmap) {
// ...
}
@Override
public void onFailureImpl(DataSource dataSource) {
// 保存失败处理
if (listener != null) {
String msg = Log.getStackTraceString(dataSource.getFailureCause());
LogUtil.w(TAG, "getShareBitmapFromDataSource onFailureImpl :" + msg);
listener.onFail(msg);
}
}
}, CallerThreadExecutor.getInstance());
}
动图 DataSource
通过操作 InputStream
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public abstract static class BaseGifDataSubscriber extends BaseDataSubscriber<CloseableReference<PooledByteBuffer>> {
@Override
protected final void onNewResultImpl(DataSource<CloseableReference<PooledByteBuffer>> dataSource) {
if (!dataSource.isFinished()) {
float progress = dataSource.getProgress();
onFailed(progress, "dataSource is not finished!");
return;
}
boolean hasResult = dataSource.hasResult();
if (!hasResult) {
float progress = dataSource.getProgress();
onFailed(progress, "dataSource has not result!");
return;
}
CloseableReference<PooledByteBuffer> bufferCloseableReference = dataSource.getResult();
if (bufferCloseableReference == null) {
float progress = dataSource.getProgress();
onFailed(progress, "PooledByteBuffer CloseableReference is null!");
return;
}
final PooledByteBuffer pooledByteBuffer = bufferCloseableReference.get();
try {
onGifResult(new PooledByteBufferInputStream(pooledByteBuffer));
} finally {
CloseableReference.closeSafely(bufferCloseableReference);
}
}
@Override
protected final void onFailureImpl(DataSource<CloseableReference<PooledByteBuffer>> dataSource) {
String msg = Log.getStackTraceString(dataSource.getFailureCause());
onFailed(dataSource.getProgress(), msg);
}
protected abstract void onGifResult(@NonNull InputStream pooledByteBufferInputStream);
protected abstract void onFailed(float progress, @NonNull String msg);
}
Resizing&Scale
Resize
Resize 并不改变原始图片,它只在解码前修改内存中的图片大小。
如果要 resize,创建 ImageRequest 时,提供一个 ResizeOptions :
Resize 有以下几个限制:
- 目前,只有 JPEG 图片可以修改尺寸。
- 对于产生的图片的尺寸,只能粗略地控制。图片不能修改为确定的尺寸,只能通过支持的修改选项来变化。这意味着即使是修改后的图片,也需要在展示前进行 scale 操作。
- 只支持以下的修改选项: N / 8,1 <= N <= 8
- Resize 是由软件执行的,相比硬件加速的 scale 操作较慢。
- the actual resize is carried out to the nearest 1/8 of the original size
- it cannot make your image bigger, only smaller (not a real limitation though)
ResizeOptions
Fresco 的加载图片,是将整个图片记载到内存中的。对于大图片官网建议 Resize,Fresco 的三级缓存机制,第三级是文件缓存,第二级是 Fresco 偷 android 系统的缓存存放原始图片,从第二级解码得到显示图片的一级内存,就是视觉上看到的 ImageView 图片都在一级内存。
在解码到一级内存中时,我们对图片 Resize:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 解码前修改内存中的图片大小
* @param uri 文件的uri
* @param draweeView 显示的imageview
*/
public void showThumbNail(Uri uri, SimpleDraweeView draweeView){
int width = ScreenUtils.dp2px(context,119);
int height = ScreenUtils.dp2px(context,119);
LogUtils.e("http",width+"_"+height);
//图片请求
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setResizeOptions(new ResizeOptions(width, height)).build();
//图片请求设置显示控制器
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(draweeView.getController())
.setControllerListener(new BaseControllerListener<ImageInfo>())
.build();
draweeView.setController(controller);
}
新问题出现:图片显示不全(具体表现为显示为纯白色,或纯黑色)
图片的 Resize 操作只适合对 jpeg 图片有效
官网提供了第二种下采样的方法:
向下采样 (Downsampling)
如果开启该选项,pipeline 会向下采样你的图片,代替 resize 操作。你仍然需要像上面那样在每个图片请求中调用 setResizeOptions 。
向下采样在大部分情况下比 resize 更快。除了支持 JPEG 图片,它还支持 PNG 和 WebP(除动画外) 图片。
但是目前还有一个问题是它在 Android 4.4 上会在解码时造成更多的内存开销(相比于 Resizing)。这在同时解码许多大图时会非常显著,我们希望在将来的版本中能够解决它并默认开启此选项。
1
.setDownsampleEnabled(true)
此处记得,不是 setDownsampleEnabled(true)
就可以了,还得调用之前的 Resize 方法,人家文档也说的很明白了,在初始化得时候,进行下采样就能支持 png,webp:
1
2
3
4
5
6
//下采样
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
.setDownsampleEnabled(true)
.build();
//初始化
Fresco.initialize(this, config);
Ref
- 下采样 +Resize 能解决 Fesco 加载大量图片的滑动卡顿和内存溢出问题.
https://github.com/facebook/fresco/issues/1343
Fresco 之 Postprocessor
https://www.fresco-cn.org/docs/modifying-image.html
有时,我们想对从服务器下载,或者本地获取的图片做些修改,比如在某个坐标统一加个网格什么的。你可以使用 Postprocessor
,最好的方式是继承 BasePostprocessor
。
高斯模糊
IterativeBoxBlurPostProcessor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 以高斯模糊显示。
*
* @param draweeView View。
* @param url url.
* @param iterations 迭代次数,越大越魔化。
* @param blurRadius 模糊图半径,必须大于0,越大越模糊。
*/
public static void loadImage(String url, SimpleDraweeView draweeView, int iterations, int blurRadius) {
try {
Uri uri = Uri.parse(url);
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setPostprocessor(new IterativeBoxBlurPostProcessor(iterations, blurRadius))// iterations, blurRadius
.build();
AbstractDraweeController controller = Fresco.newDraweeControllerBuilder()
.setOldController(draweeView.getController())
.setImageRequest(request)
.build();
draweeView.setController(controller);
} catch (Exception e) {
e.printStackTrace();
}
}
使用 Fresco 注意
1、不支持 wrap_content
Drawee 必须声明 android:layout_width 和 android:layout_height。如果没有在 XML 中声明这两个属性,将无法正确加载图像。
Drawees 不支持 wrap_content 属性。
所下载的图像可能和占位图尺寸不一致,如果设置出错图或者重试图的话,这些图的尺寸也可能和所下载的图尺寸不一致。
如果大小不一致,假设使用的是 wrap_content,图像下载完之后,View 将会重新 layout,改变大小和位置。这将会导致界面跳跃。
只有希望显示固定的宽高比时,可以使用 wrap_content。
2、Fresco 初始化要在 Activity 的 setContentView() 之前
1
Fresco.initialize(Context);
不然会报错:
1
android.view.InflateException: Binary XML file line # 7: Error inflating class com.facebook.drawee.view.SimpleDraweeView
3、Fresco 的 ControllerListener 回调
如果 DraweeView 不显示,完成不会回调?
4、使用 Fresco OOM 问题
如果加载的高清图,不进行缩放,有可能 OOM。
Fresco 默认并不支持根据 View 的尺寸去缩放图片,需要手动去缩放,用 ResizeOptions。
图片缩放 -ResizeOptions
http://frescolib.org/docs/resizing.html
解决:根据 View 的尺寸缩放图片 (ResizeOptions)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private DraweeController getDraweeController(DraweeView targetView, String url) {
Uri uri = Uri.parse(url);
int width = targetView.getWidth();
int height = targetView.getHeight();
if (width <= 0) {
width = ScreenUtil.getScreenWidth(getActivity());
}
if (height <= 0) {
height = ScreenUtil.getScreenHeight(getActivity());
}
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
//根据View的尺寸放缩图片
.setResizeOptions(new ResizeOptions(width, height))
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setOldController(targetView.getController())
.setImageRequest(request)
.setControllerListener(new ImageLoadingDetailListener())
.setAutoPlayAnimations(true)
.build();
return controller;
}
其他相关:
缩放和旋转图片
https://www.fresco-cn.org/docs/resizing-rotating.html
使用 ResizeOptions 后仍然会 OOM
有时你会发现当即使用了 ResizeOptions 后,特别是加载本地多图时仍然会发生 OOM,或加载页面会异常的卡顿忙,或者有的图片加载不出来,黑屏。是 ResizeOptions 的 bug 吗?答案肯定不是,造成的原因就在于你的使用姿势不正确!
- ResizeOptions 的参数设置有问题
设置的裁剪宽高值仍然太大了,并没有很好的适应的你需求 - ResizeOptions 默认只支持 JPEG 格式的图片
通过设置etDownsampleEnabled(true)
支持 png 和 webp 图片格式处理
1
2
3
4
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
.setDownsampleEnabled(true)
.build();
Fresco.initialize(this, config);
- 在开启 AndroidManifest.xml 中开启
android:largeHeap="true"
最终处理 Fresco OOM
短时间内处理大量图片,
- 初始化,一般在 Application 中
1
2
3
4
5
6
7
8
9
private void initFresco() {
// 默认支持jpg,添加支持png、webp
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
.setDownsampleEnabled(true)
.setBitmapMemoryCacheParamsSupplier(new FrescoCacheParams(activityManager))
.build();
Fresco.initialize(this, config);
}
FrescoCacheParams 定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class FrescoCacheParams implements Supplier<MemoryCacheParams> {
private ActivityManager activityManager;
public FrescoCacheParams(ActivityManager activityManager) {
this.activityManager = activityManager;
}
@Override
public MemoryCacheParams get() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
int cacheSize = getMaxCacheSize();
LogUtil.i("fresco", "fresco cache size = " + cacheSize);
return new MemoryCacheParams(cacheSize, 1, 1, 1, 1);
} else {
return new MemoryCacheParams(
getMaxCacheSize(),
256,
Integer.MAX_VALUE,
Integer.MAX_VALUE,
Integer.MAX_VALUE);
}
}
private int getMaxCacheSize() {
final int maxMemory = Math.min(activityManager.getMemoryClass()
* ByteConstants.MB, Integer.MAX_VALUE);
if (maxMemory < 32 * ByteConstants.MB) {
return 4 * ByteConstants.MB;
} else if (maxMemory < 64 * ByteConstants.MB) {
return 6 * ByteConstants.MB;
} else {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD) {
return 8 * ByteConstants.MB;
} else {
return maxMemory / 6;
}
}
}
}
- 缩放图片,通过 ResizeOptions
1
2
3
4
5
6
7
8
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setResizeOptions(new ResizeOptions(width, height))
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setOldController(getController())
.setImageRequest(request)
.build();
setController(controller);
- Drawee 移出屏幕时,就释放
1
2
3
4
5
6
7
8
// calling on each Drawee, once it moves out of visible area
if (getController() != null) {
getController().onDetach();
}
// once a image moves out of display area, we are evicting it out of pipeline as well.
ImagePipeline imagePipeline = Fresco.getImagePipeline();
Uri uri = Uri.parse(url);
imagePipeline.evictFromMemoryCache(uri);
Fresco OOM Ref
- 图片框架使用问题之二: Fresco 框架导致的 OOM
http://www.jianshu.com/p/160c79d61c21
其他总结:
- Fresco 最强图片加载框架详解及使用 – 使用方法很详细
http://blog.csdn.net/android_yyf/article/details/73549538 - 你所不知道的 fresco 使用集锦
http://www.jianshu.com/p/8ff81be83101 - Fresco 之强大之余的痛楚 – 源码解析
http://www.jianshu.com/p/5364957dcf49 - 一种使用 Fresco 非侵入式加载图片的方式 - 自定义 fresco 加载实现图片选择
https://fucknmb.com/2017/07/27/一种使用Fresco非侵入式加载图片的方式/
用 Fresco 的 DataSource 分享长图模糊
- 问题:
用 Fresco 的 DataSource 加载长图模糊 - 分析
由于 Fresco 的 DataSource 加载图片,会将图片解码、压缩,导致分享出去的图片很模糊,
通过 ResizeOptions 将图片设置大一点就可以了 - 解决:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void getShareBitmapFromDataSource(Context context, final String url) {
ResizeOptions resizeOptions = new ResizeOptions(2048, 2048, 1024*1024);
ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url))
.setProgressiveRenderingEnabled(true)
.setResizeOptions(resizeOptions)
.build();
DataSource<CloseableReference<CloseableImage>> dataSource = Fresco
.getImagePipeline()
.fetchDecodedImage(imageRequest, context);
dataSource.subscribe(new BaseBitmapDataSubscriber() {
@Override
public void onNewResultImpl(@Nullable final Bitmap bitmap) {
}
@Override
public void onFailureImpl(DataSource dataSource) {
}
}, CallerThreadExecutor.getInstance());
}
Fresco 的圆角与 GIF 不能兼得的解决方案
https://www.jianshu.com/p/fac1d303f10b
关于 Fresco 加载图片报 PoolSizeViolationException 异常的问题
1
com.facebook.imagepipeline.memory.BasePool$PoolSizeViolationException: Pool hard cap violation? Hard cap = 402653184 Used size = 402493656 Free size = 0 Request size = 2856600
我是在 RecyclerView 的 item 里面处理 SimpleDraweeView,不过在 RecyclerView 的外面包了一层 NestedScrollView。如果把 NestedScrollView 去掉不会报上名的错误。
这个错误的原因是没有释放图像,导致它们填满整个池,直到图像过多而引发异常。
https://github.com/facebook/fresco/issues/1600
RecyclerView does not recycle its items when its height is wrap_content and it is a child of NestedScrollView
java.lang.UnsatisfiedLinkError
https://github.com/facebook/fresco/issues/1552
https://github.com/facebook/fresco/issues/2049
Ref
- 【ReactNative】关于 32 位和 64 位 SO 库混合引入 Crash 解决方案
- java.lang.UnsatisfiedLinkError 的解决办法
https://blog.zzzmode.com/2016/10/22/android-unsatisfiedlinkerror-fix/
用 ReLinker
- 系统应用集成过程中的一些坑
https://www.jianshu.com/p/d290442a0135