送礼物作为观众打赏扶助主播的一种艺术, 也是直播应用的一大获益来自,
每个直播平台都饱含送礼这一效率, 并且都把礼物动画效果做的特别炫酷.
如此的动画效果再搭配漂亮的女生或帅哥主播的一句”谢谢某某某送的大飞机~”,
是不是思想都有点小震动, 感觉刹那间改为了整场的问题?

正文重要描述的就是大红包动效的实现. 全文共3000字左右,
大概阅读时间为5~12分钟.

先放上按系列帧播放方案实现的卡通片引擎FXAnimationEngine,
Demo中实现了直播间礼物队列、礼物配置、礼物列表,
另外还分别用动画引擎与原生Core Animation去播放连串帧动画以做相比较.

接下来国际惯例, 上两张图

梦幻城堡

天使


一、直播应用礼物动画的宽泛方案

仅个人了然, 实现iOS侧动画配置化常见方案有如下三种:

iOS方案 优点 缺点
Core Animation(此处不计CAKeyFrameAnimation) 效果流畅逼真 安卓需重新实现; 配置化成本高, 需自定义模型、协议、转换方法等(iOS侧已有现成工具, 某几家直播公司想必也有自己的动画配置化工具); 不解决动态配置问题, 则只能随包更新.
序列帧播放(CAKeyframeAnimation、CADisplaylink、ImageView等) 设计哥工具可直接导出动画序列帧图片, 简单易用; 多平台兼容 效果略差; 图片帧数多易导致资源大
Cocos2d-x 效果好; 多平台兼容 学习成本; 相应动画制作工具; 必须引入Cocos2d库;
Lottie 横跨三端, iOS, Android, React Native. 设计师可以完全按照自己的想法设计. 无需考虑实现这一块. 内存占用? 作者本人尚未使用过, 不敢妄自评论

可以见到, 系列帧播放方案是其中最简易易行的一个. 在我看来,
花椒直播用的即是那套方案, 他们每一个动画片,
都会相应一个布局文件config.ini及对相应动画的所有连串帧图片.

感兴趣的意中人可以移至最后一有的礼物资源的下载策略、资源目录结构等连锁内容,
更提出尝试去追究一下花椒、映客等主流直播应用的bundle目录以及document中的资源.

二、体系帧播放方案实施

2.1 实现模式

队列帧播放动画一方案的有血有肉落实必须能够满意以下要求:

  1. 图表显示: CALayer、UIImageView
  2. 按时间间隔逐帧播放:
    CAKeyframeAnimation、UIImageView、定时器类(CADisplayLink、NS提姆er、dispatch_source_t)+切换关键帧逻辑
  3. 提供具有体系帧播放完的事件: CAAnimationDelegate、CATransaction
    CompletionBlock、定时器类+回调触发逻辑

结缘情势很多, 比如: CALayer+CAKeyframeAnimation+delegate,
UIImageView+定时器, CALayer+定时器类等等.

大家先选定这一套组合展开实施: CALayer+CAKeyframeAnimation+delegate

// 伪代码
- (void)startAnimation {
    UIImage *frame = [UIImage imageWithContentsOfFile:...];
    NSArray<UIImage *> *frames = @[(id)frame.CGImage, ...];
    CAKeyframeAnimation *keyframeAnim = ...;
    keyframeAnim.contents = frames;
    ...
    keyframeAnim.delegate = self;
    [xxx.layer addAnimation:keyframeAnim forKey:@"xxx"];
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    // 触发动画播放结束(全部播放完、中途结束)回调
    ...
}

比方这里你已经下载了Demo, 可以打开Debug
Navigator(cmd+6)简单翻看内存增长或者留意Xcode
Instrument-Allocations中VM:ImageIO_PNG_Data一项,
就会看出有内存增长波峰. 而且体系帧图片越多, 波峰越显然.

这就是说任何方案是否出现了一致的题材吧? 是的, 其他方案一样会这么,
换成UIImageView自带的animationImages来做体系帧播放或是其他组成措施,
也现身内存激增的情形.

2.2 了然图片加载

在我们搞领会是什么导致内存激增前, 我们先领悟一下图形从磁盘加载,
到写入内存, 最后彰显到屏幕上个别都发出了什么. 大致分成如下步骤:

  1. 为磁盘中的图片成立映射
  2. IO操作读取图片数据流
  3. 图片解码位图拷贝, 写入内存
  4. 硬件绘制渲染到屏幕

2.2.1 映射文件

当大家透过[UIImage imageWithContentsOfFile:]从磁盘加载图片数据流,
实际上只是为此图片创制了一个文件映射数据,
图片文件既没有真正被加载到内存,
更没有被解码成位图的款式可供Core
Animation传递给底层硬件举办渲染, 故此时内存并不会显然扩大,
也不会并发因为解码操作导致CPU使用增多的情形.
但从网络下载图片数据不包含在内.

简言之提及一下映射文件:

A mapped file uses virtual memory techniques to avoid copying
pages of the file into memory until they are actually needed.

直译就是一个辉映文件借助虚拟内存技术来避免当他们还并未当真使用到时就被拷贝到内存中.

上边来一组比较表达一下:

对照组一

- (void)test1 {
    UIImage *frame = [UIImage imageWithContentsOfFile:filePath];
    // 确保超出局部作用域后, 依旧保持对这个Image对象的强引用
    self.frame = frame;
}
// 待上方函数执行完后, 再查看内存使用情况

对待组二

- (void)test2 {
    UIImage *frame = [UIImage imageWithContentsOfFile:filePath];
    self.imageview.image = frame;
}

咱俩得以窥见相比较组二的内存占用彰着比对照组一要多.
即经过imageWithContentsOfFile:始建的UIImage对象后, 内存并没有显然增长,
等我们将该UIImage对象赋值给UIImageView的image属性后的某个时刻,
内存才出现分明增长.

此间再留多少个问题:

  1. 大家都理解imageWithName:艺术加载的图形, 会被系统缓存,
    那么首先次经过该办法开展如上五个对照组的尝试, 结果什么呢?
  2. 通过imageWithName:措施第2、3..n次加载同名图片时,
    加载的图片数据流会不会再也被解码? 期间CPU占用有没有扩充?
  3. 品味把成立的UIImage对象桥接赋值给CALayer的contents属性, 结果什么?

2.2.2 浅谈CALayer的隐式动画及作业

从上一节中,
大家发现当给UIImageView的image属性或CALayer的contents属性赋值Image对象后的某说话,
内存和CPU占用才会油可是生显著变化. 这是因为每五次Runloop循环, Core
Animation都会在其起头创造一个卡通事务,
在这一次Runloop截止时才去实施所有添加到该事情里的具有动画操作.
此刻图片才被解码加载入内存,
图片数据会被解码为渲染可用的bitmap数据.
一些息息相关细节可看我另一篇分享.

浅谈CALayer隐式动画及工作

2.3 解决内存激增问题

时下我们面临的题目是不管采纳何种实现方案, 在履行序列帧动画时,
所有图片都会被解码成为位图并载入内存中.

2.3.1 解压后的图样所占内存大小

图形解码后的格式为位图情势.
位图是由一组像素(pixel)组成的, 每一个像素就代表图片中的一个点.
比如大规模的JPEG, 以及PNG格式的图形文件都是位图图片.

咱俩还需要了然, JPEG和PNG图片实际上都是一种编码/压缩后的位图格式,
它们是不可以间接用来图片渲染的, 所以得先对其缩减的数额开展解码/解压缩操作.

这就是说一张解压后的位图其所占内存大小怎么总结呢?

此处虽然我们有一张32位的PNG格式图片, 其像素格式为RGBA四有些组成,
每部分占8位, 该图片尺寸为160px * 320px.

32位的图片意味着其每个像素占32位, 即4个字节.
又根据图片尺寸计算出总像素数量为 160*320 个像素.
所以该图片解码后所占内存大小就为 像素总数 * 单位像素的字节数
即 (160*320) * 4 / 1024 = 200 KB.

就此显而易见, 倘使一个行列帧动画有80张图纸, 200 * 80 / 1024 = 15.625
mb, 就会占有15mb的内存. 连串帧图片越多, 占用内存越大!

2.3.2 解决方案

这就是说有如何办法可以避免吗? 可否老是播放到哪一帧时就去加载那一帧的图片,
即每一回仅加载一张图纸到内存中. 这样当播放到下一张图片时,
上一张图纸已无其他引用, 系统本来会对其开展释放.

这就是最简便有效的一套方案.
可是大家鞭长莫及靠CAAnimation及其派生类CAKeyframeAnimation来实现这一方案,
因为具备的图形都会解码导致占用大量的内存.

但大家得以经过CADisplayLink来兑现该方案,
选CADisplayLink的因由是它比NS提姆(Tim)er精度要高很多,
正常状态下CADisplayLink的回调会在屏幕每便刷新时触发,
即一般1/60秒触发两次, 适合用来做UI的重绘,
由此得以透过它来周期性的交替关键帧图片, 从而达到播放动画的机能.
那么具体怎么办吗?

在CADisplayLink的回调中赢得两遍屏幕刷新的间隔时间,
通过不断的丰裕间隔时间来判定总的时间是不是业已满足下一帧的播报时刻,
即使大于下一帧的播报时刻就可以轮换为下一帧图片了,
直至最终一张关键帧也播放完成.

举个例证, 我们要在1秒内广播完一个蕴含5张关键帧图片的卡通片,
每张图纸的停留时间、切换时间如下图2.3.2.a所示.
所以第0秒的时候就先河彰显第一张关键帧, 直到1.0秒这一阵虎时, 动画播放截至.

图 2.3.2.a

此外, 假诺还索要更为优化,
大家得以出席图片异步解码、图片预加载逻辑等方案.

  • 异步图片解码, 图片解码是一项相比较耗时、相比较占CPU的操作,
    对于未解码的图形, 系统一般会在主线程对其进展解码,
    所以可以经过在异步线程举行图片强制解压缩, 从而不占用UI线程.
    关于图片解码的详情, 强烈推荐啄磨 iOS
    中图纸的解压缩
    .

  • 图片预加载, 这多少个就是为着更加节约上下文切换时间,
    即前后两张图纸切换的时间. 就是要形成当上一帧图片播放完时,
    我们毫不等下一张图纸解码完成后再举行图片的切换,
    而是可以平素从已解码图片的缓存队列中取出直接举办切换.
    预加载我个人认为实在首要就是阈值的最优拔取,
    可参照预加载与智能预加载一文.

  • 字节对齐(byte alignment)对Core
    Animation性能的熏陶

三、系列帧动画引擎源代码及Demo

FXAnimationEngine –
Github跳转

本着该Demo如今会另起一文特别介绍, 此处占坑, 等待跳转链接

四、礼物资源下载策略及资源目录结构

4.1 礼物资源下载策略

4.1.1 两种办法相比较

方式 基本思路 优点 缺点
整包更新 所有的动画资源按目录结构进行压缩, 客户端通过比较资源包版本号发现有更新后, 仅需下载一个资源包压缩文件, 并进行解压替换即可 简单易实现, 客户端每次仅需下载一个资源包 随着资源包逐渐增大, 下载及解压时间也会延长, 从而直接影响用户体验; 即使是仅是资源中的某个图片发生改变, 客户端都要重新下载整个资源包, 容错率低且浪费流量
增量更新 每个动画资源单独压缩并上传CDN, 若客户端发现资源版本号有变化, 再对服务器下发的资源列表跟本地资源列表求差集运算从而得出增量, 单个动画资源的下载地址或者md5可作为唯一标识进行比较. 得出增量后, 客户端再对每个增量资源包进行下载, 每下载完一个即可"投入使用" 不怕资源变更频繁; 仅需下载有新增或有变更的资源包, 更节省时间以及流量; 逻辑略复杂于整包更新, 比如下载中途用户把应杀掉, 下次需要找出未更新完的增量资源并继续下载

4.1.2 资源立异流程

因对上家集团的代码保密, 此处不上具体代码

咱俩在上一小节中提及的两种更新形式,
它们重要的不比的就在于”资源改进”这一步骤

图 4.1.2.a 整包更新的流程图

整包更新流程图.png

图 4.1.2.b 增量更新的流程图

增量更新流程图.png

不知晓诸位发现五个流程共同之处没? 它们都急需检测资源版本号大小,
包括游戏补丁、热更补丁这一手续都必不可少. 相比于补丁类的,
资源立异不用太考虑灰度发表、回滚机制等问题, 但仍然照样需要小心资源核对,
内部测试, 以及日志监控等维持,
我记念在前人公司就碰到了有些地段下载下来的资源包有问题,
所以不管是CDN的题目或资源本身有题目, 前端都急需为最坏的图景做好打算,
这才是万全之策.

引用我上家商厦, 我老大兼mentor, 达文哥, 告诫的一句箴言

决不相信后台下发的数据都是毋庸置疑的

约莫意思这样, 原句没背下来, 这句话绝非不是指后台同学好生,
或者甩锅给后台
, 而是要prepare for the worst.

左右端测试都是一家人, 遭遇问题咱们先看看是不是自己问题,
不要相互甩锅..本是同根生相煎何太急, 就算有问题就一块搓一顿,
一顿不行就再来一顿

4.2 资源目录结构设计

任由哪个直播平台, 每个礼物都会相应一个逻辑id,
我们得以经过礼物的id作为该赠品的资源目录名,
然后在该目录内在去划分不同档次的图片子目录, 如下所示

- 10000             // 一级目录, 礼物id
    - - gift        // 二级目录, 小礼物序列帧图片
    - - giftlist    // 二级目录, 礼物列表序列帧图片 
    - - giftanim    // 二级目录, 大动画序列帧图片

这只是里面的一种设计, 也部分平台会利用如下模式, 所以首要仍旧看需求而定

- gift
    - - 10000
- giftlist
    - - 10000
- giftanim
    - - 10000   

此外, 有的平台还会选拔id_version, 即礼物id+礼物版本的花样来定名,
这样可以一本万利配置使后台可以灵活下发给前端具体要去播放哪个动画的某部版本了

- 10000_11  // id为10000, 版本为11的礼物资源目录
- 10000_12

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图