Szhangbiao's blog

记录一些让自己可以回忆的东西

0%

云青来图片框架封装(Fresco和Glide)

Fresco 其实是我司长期以来默认使用的图片加载框架,这个情况在 4 月份我在做新项目的技术选型的时候被打破了,那时我针对 Glide vs Fresco 的性能做了对比,在加载 GIF 的情况下,Glide + GifDrawable 三方库在内存和 CPU 上要优于 Fresco,所以领导就拍板决定在新版项目里使用 Glide, 但这个情况在后面发生了一些变化。

背景

我在最开始开发的时候其实是想过在Android 4.4上使用 Fresco,Android 5.0及以上使用 Glide,在代码层面也做了图片加载框架的封装,只不过领导认为太过麻烦给否决了。
后期在准对机顶盒和投影仪这些设备做版本开发时,发现内存使用没有很高就会出现内存波动,再加上我对 Fresco 持保留态度,知道 Fresco 在加载图片的时候会有效的利用匿名内存,会在一定程度上提高内存的使用率,所以就决定继续完善之前的图片加载框架的封装。

代码封装

因为 Fresco 是使用SimpleDraweeView这个自定义 View 来加载图片的,所以为了能够同时兼容 ImageView,我们需要先封装一个自定义 ViewGroup来同时承载ImageView或者 Fresco 的SimpleDraweeView

首先使用一个接口来承担对外的共同操作,这样可以对模块外隐藏内部实现细节。

1
2
3
4
public interface IMarketView {

...
}

然后依据不同场景来实现不同的自定义ViewGroup,这里拿其中一个来举例:

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
43
44
public class MarketIconView extends FrameLayout implements IMarketView {

...

public MarketIconView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAttrs(context, attrs);
}

...

private void initAttrs(Context context, AttributeSet attrs) {
...
if (ImageLoaderUtil.shouldUseGlideEngine()) {
addView(buildShapeableImageView(), new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
} else {
addView(buildFrescoImageView(), new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
...
}

private ImageView buildShapeableImageView() {
ShapeableImageView normalImageView = new ShapeableImageView(getContext());
normalImageView.setShapeAppearanceModel(normalImageView.getShapeAppearanceModel()
.toBuilder().setAllCorners(CornerFamily.ROUNDED, mCornerSize) //设置所有圆角
.build());
normalImageView.setStrokeColor(ColorStateList.valueOf(Color.TRANSPARENT));
normalImageView.setScaleType(ImageView.ScaleType.FIT_XY);
return normalImageView;
}

private SimpleDraweeView buildFrescoImageView() {
SimpleDraweeView frescoImageView = new SimpleDraweeView(getContext());
frescoImageView.setScaleType(ImageView.ScaleType.FIT_XY);
RoundingParams roundingParams = RoundingParams.fromCornersRadius(mCornerSize);
frescoImageView.setHierarchy(new GenericDraweeHierarchyBuilder(getResources())
.setRoundingParams(roundingParams)
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_XY)
.setFadeDuration(0)
.build());
return frescoImageView;
}
}

然后就是修改之前定义的ImageLoader接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface ImageLoader {

void init(@NonNull Context context);

void loadImageWithOptions(@NonNull IMarketView viewParent, @NonNull String url, LoadOptions options);

void loadImageWithOptions(Fragment fragment, @NonNull IMarketView viewParent, @NonNull String url, LoadOptions options);

void loadImageWithOptions(Fragment fragment, @NonNull IMarketView viewParent, @NonNull String url, LoadOptions options, OnImageLoadListener loadListener);

void clearImageLoad(Fragment fragment, @NonNull IMarketView viewParent);

void resumeRequests(Fragment fragment);

void pauseRequests(Fragment fragment);

void onTrimMemory(Context context, int level);

void clearMemoryCache(Context context);

void preloadImage(@NonNull String url);
}

这里使用了LoadOptions来封装一些加载图片时的配置,这样在不同的场景下,可以对LoadOptions进行配置,从而达到不同的效果,这里就不再赘述了。

后面就是对ImageLoader的实现,我们基本上在GlideImageLoaderFrescoImageLoader里实现了这个接口。
最后再把 xml 里的<ImageView>换成IMarketView对应的实现即可。

总结

其实这样封装是带来了一些便利性,但是也为每一个要加载图片的 View 套上一层 FrameLayout,这样就会有一定的性能损耗,但是这个损耗可以被GlideFresco所抵消,所以这个是个值得的尝试。