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++;
} }

Android 中加载几百张图片做帧动画防止 OOM 的解决方案的更多相关文章

  1. Android中加载位图的方法

    Android中加载位图的关键的代码: AssetManager assets =context.getAssets(); //用一个AssetManager 对象来从应用程序包的已编译资源中为工程加 ...

  2. Android中加载事件的方式

    Android中加载事件的方式 通过内部类的方式实现 通过外部类的方式实现 通过属性的方式实现 通过自身实现接口的方式实现 通过内部类的方式实现 Demo btn_Login.setOnClickLi ...

  3. android中加载的html获取的宽高不正确

    wap页面使用 js库是zepto,按照惯例在$(function(){})中,来获取当前可视区的宽高,但得到的宽高却与预想的相差十万八千里. 原本android手机的浏览器设定的宽高基本是360*6 ...

  4. Android 中加载本地Html 跨域问题,http协议允许加载

    一.需求: 后台加载HTML的包时间太长,太卡,让把所有的HTML包放到前台:使用的是file://协议,有些内容和样式加载不出来,H5那边说需要用http://协议来加载: 二.处理过程: 安卓最简 ...

  5. Android高效加载大图、多图解决方案,有效避免程序内存溢出现象

    好久没有写博客了,今天就先写一个小的关于在Android中加载大图如何避免内存溢出的问题. 后面会写如何使用缓存技术的核心类,android.support.v4.util.LruCache来加载图片 ...

  6. Android图片加载框架Picasso最全使用教程1

    Picasso介绍 Picasso是Square公司开源的一个Android图形缓存库 A powerful image downloading and caching library for And ...

  7. Android图片加载库:最全面的Picasso讲解

    前言 上文已经对当今 Android主流的图片加载库 进行了全面介绍 & 对比 如果你还没阅读,我建议你先移步这里阅读 今天我们来学习其中一个Android主流的图片加载库的使用 - Pica ...

  8. Android动态加载so文件

    在Android中调用动态库文件(*.so)都是通过jni的方式,而且往往在apk或jar包中调用so文件时,都要将对应so文件打包进apk或jar包,工程目录下图: 以上方式的存在的问题: 1.缺少 ...

  9. Android应用开发提高系列(4)——Android动态加载(上)——加载未安装APK中的类

    前言 近期做换肤功能,由于换肤程度较高,受限于平台本身,实现起来较复杂,暂时搁置了该功能,但也积累了一些经验,将分两篇文章来写这部分的内容,欢迎交流! 关键字:Android动态加载 声明 欢迎转载, ...

随机推荐

  1. 快速掌握Gif动态图实现代码

    版权声明:本文为博主原创文章,未经博主允许不得转载. 前言:Gif一种动态图片,网上有很多制作这个的工具,包括PS都有,但作为一名程序员,我觉得如果自己通过编写代码把它实现,不但是对代码的掌握与复习, ...

  2. Oracle GoldenGate (ogg) 11.2.1.0.20 是最后一个支持oracle db 10g的 ogg版本号

    參考原文: Oracle GoldenGate 11.2.1.0.22 Patch Set Availability (Doc ID 1669160.1) 该文章不做翻译,只摘录当中有价值的信息,例如 ...

  3. 温故而知新 chrome 浏览器一些小技巧、小细节

    1.console 模块如何换行? shift + enter即可. 2.有时候 network 没有分类标签(xhr.img.js.css)怎么办? 按下这个图标就可以显示出来了

  4. 自己工作之余做的OSX小软件

    ShareSDK是为iOS.Android.WindowsPhone提供社会功能的一个组件,开发者只需10分钟即可集成到自己的APP中,它不仅支持分享给QQ好友.微信好友.微信朋友圈.新浪微博.腾迅微 ...

  5. 关于release后retainCount还是1的问题

    转自:http://www.cocoachina.com/bbs/read.php?tid=175523 realse之后再调用还能调用的的问题,我做了这么多年也是经常遇到,也曾经试图寻找原因, 就像 ...

  6. HttpURLConnection上传文件

    HttpURLConnection上传文件 import java.io.BufferedReader; import java.io.DataInputStream; import java.io. ...

  7. python内置函数之print()

    定义:将值打印到一个流对象,或者默认打印到sys.stdout. 语法: print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=Fal ...

  8. 【C语言】19-static和extern关键字1-对函数的作用

    一.extern与函数 在前面我提到过一句话:如果一个程序中有多个源文件(.c),编译成功会生成对应的多个目标文件(.obj),这些目标文件还不能单独运行,因为这些目标文件之间可能会有关联,比如a.o ...

  9. CEF Xilium.CefGlue 在当前窗体中打开全部链接(防止弹窗)

    我们在使用Xilium.CefGlue编写浏览器应用程序时.对于嵌入的网页假设有链接会在新窗体打开.这种用户体验会非常差.因此我们须要改动程序,使全部链接都在当前窗体中打开. 首先引用Xilium.C ...

  10. Oracle DBA面试突击题

    一份ORACLE DBA面试题 一:SQL tuning 类 1:列举几种表连接方式 答: Oracle的多表连接算法有Nest Loop.Sort Merge和Hash Join三大类,每一类又可以 ...