文章

Fresco进阶用法

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 有以下几个限制:

  1. 目前,只有 JPEG 图片可以修改尺寸。
  2. 对于产生的图片的尺寸,只能粗略地控制。图片不能修改为确定的尺寸,只能通过支持的修改选项来变化。这意味着即使是修改后的图片,也需要在展示前进行 scale 操作。
  3. 只支持以下的修改选项: N / 8,1 <= N <= 8
  4. Resize 是由软件执行的,相比硬件加速的 scale 操作较慢。
  5. the actual resize is carried out to the nearest 1/8 of the original size
  6. 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);
}

新问题出现:图片显示不全(具体表现为显示为纯白色,或纯黑色)

om6v2

图片的 Resize 操作只适合对 jpeg 图片有效
官网提供了第二种下采样的方法:

向下采样 (Downsampling)

如果开启该选项,pipeline 会向下采样你的图片,代替 resize 操作。你仍然需要像上面那样在每个图片请求中调用 setResizeOptions 。
向下采样在大部分情况下比 resize 更快。除了支持 JPEG 图片,它还支持 PNG 和 WebP(除动画外) 图片。
但是目前还有一个问题是它在 Android 4.4 上会在解码时造成更多的内存开销(相比于 Resizing)。这在同时解码许多大图时会非常显著,我们希望在将来的版本中能够解决它并默认开启此选项。

1
.setDownsampleEnabled(true)

83on8

此处记得,不是 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 的 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 解决方案

https://blog.csdn.net/u013531824/article/details/53931307

  • java.lang.UnsatisfiedLinkError 的解决办法

https://blog.zzzmode.com/2016/10/22/android-unsatisfiedlinkerror-fix/
用 ReLinker

本文由作者按照 CC BY 4.0 进行授权