Szhangbiao's blog

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

0%

低端设备上GIF依次播放实践

GIF 播放是我司应用市场类应用的一个重要的功能,之前做图片加载框架技术选型时也是以加载 GIF 的性能为基准来进行选择的,GIF 播放功能在手机端比较常见,通常交给支持 GIF 的图片加载框架处理就行,但是对于开发板端而言,这类功能对内存和 CPU 有较大的挑战,稍微不注意就很容易造成 ANR 和应用崩溃。

背景

GIF播放主要是为了更加生动的展示广告,吸引用户查看与点击,增加推荐位的曝光率等,属于我司规划了很久的一个功能,但是为了应对杂乱无章的满屏的GIF播放以及其带来的高内存和CPU高负载的挑战,GIF播放需加以控制使其顺序播放,同时,之前的性能优化在Android 6.0及以下使用Fresco加载,其它的使用Glide加载,这一情况更加增加了该功能的复杂性,再者图片的父容器中包含RecyclerViewViewPager,也要兼顾触屏滑动时的用户体验。

构思与实现

整个流程分为辨别GIF、加入队列、确定顺序,播放控制、特殊情况处理、兼容性处理和其他的细节处理等步骤。

基本情况

页面总体上的一个架构是ViewPager+Fragemnts的结构,每一个Fragment里是用RecyclerView展示的是类图片瀑布流,这是一种比较常见的平板或TV上的主页布局结构,其中部分位置根据后台数据的配置展示是GIF图片。当满足当前屏幕里的所有GIF都加载成功则开始按位置的顺序依次播放。

影响因素

GIF播放整个流程中涉及到很多影响播放控制的点,这里我们把它们一一列举出来:

  • FragmentonResumeonPauseonDestory方法
  • GIF加载的的状态,像开始、成功和失败等状态
  • ViewPagerRecyclerView的滑动状态
  • 影响图片控件状态的attachedToWindowdetachedFromWindow方法

以上这些点或推进或影响整体的GIF播放控制

代码封装

根据以上列举的点,我们很轻易的得出播放控制的接口类

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
public interface IGifPlayController {

void pageResume(String pageId);

void pageResume(String pageId, RecyclerView recyclerView, boolean turnBackFromSecondPage);

void pagePause(String pageId);

void pagePause(String pageId, RecyclerView recyclerView);

void pageDestroy(String pageId);

void notifyGifLoadStart(String uniqueTag, String gifUrl);

void notifyLoadSuccess(String uniqueTag, SimpleDraweeView imageView);

void notifyLoadSuccess(String uniqueTag, ImageView imageView, Bitmap firstFrame);

void notifyLoadFail(String uniqueTag);

void notifyScrollStateChanged(int newState);

void attachToWindow(IMarketView marketView);

void attachToWindow(List<IMarketView> marketViews);

void detachFromWindow(String uniqueTag);

void detachFromWindow(List<String> uniqueTags);
}

部分方法及参数说明:

  • resumepause的两个方法分别对应可上下滑动的Fragment和整页显示的Fragment
  • uniqueTag是在AdapteronBindViewHolder方法里调用View.setTag()设置的,其中包含了pageIdposition相对位置信息
  • attachedToWindowdetachedFromWindow的两个方法对应的则是RecyclerView里不同的viewType布局类型
  • notifyScrollStateChanged方法则只有newState等于SCROLL_STATE_IDLE时重新播放当前屏幕里展示的GIF

出于性能的考虑,目前加载GIF时先静态加载GIF的第一帧,等到真正的轮到当前GIF播放时再动态加载GIF并播放。
为此还定义了一个播放状态的实体类

1
2
3
4
5
6
7
8
9
10
11
12
public class GifPlayDTO {
protected final String uniqueTag;
protected final String gifUrl;
// 用于GIF加载间隙图片闪烁问题
protected Bitmap previousFrame;
protected boolean isDownloaded;
protected boolean isPlayed;
protected boolean isActive;
// 根据 uniqueTag 得到相对位置,从而计算该控件是否完整的显示在屏幕中
private int tryPosition = -1;
...
}

对于兼容性的处理则是根据FrescoGlide的特性,分别实现IGifPlayController接口,虽然加载图片的 API 不一样,但总体流程的处理上是一致的。

简单图示

当一个页面初次展示GIF时,Controller的大致处理流程和下图一致

image

虽然一些细节不能体现出来,但总体上还是可以的

总结

其实使用GIF来展示动图并不是第一优选,像LottieWebP以及SVGA等都可以展示动图,之所以没有考虑这些方案有以下原因:

  • 没有相应的配套,需要设计和后端人员的支持,特别对设计的要求比较高
  • 国内公司的技术路径依赖,GIF播放是经过实践且成熟的方案,其它三个方案并没有在开发板上得到过验证,可能有兼容或其它的一些问题
  • 其它三个方案一样要做顺序播放控制,且不一定与现有方案兼容