Android 帧动画(Frame Animation)的使用十分简单
定义 Animation-List 资源 res/anim/rocket.xml
1 2 3 4 5 6 7
| <?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@drawable/rocket_thrust1" android:duration="200" /> <item android:drawable="@drawable/rocket_thrust2" android:duration="200" /> <item android:drawable="@drawable/rocket_thrust3" android:duration="200" /> </animation-list>
|
设置到 ImageView
1 2 3 4 5
| ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image); rocketImage.setBackgroundResource(R.drawable.rocket_thrust);
rocketAnimation = (AnimationDrawable) rocketImage.getBackground(); rocketAnimation.start();
|
但是,当你的帧动画比较长,例如有40张图片,而每张图片又比较大的时候就会很容易出现OOM。
显然 Animation-List 满足不了40张图片这种情况,40张图片就会占用40份内存,非常糟糕的内存占用。
解决方案: BitmapFactory.Options.inBitmap
From: Managing Bitmap Memory
(中文)
从Android 3.0 (API Level 11)开始,引进了BitmapFactory.Options.inBitmap字段。
如果使用了这个设置字段,decode方法会在加载Bitmap数据的时候去重用已经存在的Bitmap。
这意味着Bitmap的内存是被重新利用的,这样可以提升性能,并且减少了内存的分配与回收。
然而,使用inBitmap有一些限制,特别是在Android 4.4 (API level 19)之前,只有同等大小的位图才可以被重用。详情请查看inBitmap文档。
使用该技术,在播放动画的时候只需要一份内存就可以了,重复使用 Bitmap 内存,连内存抖动都不会发生。
所以我的做法是自己实现动画播放: load resource -> decode(inBitmap) -> set to ImageView -> delay -> load resource -> …
要让动画流畅,需要确保 load resource 与 decode 的工作在16毫秒内完成,(16ms 来源)
16ms 优化思路:
- load resource : 如果所有资源的大小加起来只有几Mb,则可以将资源缓存起来,事先把所有资源加载到内存,那后面解码的话将会很快。
- 根据实际业务真机测试下decode工作时间大概为多少,如果小于16ms,那就在主线程实现,如果decode耗时有点长,那就启用两个线程,使用两份内存(two inBitmap)。
主线程大致实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| public abstract class FrameAnimationHelper { private byte bitmapBytes[][]; private int[] frames;
private Bitmap bitmap; private ImageView imageView; private BitmapFactory.Options options; private int order = 0;
public FrameAnimationHelper(ImageView imageView) { this.imageView = imageView; frames = getFrameResources(); if (frames == null || frames.length < 1) { throw new RuntimeException("FrameAnimationHelper need frame resource"); } bitmapBytes = new byte[frames.length][];
options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.ARGB_8888; options.inMutable = true;
if (bitmapBytes[0] == null) { Resources resources = imageView.getResources(); int bytesRead; InputStream is; ByteArrayOutputStream bos; byte[] b = new byte[4096]; try { for (int i = 0; i < frames.length; i++) { is = resources.openRawResource(frames[i]); bos = new ByteArrayOutputStream(); while ((bytesRead = is.read(b)) != -1) { bos.write(b, 0, bytesRead); } bitmapBytes[i] = bos.toByteArray(); } } catch (IOException e) { } } imageView.setImageBitmap(decode(order)); }
public void start() { if (order >= frames.length) { order = 0; } imageView.post(runnable); }
public void stop() { imageView.removeCallbacks(runnable); }
private Bitmap decode(int order) { if (order < frames.length) { options.inBitmap = bitmap; bitmap = BitmapFactory.decodeByteArray(bitmapBytes[order], 0, bitmapBytes[order].length, options); } return bitmap; }
private Runnable runnable = new Runnable() { @Override public void run() { if (order < frames.length) { imageView.setImageBitmap(decode(order)); imageView.postDelayed(runnable, 16); order++; } } };
protected abstract int[] getFrameResources(); }
|