GIF 播放是我司应用市场类应用的一个重要的功能,之前做图片加载框架技术选型时也是以加载 GIF 的性能为基准来进行选择的,GIF 播放功能在手机端比较常见,通常交给支持 GIF 的图片加载框架处理就行,但是对于开发板端而言,这类功能对内存和 CPU 有较大的挑战,稍微不注意就很容易造成 ANR 和应用崩溃。
背景
GIF
播放主要是为了更加生动的展示广告,吸引用户查看与点击,增加推荐位的曝光率等,属于我司规划了很久的一个功能,但是为了应对杂乱无章的满屏的GIF
播放以及其带来的高内存和CPU
高负载的挑战,GIF
播放需加以控制使其顺序播放,同时,之前的性能优化在Android 6.0
及以下使用Fresco
加载,其它的使用Glide
加载,这一情况更加增加了该功能的复杂性,再者图片的父容器中包含RecyclerView
和ViewPager
,也要兼顾触屏滑动时的用户体验。
构思与实现
整个流程分为辨别GIF
、加入队列、确定顺序,播放控制、特殊情况处理、兼容性处理和其他的细节处理等步骤。
基本情况
页面总体上的一个架构是ViewPager
+Fragemnts
的结构,每一个Fragment
里是用RecyclerView
展示的是类图片瀑布流,这是一种比较常见的平板或TV
上的主页布局结构,其中部分位置根据后台数据的配置展示是GIF
图片。当满足当前屏幕里的所有GIF
都加载成功则开始按位置的顺序依次播放。
影响因素
在GIF
播放整个流程中涉及到很多影响播放控制
的点,这里我们把它们一一列举出来:
Fragment
的onResume
、onPause
和onDestory
方法GIF
加载的的状态,像开始、成功和失败等状态ViewPager
和RecyclerView
的滑动状态- 影响图片控件状态的
attachedToWindow
和detachedFromWindow
方法
以上这些点或推进或影响整体的GIF
播放控制
代码封装
根据以上列举的点,我们很轻易的得出播放控制的接口类
1 | public interface IGifPlayController { |
部分方法及参数说明:
resume
和pause
的两个方法分别对应可上下滑动的Fragment
和整页显示的Fragment
uniqueTag
是在Adapter
的onBindViewHolder
方法里调用View.setTag()
设置的,其中包含了pageId
和position
相对位置信息attachedToWindow
和detachedFromWindow
的两个方法对应的则是RecyclerView
里不同的viewType
布局类型notifyScrollStateChanged
方法则只有newState
等于SCROLL_STATE_IDLE
时重新播放当前屏幕里展示的GIF
出于性能的考虑,目前加载GIF
时先静态加载GIF
的第一帧,等到真正的轮到当前GIF
播放时再动态加载GIF
并播放。
为此还定义了一个播放状态的实体类
1 | public class GifPlayDTO { |
对于兼容性的处理则是根据Fresco
和Glide
的特性,分别实现IGifPlayController
接口,虽然加载图片的 API 不一样,但总体流程的处理上是一致的。
简单图示
当一个页面初次展示GIF
时,Controller
的大致处理流程和下图一致
虽然一些细节不能体现出来,但总体上还是可以的
总结
其实使用GIF
来展示动图并不是第一优选,像Lottie
、WebP
以及SVGA
等都可以展示动图,之所以没有考虑这些方案有以下原因:
- 没有相应的配套,需要设计和后端人员的支持,特别对设计的要求比较高
- 国内公司的技术路径依赖,
GIF
播放是经过实践且成熟的方案,其它三个方案并没有在开发板上得到过验证,可能有兼容或其它的一些问题 - 其它三个方案一样要做顺序播放控制,且不一定与现有方案兼容