原标题:【实战总结】帧动画调优实践

原文链接:https://www.zybuluo.com/avenwu/note/876161

APP架构师整理发布,转载请联系作者获得授权。

1.背景

在做动画的时候我们有很多选择方案。

最常见的是Android原生的帧动画,位移动画,旋转动画,属性动画等等,具体根据动画效果选择实现方案;

针对那些有规律,不太复杂的矢量动画我们往往也采取自定义View来实现,比如各种狂拽炫酷的loading动画;

如果自定义实现成本比较大,或者难以达到Android/iOS多端统一,也经常采用帧动画,由UE提供帧动画素材;

如果项目支持,也可以使用Airbnb出品的Lottie[1],这是一个非常牛x的动画解析库,不过对sdk版本有要求,具体可以自行查验下.

目前最优方案是使用Lottie动画库,在无法使用Lottie的情况下,我们就需要手动来具体帧动画的调优了。

本文主要谈谈做帧动画的一些优化策略以避免OutOfMemoryError问题。

2. 帧动画有什么问题?

我们都知道原生的Android帧动画在加载序列帧时,是一次性将所有序列帧的图片编码到内存当中的,所以执行帧数较多的动画时很容易发生内存不足,抛出OutOfMemoryError。

所以呢,你就需要解决内存问题,基本上可以从以下几方面入手;

降帧

在保证UI效果和视觉流畅度的情况下尽可能减少帧数,比如UE输出的序列帧可能默认有有4,5十张图片,减帧后可能只有20张;这里一定要注意的是,必须经过UE来减帧,RD不允许私自调整,避免效果不达标;

压缩尺寸

根据在手机上的展示大小,缩放到一致的高宽,避免屏幕上展示100x100,你用一个500x500的资源;

压缩体积

采用无损或者可接受的有损压缩图片质量,这个也无需多说,老司机都懂得。打个广告,推荐笔者写的IntelliJ插件 http://avenwu.net/biu/;

重写帧动画的编码逻辑

这里的一个思路是动态编码图片,不再一次性加载所有图片,通过懒加载的方式,图片对内存的要求会大幅降低;

3. 手工实现帧动画

3.1 帧动画分析

下面主要针对重写帧动画的编码逻辑这个维度来聊聊具体的实现策略。

既然要重写帧动画,首先就要知道Android原生帧动画的实现逻辑;通过阅读相关源码,笔者绘制了如下简化示意图,基本涵盖了帧动画的构造和执行过程;

可以看到帧动画在首次inflate的时候会解析xml,并将每一个item节点解析为drawable对象实例,然后加入到数组当中;后续动画过程就是轮询绘制,在Drawable容器中绘制当前drawable即可,整体代码简洁漂亮。

但是这个逻辑我们并不能直接复用到手工实现的帧动画中,为什么?这里暂不解答,读者可以自己思考一下:)

现在我们把帧动画的实现策略抽象一下,它就是一个生产者和消费者的关系,如下图所示:

总的来说我们的优化策略集中在编码和缓存上面,渲染不需要改变。根据编码的的策略可能代码差异会很大。在实际开发中,我们也遇到过一些坑,比如编码速度跟不上,导致渲染的时候出现跳帧,推测主要是CPU时间切片问题。

所以如果做成单线程编码,UI线程渲染是要非常谨慎的,否则很有可能你的编码业务全部被阻塞了,导致UI层面频繁丢帧。

3.2 Bitmap 复用

在图片编码之后,我们需要考虑Bitmap的编码复用,这样可以避免每次decode一张图片都要全新申请一块内存,通过复用已有的bitmap实例对象我们可以做到编码的更低开销。

其核心在于Options#inBitmap,这个API是Android引入的一个新API,新API本身没什么问题,问题在于这个属性是API 11引入的,但并不可以直接使用,这导致做版本判断的时候比较坑(你无法确定这个接口是否可用,除非你穷举一下相关SDK版本)。根据源代码,在API 14的时候使用该属性会抛异常,具体大家可以去develop了解相关细节,。

在引入了Options#inBitmap的时候一定要注意姿势。否则的话极有可能出现大量日志警告:

Calledreconfigure on a bitmap thatisinuse!Thismay cause graphical corruption!

当然如果出现了这个警告也不要紧张,肯定是可以解决的,这个警告是在core/jni/android/graphics/Bitmap.cpp中抛出来的,含义其大致就是说如果你在修改一个正在被使用的bitmap那么这会导致graphical corruption。这个单词没有想到合适的中文翻译,我猜就是会破坏图形的意思吧。

如果你一定要忽略这个异常的话,请忍受控制台的大量警告输出,反正我是忍不了。要解决整个警告也很简单,只要保证复用bitmap作为inBitmap时不要使用当前正在被ImageView展示的bitmap实例就可以了。

3.3 RAM和CPU的平衡处理

前面提到了,编码图片可能会跟不上渲染,这是因为编码一张图片需要CPU去处理图片,包括IO之类的,这需要编码线程申请CPU时间切片,如果UI线程或者其他线程池有高优的任务,那么编码这一块就会很尴尬。所以要解决这个问题一方面可以提供bitmap内存缓存,减少需要重新编码的次数,比如我们通过调试把图片复用的命中率提高到40%,50%之类。另外一个就是不能给编码线程设置过低的优先级,不要觉得这是后台任务应该放一个BACKGROUND之类的低优先级,根据andorid的官方说明(具体文档我忘了),后台线程和前台(其他默认优先级线程)之间获取CPU的能力差异是非常大的,好像接近二八开的样子。

3.4 缓存策略

缓存策略也是需要认真考虑的地方,前面我们讲到了为了平衡编码压力,我们可以引入内存缓存,那么以什么策略来缓存图片呢?(在心里说出一个答案,看看对不对)

LRU?

如果你回答LRU的话,恭喜你,你的缓存完全失效了。

虽然LRU使用非常广泛的缓存算法,但是很遗憾他并不适合帧动画缓存,为什么呢?读者可以自己思考一下LRU的淘汰策略和帧动画的执行策略就明白了。

所以我们用的是什么装逼的算法呢?这个笔者并不是专研算法的,所以不懂得太多唬人的名字。不过回想起多年前求学时,耳(quan)熟(bu)能(wang)详(ji)的那些算法,你会想起还有一个叫LIFO的东西,也就是后进先出算法(Last in first out)。

我们根据需要可以稍加改造找一下,首先实现一个LifoCache,这个可以从LruCache略加改造,然后在取缓存/删缓存时,取倒数第二个即可。这样可以最早执行的动画被缓存下来,并且有机会再下一轮动画来临时被复用。当然机智的你也可以选择其他更好的算法。

3.5 优化点回顾

以上是我们在实践当中总结的一些比较关键的点。出于尊重原创的职业素养,必须声明,我们的整体优化的启示灵感来源于FasterAnimationsContainer[2];

原项目更多的像一个示意demo,存在诸多bug,需要修复才能满足基本使用,因此我们修复了这些问题,并且发起了 Pull Request[3]。

不过从维护记录来看原项目已经不太活跃,我们将这些改动和新增的功能优化做了梳理。

当然除了修复这些问题,我们实际上进行了几乎95%以上的重构,所以严格来说,这两个项目除去解决内存泄漏的思想,在工程上已经没有太多相似处了。

我们主要做了如下改动:

消除bitmap编码警告;

修复前后台切换后动画僵死的问题;

取消单例模式,支持每个ImageView控制独立的动画;

新增图片内存缓存,支持缓存比配置;

动画不显示时缓存自动释放与恢复;

支持原生xml定义,兼容原生写法;

4. 调优效果

这一节,我们简单对比下,手工实现的帧动画和原生帧动画的内存数据;

4.1 原生标准帧动画

在这幅图中,我们执行了一个完整的操作流程

启动程序(不包含目标动画视图);

跳转到原生帧动画Activity,内部有一个ImageView,设置了android:src为一个xml帧动画;

可以看到刚进入目标Activity内存立刻开始大幅上升,CPU出现了波动;而此时我们实际上没有开始执行动画;

获取ImageView的src并start动画,此时内存没有明显波动,并且CPU也相对平稳;

持续播放动画,内存和CPU基本不再变化,维持在一个高位状态;

退出页面,GC后内存全部释放;

4.2 懒加载帧动画

前面已经提到,我们的帧动画支持缓存设置,所以分别看一下缓存40%和100%缓存的两种情况。

我们保持一致的操作流程,来看一下优化后的帧动画的数据;

启动程序(不包含目标动画视图)

跳转到优化帧动画Activity,内部有一个ImageView,设置了app:src为一个xml帧动画;

刚进入目标Activity内存小幅增加,CPU出现波动;此时我们实际上也没有开始执行动画,这个内存开销是预览的首帧;

获取ImageView的src并start动画,随着动画的执行内存使用量开始慢慢上爬,期间伴随着CPU的波动;

持续播放动画,内存达到一个稳定态,基本不再变化;CPU持续变化;

退出页面,GC后内存全部释放;

类似的当我们把缓存比调大,比如调到100%后,可以得到一个新的图。这个图的内存高峰和CPU状态基本可以看做是上面两种情况的结合体。当缓存占比高了,那么后续需要重新编码的次数也就少了,所以CPU的占用也就少了。

4.3 数据对比

以我们的测试为例,选用了16张600x600的jpg图片,每张大小约为14~18KB之间。

为了方便,对比我们选取几个关键点的近似内存和CPU数据。

5. 使用说明

介绍完了原理,就来看下怎么使用吧,API层面没有做太多改变和原生使用比较接近。由于项目是开源的,这里放上传送门:

http://hub.hacktons.cn/animation/

通过上述地址可以获取项目最新的变化和源代码等信息。 下面我们简单看下目前如何使用我们的优化方案来播放一个帧动画。

5.1 配置Gradle依赖库

compile'com.github.avenwu:animation:0.2.0'

5.2 通过MockFrameImageView使用动画

先进行动画定义,一般都是用一个xml搞定,方便省事。

在layout中进行布局定义

最后在Activity或者其他地方就可以启动动画了

上面的使用方法维持了和原生帧动画一致的操作和配置,除了layout里面写的控件不是ImageView,其他一模一样的。

5.3 通过代码使用动画

除此之外,我们也可以直接用代码来实现这个动画配置,这也是项目最开始所支持的方式,这个就不需要依赖任何自定义的View,直接在原生ImageView上生效:

5.4 效果对比图

最后看一下各种帧动画的实现效果。

Download Video

6. 小结

综合来看,原生帧动画更适合体量较小,内存压力不那么大的帧动画,如此一次性加载所有帧,可以保证后续帧切换的流程性;而MockFrameAnimation则为了解决内存问题,采取动态编码序列帧。

这相当于用CPU的编码/计算能力换取了内存消耗;同时为了达到适合的平衡,我们允许开发者设置图片缓存的张数,缓存数越大那么内存消耗越多,需要重新编码的次数也就相对更少;

[1] https://github.com/airbnb/lottie-android ↩

[2] https://github.com/tigerjj/FasterAnimationsContainer ↩

[3] https://github.com/tigerjj/FasterAnimationsContainer/issues/11 ↩返回搜狐,查看更多

责任编辑:

android动画不占cpu如何实现,【实战总结】帧动画调优实践相关推荐

  1. 《Apache Kafka实战》读书笔记-调优Kafka集群

    <Apache Kafka实战>读书笔记-调优Kafka集群 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.确定调优目标 1>.常见的非功能性要求 一.性能( ...

  2. 《Storm企业级应用:实战、运维和调优》——1.4 Storm的特性

    本节书摘来自华章计算机<Storm企业级应用:实战.运维和调优>一书中的第1章,第1.4节,作者:马延辉 陈书美 雷葆华著, 更多章节内容可以访问云栖社区"华章计算机" ...

  3. 《Storm企业级应用:实战、运维和调优》——1.6 本章小结

    本节书摘来自华章计算机<Storm企业级应用:实战.运维和调优>一书中的第1章,第1.6节,作者:马延辉 陈书美 雷葆华著, 更多章节内容可以访问云栖社区"华章计算机" ...

  4. python做flash帧动画_[练习]利用CSS steps 实现逐帧动画

    网页逐帧动画的实现方式 网页中的逐帧动画,大致可分为两大类的实现方式, 分别是使用JS控制,和单纯使用CSS实现,两者的优劣总体概括来说就是: JS动画可控性更强,但开销大,实现复杂. CSS动画实现 ...

  5. 《Storm企业级应用:实战、运维和调优》

    <Storm企业级应用:实战.运维和调优> 全面介绍Storm的架构.原理.核心概念.操作和数据流模型:6个不同领域的经典案例完整呈现大型数据应用系统的设计:系统总结了Storm常见运维故 ...

  6. 实战:Eclipse运行速度调优

    很多Java开发人员都有一种错觉,认为系统调优的工作都是针对服务端应用的,规模越大的系统,就需要越专业的调优运维团队参与.这个观点不能说不对,只是有点狭隘了.上一节中笔者所列举的案例确实大多是服务端运 ...

  7. elasticsearch运维实战之2 - 系统性能调优

    elasticsearch性能调优 集群规划 独立的master节点,不存储数据, 数量不少于2 数据节点(Data Node) 查询节点(Query Node),起到负载均衡的作用 Linux系统参 ...

  8. linux企业级应用实战运维和调优资源,Linux企业级应用实战、运维和调优

    部分 Linux实用生产技能 章 生产常用基础技能 2 1.1 Hostname & Network 3 1.1.1 基础知识 3 1.1.2 Network指令 3 1.1.3 配置以太网静 ...

  9. spark 大型项目实战(三十一): --性能调优之在实际项目中使用fastutil优化数据格式

    fastutil介绍: fastutil是扩展了Java标准集合框架(Map.List.Set:HashMap.ArrayList.HashSet)的类库,提供了特殊类型的map.set.list和q ...

最新文章

  1. 如何启用SQL Server 2008的FILESTREAM特性
  2. 程序员级别鉴定书(.NET面试问答集锦)
  3. Mysql多表查询笔记
  4. VS中修改站点运行方式(集成 Or 经典)
  5. 华为完成拉美铜网宽带G.fast技术部署测试
  6. Windows Embedded CE 6.0开发初体验(六)平台定制
  7. linux运行中望cad,国产CAD软件中望的Linux版适配UOS, 我在国产系统里试了试
  8. Java讲课笔记06:选择结构与条件循环
  9. JDK 8 十大新特性详解
  10. 头像上传html js版,javascript头像上传代码实例
  11. Docker代理设置方法
  12. [2018.11.05 T3] 零食
  13. steam安装包_Steam已经绑定了令牌,为什么还会频繁被盗?
  14. html 给表格添加背景颜色,HTML表格标记教程(7):背景颜色属性BGCOLOR
  15. OTL/OCL/BTL/甲类/乙类/甲乙类
  16. 进程隐藏的各种方法 以及分析比较以及实现链接
  17. 以计算机作为类 触摸板作为接口,怎样用Arduinopromicro将电脑触控板转换为USB设备...
  18. taskset 查询或设置进程绑定CPU(亲和性)
  19. 集中式、分布式版本控制系统的区别
  20. 关于文件命名和整理的小技巧

热门文章

  1. 影响计算机安全的诸多因素中,影响计算机设备安全的诸多因素中,影响较大的是()。...
  2. 7-4 接话茬 (20分)
  3. python股票量化投资课程 百度云_网易云课堂 Python股票量化投资课程|百度云|天翼云|GD|OD下载...
  4. 根据文件名修改文件(创业天使-xxx 120101_超清.mp4 -- 120101-创业天使-xxx.mp4)
  5. ubuntu Ntop 安装配置 监控局域网流量
  6. 前端安全问题及防范措施
  7. 别再羡慕马斯克的脑机接口了!中国强大的脑机接口在这里
  8. php 二码合一支付_关于微信支付与支付宝支付前端这块二码合一的方法记录
  9. 利用bmob实现简单的注册和登录
  10. Python3.x 网络爬虫之淘宝模特照片信息爬取