Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 520|回复: 0

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

[复制链接]
  • TA的每日心情
    奋斗
    2024-4-6 11:05
  • 签到天数: 748 天

    [LV.9]以坛为家II

    2034

    主题

    2092

    帖子

    70万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    705612
    发表于 2021-4-22 02:08:03 | 显示全部楼层 |阅读模式

    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图片:

    图片

    接下来就好做了,解析 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++;
    	}
    
    }
    
    
    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-5-3 14:44 , Processed in 0.067210 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表