Android使用SurfaceView代替AnimationDrawable播放多图帧动画,避免OOM和卡顿

来源:互联网 发布:织梦cms邀请码 编辑:程序博客网 时间:2024/05/16 10:03

关于Android帧动画

       当我们在应用中需要使用帧动画的时候,最先想到的就是Android提供的AnimationDrawable了,但是如果你的帧动画中如果包含上百帧图片,此时再用AnimationDrawable就不是那么理想了。AnimationDrawable是使用一个Drawable数组来存储每一帧的图像的,当帧动画中包含非常多的图片时,AnimationDrawable在加载图片的过程中会有卡顿现象,很多图片被加载进内存,在一些低端设备上很容易造成OOM。

使用SurfaceView来实现帧动画的效果

    最近的项目中需要用到大量的帧动画(多的高达200帧了),在低端手机一跑就OOM的,即使是旗舰机也是会有卡顿的现象。

第一个想到的解决办法就是用openGL来绘制了。

   因为是直播的项目,直接在GLsurfaceView中绘制的,用OpenGL直接绘制一层Texture直接推流还省事。。只在主播端处理就行了,但是IOS那边都弄得差不多了,直接原生的不用处理也不会有什么异常什么的。。所以只能走即时通信了,想想IOS真幸福。。。

第二个就是使用Android自带的surfaceView了

    好吧,第一个不行那就想到Android自带的surfaceView啦。我首先用不同的手机测试了下应用从本地decode一个bitmap的时间(png格式,414*736,大小在30-100k之间),因为帧动画的每帧不会太大,在性能好点的设备上基本保持在10-30ms之间(不推流基本上推流状态下10ms左右,推流状态下20左右),在性能稍差的设备中基本上也不会超过50ms,所以说是没什么大问题的。

 实现

     代码其实很简单,其实就是一个线程缓存,一个线程绘制,因为对fps要求不高,所以我只保存了5个bitmap在内存中,已经可以完全跟上绘制的节奏了,最后测试了下即使每帧50ms也是没有问题的。如果你的每帧图片比较大,可以适当的增加缓存数量。下面是代码,因为是写的demo,为了测试方便,图片时放在assets目录下的,每个帧动画中的图片放在一个单独的文件夹下就行了。
内存 CPU使用情况

public class MainActivity extends AppCompatActivity implements View.OnClickListener {private SurfaceView sv_main;private SurfaceHolder surfaceHolder;private SvCallback callback;private Map<String,Bitmap> bitmapCache =new HashMap<>();private String[] assets;private String folderName="daku";private  AssetManager assetManager;private int totalCount;private Handler decodeHandler;private Thread decodeThread;private Button btn_am1;private Button btn_am2;private Button btn_am3;private Button btn_am4;@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main);    initView();    initData();}private void initView() {    sv_main = (SurfaceView) findViewById(R.id.sv_main);    btn_am1 = (Button) findViewById(R.id.btn_am1);    btn_am2 = (Button) findViewById(R.id.btn_am2);    btn_am3 = (Button) findViewById(R.id.btn_am3);    btn_am4 = (Button) findViewById(R.id.btn_am4);    btn_am1.setOnClickListener(this);    btn_am2.setOnClickListener(this);    btn_am3.setOnClickListener(this);    btn_am4.setOnClickListener(this);}private void initData(){    assetManager=getAssets();    surfaceHolder=sv_main.getHolder();    callback=new SvCallback();    sv_main.setZOrderOnTop(true);    surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);    surfaceHolder.addCallback(callback);}@Overridepublic void onClick(View v) {    if(callback.isDrawing)    {        callback.stopAnim();    }    switch (v.getId()) {        case R.id.btn_am1:            folderName="daku";            break;        case R.id.btn_am2:            folderName="crow";            break;        case R.id.btn_am3:            folderName="bullshit";            break;        case R.id.btn_am4:            folderName="huabanyu";            break;    }    try {        assets=assetManager.list(folderName);    } catch (IOException e) {        e.printStackTrace();    }    totalCount=assets.length;    startDecodeThread();}private class SvCallback implements SurfaceHolder.Callback{    private Canvas canvas;    private Bitmap currentBitmap;    private int position=0;    public boolean isDrawing=false;    private Thread drawThread;    private Rect rect;    @Override    public void surfaceCreated(SurfaceHolder surfaceHolder) {    }    @Override    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {        //full screen        rect=new Rect(0,0,width,height);    }    private void drawBitmap()    {        if(position>=totalCount)        {            isDrawing=false;            decodeHandler.sendEmptyMessage(-2);            canvas=surfaceHolder.lockCanvas();            //clear surfaceView            canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);            surfaceHolder.unlockCanvasAndPost(canvas);            return;        }        if(!bitmapCache.containsKey(assets[position]))        {            return;        }        currentBitmap=bitmapCache.get(assets[position]);        decodeHandler.sendEmptyMessage(position);        canvas=surfaceHolder.lockCanvas(rect);        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);        canvas.drawBitmap(currentBitmap,null,rect,null);        surfaceHolder.unlockCanvasAndPost(canvas);        currentBitmap.recycle();        position++;    }    @Override    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {        stopAnim();    }    public void startAnim()    {        isDrawing=true;        position=0;        drawThread=new Thread()        {            @Override            public void run()            {                super.run();                while (isDrawing)                {                    try                    {                        long now = System.currentTimeMillis();                        drawBitmap();                        //100ms draw one frame , you can change this time                        sleep(100 - (System.currentTimeMillis() - now)>0?100 - (System.currentTimeMillis() - now):0);                    } catch (InterruptedException e1)                    {                        e1.printStackTrace();                    }                }            }        };        drawThread.start();    }    public void stopAnim()    {        isDrawing=false;        position=0;        bitmapCache.clear();        assets=null;        //this is necessary        drawThread.interrupt();    }}private void startDecodeThread(){    decodeThread=new Thread()    {        @Override        public void run()        {            super.run();            Looper.prepare();            decodeHandler=new Handler(Looper.myLooper())            {                @Override                public void handleMessage(Message msg)                {                    super.handleMessage(msg);                    if(msg.what==-2)                    {                        getLooper().quit();                        return;                    }                    decodeBitmap(msg.what);                }            };            decodeBitmap(-1);            Looper.loop();        }    };    decodeThread.start();}private void decodeBitmap(int position){    try {        if(position==-1)        {            for(int i=0;i<5;i++)            {                bitmapCache.put(assets[i], BitmapFactory.decodeStream(assetManager.open(folderName + "/" + assets[i])));            }            callback.startAnim();        }else if(position==-2)        {            callback.stopAnim();        }else        {            if(position+5<=totalCount-1)            {                bitmapCache.remove(assets[position]);                bitmapCache.put(assets[position+5],BitmapFactory.decodeStream(assetManager.open(folderName + "/" + assets[position+5])));            }        }    } catch (IOException e) {        e.printStackTrace();    }}}

    因为只是写了个demo,可能还有考虑不周的地方,欢迎一起探讨。完整的项目 Github

0 0
原创粉丝点击