FrameAnimation

如果有播放超多帧动画的需求,直接点击 FrameAnimation 在github查看,百分之99.99能满足你的需求,基本此文就可以终结了。

PS. 此文年久失修,上述代码的具体实现本文已有较大差距,不过整体思路还是可以参考下的。

关于Android帧动画

       当在应用中需要使用帧动画的时候,最先想到的就是Android提供的AnimationDrawable了,但是如果帧动画中如果包含上百帧图片,此时再用AnimationDrawable就不是那么理想了。AnimationDrawable使用一个Drawable数组来存储每一帧的图像,会直接把全部图片加载进内存。随着帧数量的增多,就算性能再强劲的机器也会卡顿、OOM。

使用SurfaceView来实现帧动画的效果

    最近的项目中需要用到大量的帧动画(各种闪瞎24K钛合金狗眼的礼物效果,多的高达200帧),既然AnimationDrawable不行,就想到了两种解决方法。

第一个想到的解决办法就是用openGL来绘制了。

   因为是直播的项目,包含人脸贴图等都是用opengl绘制的,如果用OpenGL绘制一层Texture直接推流还省事。只在主播端处理就行了,但是IOS那边都弄得差不多了,直接原生的不用处理也不会有什么异常什么的。。很尴尬。

第二个就是使用Android自带的surfaceView了

    好吧,第一个不行那就想到Android自带的surfaceView啦。我首先用不同的手机测试了下应用从本地decode一个bitmap的时间(png格式,414*736,大小在30-100k之间),因为帧动画的每帧不会太大,在性能好点的设备上基本保持在10-30ms之间(不推流基本上推流状态下10ms左右,推流状态下20左右),在性能稍差的设备中基本上也不会超过50ms,所以说是没什么大问题的。

在移动设备播放的帧动画一定要尽最大可能的压缩,推荐一个网站,会把图片颜色深度压缩成8位的。TinyPNG

实现思路

整个思路大概如图。
既然不能完全加载到内存,想到的就是类似视频播放或者视频直播类似的思路。首先定义一个Bitmap的缓冲区,边绘制边加载。首先加载一定数量的帧到Bitmap缓冲区,加载完成后通知SurfaceView开始绘制。SurfaceView绘制一帧完成后通知Bitmap缓冲区加载下一帧,同时将绘制过的一帧的从Bitmap缓冲区移除。一帧绘制完成后,绘制线程根据设置的帧间隔休眠一段时间,休眠完成后开始从Bitmap缓冲区获取下一帧,依此类推,一直循环,直到播放完成或者手动停止。按照这种方式实现起来,发现oom卡顿什么的果然不存在了,内存的使用情况如图。

但是看着这个垃圾桶一个挨一个,这个内存回收情况完全不正常!GC太频繁了。想着应该是这里出现了问题。
频繁的添加移除bitmap,导致了不算太严重的内存抖动。之所以称之为不算太严重,因为大概400ms一次,一次gc花费2ms左右。不看内存,只看运行效果。真的感觉不出来。但是呢,这样显然也是不行滴。
####内存抖动的解决
最常见的解决方法就是对象的复用,创建各种pool。Android也提供了Bitmap的复用方式,在加载bitmap的时候传入一个inBitmap,那么加载的bitmap就会复用原bitmap的内存空间,所以理论上将要复用的bitmap和新加载的bitmap在颜色深度一样的情况下,复用的bitmap宽高要大于新加载的bitmap。50L的桶毕竟最多只能装50L的水。关于inBitmap更多资料可以参考这里,还有这里。(请自备梯子)。 使用起来很简单,大概就是这样

Bitmap mInBitmap;BitmapFactory.Options mOptions = new BitmapFactory.Options();mOptions.inMutable = true;mOptions.inSampleSize = 1;//mInBitmap不能为null,此处省去赋值mOptions.inBitmap = mInBitmap;Bitmap bitmap = BitmapFactory.decodeStream(mAssetManager.open(path), null, mOptions);

然后实现思路就是在这里修改了,把将要删除的哪一帧留下来作为inBitmap。最后再看下内存的使用情况,首先是运行动画前。

然后是运行动画时

内存的使用非常平稳,其实一直是加载到内存中的那几帧,和上图抖动的垃圾桶形成了鲜明的对比。内存占用和播放动画之前只多了那么一丢丢。就是你有1000000000张帧动画要播放,还是这么一丢丢。

使用

关于代码我觉得不贴了,贴了也不一定有人看,这里只分享下核心的实现思路。大家有兴趣的可以自己搞一下。更可以方便的直接去github查看和使用SilkyAnimation。可以超级方便的播放帧动画。

SilkyAnimation mAnimation=                new SilkyAnimation.Builder(mSurfaceView)                .build();//初始化完成之后直接就调用start传入file或者asset资源目录播放了File file=new FIle(Environment.getExternalStorageDirectory() + File.separator+ "bird")mAnimation.start(file);//或者String assetsPath="bird/crow";mAnimation.start(assetsPath);//然后你还可有更多设置new SilkyAnimation.Builder(mSurfaceView)                //设置常驻内存的缓存数量, 默认5.                 .setCacheCount(8)                //设置帧间隔, 默认100                .setFrameInterval(80)                //设置缩放类型, 默认fit center,与ImageView的缩放模式通用                .setScaleType(SilkyAnimation.SCALE_TYPE_FIT_END)                //设置动画开始结束状态监听                .setAnimationListener(listener)                //设置是否支持bitmap复用,默认为true                .setSupportInBitmap(false)                //设置循环模式, 默认不循环                .setRepeatMode(SilkyAnimation.MODE_INFINITE)                .build();

看上去是不是超级方便,如果有任何问题的话也可以直接在github提交issue。

问题

关于从本地加载图片的方式,当时在想用多线程异步加载或者单线程同步阻塞加载的哪一个。最后选择了单线程同步阻塞加载,因为个人觉得决定加载速度的更多应该是io速度,多线程并不能解决。如果再加上各种锁,或许多线程异步加载并没有什么优势,并且实现起来单线程明显工作量小很多,而且最后实现起来并没有发现因为加载速度导致的问题。预先加载5个到内存,更多是为了对冲加载某个图片耗时异常的风险,如果加载每个超大图片的时间都很长,那么解决的方式只能是增大帧间隔。这就像class4 的tf卡不能用来拍摄4K视频似的。以上也只是个人的想法,如果有错误的地方也欢迎小伙伴们指正。

更多相关文章

  1. Android:最全面的 Webview 详解
  2. Android(安卓)内存溢出解决方案(OOM) 整理总结
  3. android点滴(29) android中设置用户自定义的字体
  4. Android之 RecyclerView,CardView 详解和相对应的上拉刷新下拉加
  5. 2014-11-8Android学习------Android(安卓)实现图片的旋转-------
  6. Android中解决图像解码导致的OOM问题
  7. 从零学Android(十二)、Android中的图形和动画之属性动画
  8. android加载网络图片(逐行扫描格式png图片)的一个bug
  9. android动画效果

随机推荐

  1. 【Android】如何调节屏幕亮度,关闭屏幕
  2. android lisetview的多列模版
  3. ListView高级用法
  4. Androkd开发坏境配置以及常用插件
  5. 老罗Android开发视频教程(Android入门介绍
  6. 相对布局属性
  7. android lisetview的多列模版
  8. android 3G pppd 调试记录。
  9. Android证书创建之 keytool 错误:java.io.
  10. 【Android】自带Theme