经过三次优化,我将gif加载优化了16.9%

WeTest 导读html


如今app愈来愈炫,动不动就搞点动画,复杂的动画用原生实现起来挺复杂,如是就搞起gif播放动画的形式,节省开发成本。
 

背 景java

设计同窗准备给一个png序列,开发读取png序列,一帧一帧的播放出来,实现一个动画的效果。android

 

为何不直接使用gif,github上有好的开源库能够直接播放gif的,为嘛?大部分缘由仍是要回答,项目需求决定。git

 

实现思路:github

一、比较偷懒的方式,将设计同窗给的png序列直接放到一个 animation-list中,就像这样子:性能优化

 

 

而后直接,放在设置为一个ImageView就能够了多线程

 

 

那么,真的就能够了吗?答案是,能够,也不能够,所以最终不能够~~(有点绕。。。)app

 

设计同窗给了一个90多张png的序列,因而oom的发生,真是悲剧啊,这么简单的方案,结果倒是这么华丽的被抛弃了。工具

 

二、使用一个线程来读取PNG序列,另一个线程去播放读取出来的PNG序列,那么有一些问题咱们要去面对:性能

 

a、一个线程来读,一个线程写,读PNG的线程写,播PNG的线程读,哎呀,有点拗口~~,不过很显然,这是一个《生产者-消费者模型》,那么问题是使用什么存放读取好的bitmap呢,使用BlockingQueue 吧,为何要使用BlockingQueue,若是不懂,请点击这里,还能不能使用别的,固然,有,并且还不止一个,感兴趣能够去这个包下java.util.concurrent探索下。

 

b、不是怕OOM吗?那么,这个方案是否能够解决OOM呢?可是显然是确定的了。为何这么说,都到了这种粒度了,OOM固然是能够解决。

 

b一、首先,咱们能够拿到当前的最大内存Runtime.getRuntime().maxMemory(),和当前的可用内存Runtime.getRuntime().freeMemory();

所以,结合BitmapFactory.Options,的这个inJustDecodeBounds属性,你彻底能够判断是否还有足够的内存加载更多的bitmap。

 

b二、其次,维护一个currentSize,记录解析到内存测bitmap占用的内存,每读一张,currentSize+读出来的bitmap占用的内存,currentSize显然是变更的,播放完的bitmap请补上一刀,currentSize - 刚刚播放完的bitmap。

 

那么,整个过程彷佛能够用这个图来清晰的表达了:

 

 

觉得这样就结束了,那你就TOO YOUNG TO SIMPLE 了,是否还能优化?你猜应该是能够吧!

 

我猜也是能够的,不难发现消费者的消费能力实在太强,读取PNG的线程太不给力,读的太慢了,播放老是等待读新的bitmap出来已供展现。那么?肿么办?

多个线程去读啊!

嗯,彷佛能够改进成这样,对吗?

 

 

这里,可能有多个读取PNG的线程,一旦引入了多线程,你就会体会到问题会变得复杂多了!

 

这里,你要控制,当前读取进度到了哪里,由于是多线程,因此,你之间那个简单的int currentLoad 已经不能用了,不然,三个线程读同一张png可能会被你不巧碰到,那么怎么办,使用AtomicInteger,OK,这个问题好像被你解决了,此时,你保证了,全部png被不重复加载完毕!

 

然而,一个更加头疼的问题还要你去面对,注意,gif是有播放顺序的,然而,你把BlockingQuene作成了这么一个序列:

 

 

同窗,这样好吗?显然不能接受。那么,如何保证塞入到BlockingQuene中的bitmap是按照png序列的顺序呢?

 

很显然要作到这一点,就须要将png的序号带入到读取线程中。读取线程读取完毕以后,去问一个manger,大哥,有比我小的读取线程尚未提交他拿到的bitmap吗?大哥告诉你还有,那对不起,你乖乖等一会吧,wait(关键字),对么?若是大哥告诉你没有,你丫就是序号最小的那个哦,那你就把bitmap交给BlockingQuene吧,然而本身就完成光荣使命了。

 

可问题是,若是你在wait,谁来叫醒你呢?大哥说,他来notify,大哥收到最小的序号的提交的bitmap,等等,(上面说错了,最小的须要把bitmap交给大哥来提交,),将bitmap交给BlockingQuene,而后大哥此时通知全部读取线程的小弟们,大伙赶忙来交做业了,如是此时你单身10年的左手终于抢到了“锁”,如是,你把你的做业bitmap交给了大哥了。

 

图,我就不画了,脑补也能补出来,不是吗?

 

不知足锁,能够优化成无锁,大哥能够维护一个序列,1对应的座位只能1提交过来,2对应的只能2提过来,维护一个已交给BlockingQuene位置的游标,有好多种状况,咱们用绿色的表明已交给大哥的任务好吗?

 

如是,这种状况表达0123已经提交给BlockingQuene,5先完成了,而后3完成了,4没完成,此时大哥会吧3提交给BlockingQuene对吗?显然是,状况还有不少,,能够本身脑补一下,总之,这么作,读取线程只要读取完毕,把做业交给大哥就好,不用等待大哥说你是最小的,才让你提交,是吗?

 

 

这样就OK了吗?

 

若是说是,那你仍是TOO YOUNG TOO SIMPLE!

 

万万没想到,以前单个线程读的时候,加载一张PNG耗时才220ms左右,(测试使用模拟器),真机华为mate8略快。

 

 

然而,使用多线程读的时候,加载一张PNG竟然耗时1100ms左右,开了4个读线程。。,真是醉了。

 

 

线程开的有点多?那个2个试试???400ms左右!!!

 

 

OH,no,回过头来想一想,其实,瓶颈在读没有错,可是读的瓶颈在手机存储卡上。。。或许还有其余因素。

 

三、不死心,继续思考,单个线程读取png的状况下,是否有可能提升读取效率?

 

先把问题放一放,假如真的找不到好的办法,至少要保证内存占用方面,流畅性方面先,看下内存图谱吧,不看没关系,一看,就醉了:

 

细心的同窗应该看到了锯齿了,这GC,太酸爽了吧,分析一下,咱们没播放完一帧,就将bitmap给回收了(recycle)了。结果就致使了这种图的出现,可是又不能不recycler掉,随着bitmap内存占用不断增长,OOM势必难以免。

 

那么,既然释放也不是,不释放也不是,那么,能够不能够将这个要释放的bitmap继续拿过来用呢?

 

什么意思?

 

若是要释放的bitmap的那块内存,可以直接用来加载新的png,那该多好啊,那么,是否有这个可能呢?问了下google,他给了我这么一个答案:

https://developer.android.com/training/displaying-bitmaps/manage-memory.html#recycle

 

 

 

options有这么一个参数 ,能够重用一个bitmap的内存去存放解析出另一个新的bitmap,可是有必定的要求:

4.4以上,只须要old bitmap字节数比将要加载的bitmap所需的字节数大,可是低于4.4,要知足和待加载bitmap长宽像素一致便可 (更加苛刻)。

 

而咱们的png序列,每张图片都是同样大小,显然,符合这个全部特性(长宽一致)。

 

如是,有多了集合去存储即将释放的bitmap,用来重用。

 

 

测试一下:

锯齿果断消失了,并且,彷佛还获得一个额外的奖励!

 

 

 

加载速度提高了

 

 

分析,多是由于bitmap内存的重用,使得加载新bitmap的时候不用从新分配内存,节省了必定的时间。

 

最后看看丝丝顺滑的效果吧

 

 

 

 


针对手游的性能优化,腾讯WeTest平台的Cube工具提供了基本全部相关指标的检测,为手游进行最高效和准确的测试服务,不断改善玩家的体验。目前功能还在免费开放中。欢迎当即体验!

 


帮助中心:http://wetest.qq.com/help/documentation/10096.html 

 

若是对使用当中有任何疑问,欢迎联系腾讯WeTest企业qq:800024531

相关文章
相关标签/搜索