Android 中加载几百张图片做帧动画防止 OOM 的解决方案

最近,项目中有个需求:就是要做一个帧动画,按理说这个是很简单的!但是我能说这个帧动画拥有几百张图片吗?。。。。。。

填坑一 ---帧动画

一开始我的想法是直接用帧动画来做,可是我太天真了,当帧数放到 50 几张的时候,已经在有些机器上奔溃了!所以这个方案否决!

填坑二 ---GIF动图

虽然可以显示,但是已经卡的我,已经不想看了,直接放弃

填坑三 ---视频

在这里,我突然想到我可以直接把他做成一个小视频啊,而且可以极限压缩视频。最终,视频大小被压缩到 500K 左右。此时已经基本可以满足需求了,但是我们有好多类似的动画,要求在每个动画切换的时候要有衔接感,不能有突兀的感觉,所有在这里视频就不能很好的完成任务了,所有再次放弃,已经泪牛满面了!!!!

填坑四 --- SurfaceView + BitmapRegionDecoder +缓存

首先回答一下:为什么会想到这个解决方案?

  1. 首先在做帧动画的时候,大约每帧之间的时间差值是 40ms 可以说速度非常快了,在如此快速的图片切换上,自然而然的想到来了使用SurfaceView。
  2. 现在再来说说为什么想到要使用这个类 BitmapRegionDecoder .这个也是从我司游戏开发人员那儿得到的经验?他们在做游戏的时候,游戏中的切图都是放在一张大图上的,然后在根据对应的 xml,json 文件,获取相应的图片,接着再来切图。对此,我想能不能把所有的动图都放到同一张的图片上呢,之后在根据对应的描述文件,裁剪出我想要的图片呢!所以就用到了 BitmapRegionDecoder. 它的作用是:于显示图片的某一块矩形区域!之后,我在找设计人员商量一一下,把图片在尽量的压缩。之后从美工那儿获取的信息是这样的:
    json格式的描述文件:
{"frames": [

{
    "filename": "kidbot-正常闭眼0000",
    "frame": {"x":0,"y":0,"w":360,"h":300},
    "rotated": false,
    "trimmed": false,
    "spriteSourceSize": {"x":0,"y":0,"w":360,"h":300},
    "sourceSize": {"w":360,"h":300}
}
.....
}

png图片:

android下帧动画 android 帧动画优化_帧动画

接下来就好做了,解析 json 格式的文件,裁剪图片。

  1. 最后说一下为什么使用缓存,其实很简单,因为切换的频率实在太高了,没有必要每次都从图片中裁剪,这里就把裁剪出来的 bitmap 缓存起来在用。从而介绍内存开销!

最后给出代码:

public class AnimView extends SurfaceView implements SurfaceHolder.Callback {
    private BitmapRegionDecoder bitmapRegionDecoder;
    private SurfaceHolder mHolder;
    private boolean isrunning = true;
    private AnimThread thread;
    private Paint mPaint;
    private int WIDTH = 0;
    private int HEIGHT = 0;
    private int state = -1;
    private boolean isstart = false;
    private boolean isblinkfirst = false;
    private int rate = 40;
    private int index = 0;
    private Matrix matrix;
    private Random rand;
    private Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            isblinkfirst = true;
        };
    };
    private SparseArray<WeakReference<Bitmap>> weakBitmaps;
    private SparseArray<WeakReference<Bitmap>> cweakBitmaps;

    private BitmapFactory.Options options;

    public AnimView(Context context) {
        super(context);
        init();

    }

    public AnimView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public AnimView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @SuppressLint("NewApi")
    private void init() {
        weakBitmaps = new SparseArray<WeakReference<Bitmap>>();
        cweakBitmaps = new SparseArray<WeakReference<Bitmap>>();
        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        setState(FaceBean.BLINK);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        matrix = new Matrix();
        float[] values = { -1f, 0.0f, 0.0f, 0.0f, 1f, 0.0f, 0.0f, 0.0f, 1.0f };
        matrix.setValues(values);
        WindowManager manger = (WindowManager) getContext().getSystemService(
                Context.WINDOW_SERVICE);
        DisplayMetrics displayMetrics = new DisplayMetrics();
        manger.getDefaultDisplay().getMetrics(displayMetrics);
        WIDTH = displayMetrics.widthPixels / 2;
        HEIGHT = displayMetrics.heightPixels / 2;
        rand = new Random();
        options = new Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
    
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        handler.sendEmptyMessageDelayed(0, 1000 * (4 + rand.nextInt(4)));
        thread = new AnimThread();
        thread.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (thread != null) {
            thread.stopThread();
        }
    }

    public class AnimThread extends Thread {

        @Override
        public void run() {
            super.run();
            SurfaceHolder holder = mHolder;
            while (isrunning) {
                Canvas canvas = holder.lockCanvas();
                if (canvas == null)
                    continue;
                synchronized (AnimThread.class) {
                    AnimBean.Frames frames;
                    switch (state) {
                    case FaceBean.BLINK:
                        frames = KidbotRobotApplication.animBlink.getFrames()
                                .get(index);
                        if (frames.getFrame().getW() <= 0) {
                        } else {
                            Rect rect = new Rect(frames.getFrame().getX(),
                                    frames.getFrame().getY(), frames.getFrame()
                                            .getX()
                                            + frames.getSourceSize().getW(),
                                    frames.getFrame().getY()
                                            + frames.getSourceSize().getH());
                            WeakReference<Bitmap> weakBitmap = weakBitmaps
                                    .get(index);
                            Bitmap map = null;
                            if (weakBitmap == null) {
                                map = bitmapRegionDecoder.decodeRegion(rect,
                                        options);
                                weakBitmaps.put(index,
                                        new WeakReference<Bitmap>(map));
                            } else {
                                map=weakBitmap.get();
                                if (map == null) {
                                    map = bitmapRegionDecoder.decodeRegion(
                                            rect, options);
                                    weakBitmaps.put(index,
                                            new WeakReference<Bitmap>(map));
                                }
                            }
                            if (map == null) {
                                holder.unlockCanvasAndPost(canvas);
                                continue;
                            }
                            mPaint.setXfermode(new PorterDuffXfermode(
                                    Mode.CLEAR));
                            canvas.drawPaint(mPaint);
                            mPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
                            canvas.drawBitmap(map,
                                    (int) (WIDTH - (map.getWidth() * 1) - 150),
                                    (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            canvas.drawBitmap(map, (int) (WIDTH + 150),
                                    (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);

                            if (index == 0) {

                            }

                            if (map.isRecycled()) {
                                map.recycle();
                            }

                        }
                        if (!isstart) {
                            if (index < KidbotRobotApplication.animBlink
                                    .getFrames().size()) {
                                index++;
                                if (index == KidbotRobotApplication.animBlink
                                        .getFrames().size()) {
                                    index--;
                                    isstart = true;
                                    if (rand.nextInt(10) <= 2) {
                                        index = 1;
                                    }
                                }
                            } else {
                                index--;
                                isstart = true;
                            }
                        } else {
                            if (index > 0) {
                                index--;
                                if (index == 0) {
                                    isstart = false;
                                }
                            } else {
                                index++;
                                isstart = false;
                            }
                        }
                        if (!isblinkfirst) {
                            index = 0;
                        } else {
                            if (index == KidbotRobotApplication.animBlink
                                    .getFrames().size() - 1) {
                                isblinkfirst = false;
                                index = 0;
                                handler.sendEmptyMessageDelayed(0,
                                        1000 * (4 + rand.nextInt(4)));
                            }
                        }
                        break;
                    case FaceBean.ANGRY:
                        frames = KidbotRobotApplication.animAngry.getFrames()
                                .get(index);
                        if (frames.getFrame().getW() <= 0) {
                        } else {
                            Rect rect = new Rect(frames.getFrame().getX(),
                                    frames.getFrame().getY(), frames.getFrame()
                                            .getX() + frames.getFrame().getW(),
                                    frames.getFrame().getH()
                                            + frames.getFrame().getX());
                            WeakReference<Bitmap> weakBitmap = weakBitmaps
                                    .get(index);
                            Bitmap map = null;
                            if (weakBitmap == null) {
                                map = bitmapRegionDecoder.decodeRegion(rect,
                                        options);
                                weakBitmaps.put(index,
                                        new WeakReference<Bitmap>(map));
                            } else {
                                map=weakBitmap.get();
                                if (map == null) {
                                    map = bitmapRegionDecoder.decodeRegion(
                                            rect, options);
                                    weakBitmaps.put(index,
                                            new WeakReference<Bitmap>(map));
                                }
                            }
                            if (map == null) {
                                holder.unlockCanvasAndPost(canvas);
                                continue;
                            }
                            mPaint.setXfermode(new PorterDuffXfermode(
                                    Mode.CLEAR));
                            canvas.drawPaint(mPaint);
                            mPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
                            Bitmap dstbmp =null;
                            weakBitmap=cweakBitmaps.get(index);
                            if(weakBitmap==null){
                                dstbmp = Bitmap.createBitmap(map, 0, 0,
                                        map.getWidth(), map.getHeight(),
                                        matrix, true);
                                cweakBitmaps.put(index,
                                        new WeakReference<Bitmap>(dstbmp));
                            }else{
                                dstbmp=weakBitmap.get();
                                if(dstbmp==null){
                                    dstbmp = Bitmap.createBitmap(map, 0, 0,
                                            map.getWidth(), map.getHeight(),
                                            matrix, true);
                                    cweakBitmaps.put(index,
                                            new WeakReference<Bitmap>(dstbmp));
                                }
                            }
                            canvas.drawBitmap(
                                    map,
                                    frames.getSpriteSourceSize().getX()
                                            + (int) (WIDTH
                                                    - (map.getWidth() * 1) - 150),
                                    frames.getSpriteSourceSize().getY()
                                            + (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            canvas.drawBitmap(dstbmp, frames
                                    .getSpriteSourceSize().getX()
                                    + (int) (WIDTH + 150), frames
                                    .getSpriteSourceSize().getY()
                                    + (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            if (dstbmp.isRecycled()) {
                                dstbmp.recycle();
                            }
                            if (map.isRecycled()) {
                                map.recycle();
                            }
                        }
                        if (!isstart) {
                            if (index < KidbotRobotApplication.animAngry
                                    .getFrames().size()) {
                                index++;
                                if (index == KidbotRobotApplication.animAngry
                                        .getFrames().size()) {
                                    index--;
                                    isstart = true;
                                }
                            } else {
                                index--;
                                isstart = true;
                            }
                        } else {
                            if (index > 0) {
                                index--;
                                if (index == 0) {
                                    isstart = false;
                                }
                            } else {
                                index++;
                                isstart = false;
                            }
                        }
                        break;
                    case FaceBean.HAPPY:
                        frames = KidbotRobotApplication.animHappy.getFrames()
                                .get(index);
                        if (frames.getFrame().getW() <= 0) {
                        } else {
                            Rect rect = new Rect(frames.getFrame().getX(),
                                    frames.getFrame().getY(), frames.getFrame()
                                            .getX()
                                            + frames.getSourceSize().getW(),
                                    frames.getFrame().getY()
                                            + frames.getSourceSize().getH());
                            WeakReference<Bitmap> weakBitmap = weakBitmaps
                                    .get(index);
                            Bitmap map = null;
                            if (weakBitmap == null) {
                                map = bitmapRegionDecoder.decodeRegion(rect,
                                        options);
                                weakBitmaps.put(index,
                                        new WeakReference<Bitmap>(map));
                            } else {
                                map=weakBitmap.get();
                                if (map == null) {
                                    map = bitmapRegionDecoder.decodeRegion(
                                            rect, options);
                                    weakBitmaps.put(index,
                                            new WeakReference<Bitmap>(map));
                                }
                            }
                            if (map == null) {
                                holder.unlockCanvasAndPost(canvas);
                                continue;
                            }
                            mPaint.setXfermode(new PorterDuffXfermode(
                                    Mode.CLEAR));
                            canvas.drawPaint(mPaint);
                            mPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
                            Bitmap dstbmp =null;
                            weakBitmap=cweakBitmaps.get(index);
                            if(weakBitmap==null){
                                dstbmp = Bitmap.createBitmap(map, 0, 0,
                                        map.getWidth(), map.getHeight(),
                                        matrix, true);
                                cweakBitmaps.put(index,
                                        new WeakReference<Bitmap>(dstbmp));
                            }else{
                                dstbmp=weakBitmap.get();
                                if(dstbmp==null){
                                    dstbmp = Bitmap.createBitmap(map, 0, 0,
                                            map.getWidth(), map.getHeight(),
                                            matrix, true);
                                    cweakBitmaps.put(index,
                                            new WeakReference<Bitmap>(dstbmp));
                                }
                            }
                            canvas.drawBitmap(
                                    map,
                                    frames.getSpriteSourceSize().getX()
                                            + (int) (WIDTH
                                                    - (map.getWidth() * 1) - 150),
                                    frames.getSpriteSourceSize().getY()
                                            + (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            canvas.drawBitmap(dstbmp, frames
                                    .getSpriteSourceSize().getX()
                                    + (int) (WIDTH + 150), frames
                                    .getSpriteSourceSize().getY()
                                    + (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            // if (dstbmp.isRecycled()) {
                            // dstbmp.recycle();
                            // }
                            // if (map.isRecycled()) {
                            // map.recycle();
                            // }

                        }
                        if (!isstart) {
                            if (index < KidbotRobotApplication.animHappy
                                    .getFrames().size()) {
                                index++;
                                if (index == KidbotRobotApplication.animHappy
                                        .getFrames().size()) {
                                    index--;
                                    isstart = true;
                                }
                            } else {
                                index--;
                                isstart = true;
                            }
                        } else {
                            if (index > 0) {
                                index--;
                                if (index == 0) {
                                    isstart = false;
                                }
                            } else {
                                index++;
                                isstart = false;
                            }
                        }
                        break;
                    case FaceBean.RESOLVE:
                        break;
                    case FaceBean.RISUS:
                        break;
                    case FaceBean.SEERIGHT:
                        break;
                    case FaceBean.SAD:
                        frames = KidbotRobotApplication.animSad.getFrames()
                                .get(index);
                        if (frames.getFrame().getW() <= 0) {
                        } else {
                            Rect rect = new Rect(frames.getFrame().getX(),
                                    frames.getFrame().getY(), frames.getFrame()
                                            .getX()
                                            + frames.getSourceSize().getW(),
                                    frames.getFrame().getY()
                                            + frames.getSourceSize().getH());

                            WeakReference<Bitmap> weakBitmap = weakBitmaps
                                    .get(index);
                            Bitmap map = null;
                            if (weakBitmap == null) {
                                map = bitmapRegionDecoder.decodeRegion(rect,
                                        options);
                                weakBitmaps.put(index,
                                        new WeakReference<Bitmap>(map));
                            } else {
                                map=weakBitmap.get();
                                if (map == null) {
                                    map = bitmapRegionDecoder.decodeRegion(
                                            rect, options);
                                    weakBitmaps.put(index,
                                            new WeakReference<Bitmap>(map));
                                }
                            }
                            if (map == null) {
                                holder.unlockCanvasAndPost(canvas);
                                continue;
                            }
                            mPaint.setXfermode(new PorterDuffXfermode(
                                    Mode.CLEAR));
                            canvas.drawPaint(mPaint);
                            mPaint.setXfermode(new PorterDuffXfermode(Mode.SRC));
                            Bitmap dstbmp =null;
                            weakBitmap=cweakBitmaps.get(index);
                            if(weakBitmap==null){
                                dstbmp = Bitmap.createBitmap(map, 0, 0,
                                        map.getWidth(), map.getHeight(),
                                        matrix, true);
                                cweakBitmaps.put(index,
                                        new WeakReference<Bitmap>(dstbmp));
                            }else{
                                dstbmp=weakBitmap.get();
                                if(dstbmp==null){
                                    dstbmp = Bitmap.createBitmap(map, 0, 0,
                                            map.getWidth(), map.getHeight(),
                                            matrix, true);
                                    cweakBitmaps.put(index,
                                            new WeakReference<Bitmap>(dstbmp));
                                }
                            }
                            canvas.drawBitmap(
                                    map,
                                    frames.getSpriteSourceSize().getX()
                                            + (int) (WIDTH
                                                    - (map.getWidth() * 1) - 150),
                                    frames.getSpriteSourceSize().getY()
                                            + (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            canvas.drawBitmap(dstbmp, frames
                                    .getSpriteSourceSize().getX()
                                    + (int) (WIDTH + 150), frames
                                    .getSpriteSourceSize().getY()
                                    + (int) (HEIGHT - (map.getHeight() / 2)),
                                    mPaint);
                            if (dstbmp.isRecycled()) {
                                dstbmp.recycle();
                            }
                            if (map.isRecycled()) {
                                map.recycle();
                            }
                        }
                        if (!isstart) {
                            if (index < KidbotRobotApplication.animSad
                                    .getFrames().size()) {
                                index++;
                                if (index == KidbotRobotApplication.animSad
                                        .getFrames().size()) {
                                    index--;
                                    isstart = true;
                                }
                            } else {
                                index--;
                                isstart = true;
                            }
                        } else {
                            if (index > 0) {
                                index--;
                                if (index == 0) {
                                    isstart = false;
                                }
                            } else {
                                index++;
                                isstart = false;
                            }
                        }
                        break;
                    default:
                        break;
                    }
                }
                holder.unlockCanvasAndPost(canvas);
                try {
                    Thread.sleep(rate);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        public void stopThread() {
            isrunning = false;
            try {
                join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void setRate(int rate) {
        this.rate = rate;
    }

    public int getState() {
        return this.state;
    }

    public synchronized void setState(int state) {
        // if (FaceBean.BLINK == this.state) {
        // while ((index != KidbotRobotApplication.animBlink.getFrames()
        // .size() - 1)) {
        // continue;
        // }
        // }
        cweakBitmaps.clear();
        weakBitmaps.clear();
        this.state = state;
        this.index = 0;

        switch (state) {
        case FaceBean.BLINK:
            try {
                bitmapRegionDecoder = BitmapRegionDecoder.newInstance(
                        getContext().getAssets().open("kidbot_blink.png"),
                        false);
            } catch (IOException e) {
                e.printStackTrace();
            }
            break;
        case FaceBean.ANGRY:
            try {
                bitmapRegionDecoder = BitmapRegionDecoder.newInstance(
                        getContext().getAssets().open("kidbot_angry.png"),
                        false);
            } catch (IOException e) {
                e.printStackTrace();
            }
            break;
        case FaceBean.HAPPY:
            try {
                bitmapRegionDecoder = BitmapRegionDecoder.newInstance(
                        getContext().getAssets().open("kidbot_happy.png"),
                        false);
            } catch (IOException e) {
                e.printStackTrace();
            }
            break;
        case FaceBean.RESOLVE:
            try {
                bitmapRegionDecoder = BitmapRegionDecoder.newInstance(
                        getContext().getAssets().open("kidbot_blink.png"),
                        false);
            } catch (IOException e) {
                e.printStackTrace();
            }
            break;
        case FaceBean.RISUS:
            try {
                bitmapRegionDecoder = BitmapRegionDecoder.newInstance(
                        getContext().getAssets().open("kidbot_blink.png"),
                        false);
            } catch (IOException e) {
                e.printStackTrace();
            }
            break;
        case FaceBean.SEERIGHT:
            break;
        case FaceBean.SAD:
            try {
                bitmapRegionDecoder = BitmapRegionDecoder.newInstance(
                        getContext().getAssets().open("kidbot_sad.png"), false);
            } catch (IOException e) {
                e.printStackTrace();
            }
            break;
        }
    }

    public synchronized void setRunning(boolean isrunning) {
        this.isrunning = isrunning;
    }

    public synchronized void addIndex() {
        this.index++;
    }

}