Android 中加載幾百張圖片做幀動畫防止 OOM 的解決方案
最近,項目中有個需求:就是要做一個幀動畫,按理說這個是很簡單的!但是我能說這個幀動畫擁有幾百張圖片嗎?。。。。。。
填坑一 ---幀動畫
一開始我的想法是直接用幀動畫來做,可是我太天真了,當幀數放到 50 幾張的時候,已經在有些機器上奔潰了!所以這個方案否決!
填坑二 ---GIF動圖
雖然可以顯示,但是已經卡的我,已經不想看了,直接放棄
填坑三 ---視頻
在這里,我突然想到我可以直接把他做成一個小視頻啊,而且可以極限壓縮視頻。最終,視頻大小被壓縮到 500K 左右。此時已經基本可以滿足需求了,但是我們有好多類似的動畫,要求在每個動畫切換的時候要有銜接感,不能有突兀的感覺,所有在這里視頻就不能很好的完成任務了,所有再次放棄,已經淚牛滿面了!!!!
填坑四 --- SurfaceView + BitmapRegionDecoder +緩存
首先回答一下:為什么會想到這個解決方案?
- 首先在做幀動畫的時候,大約每幀之間的時間差值是 40ms 可以說速度非常快了,在如此快速的圖片切換上,自然而然的想到來了使用SurfaceView。
- 現在再來說說為什么想到要使用這個類
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
格式的文件,裁剪圖片。
- 最后說一下為什么使用緩存,其實很簡單,因為切換的頻率實在太高了,沒有必要每次都從圖片中裁剪,這里就把裁剪出來的
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++;
}
}