拒绝OOM,打造自定义帧动画

来源:互联网 发布:复读好不好知乎 编辑:程序博客网 时间:2024/06/18 05:27

android开发:拒绝OOM,打造自定义帧动画加载框架

转载请标明出处:漆可的博客http://blog.csdn.net/q649381130/article/details/49407129

一、概述

在安卓开发中,帧动画是通过逐帧显示配置在动画资源文件中的图片来实现。然而,这存在一个巨大的风险,由于安卓是一次性把动画资源文件中的所有图片资源全部加载,这就意味着如果图片数目过多,极其容易造成内存溢出。

那么,如何避免这种情况的发生呢。下面我们推出本文的主角SurfaceView。

二、SurfaceView详解

SurfaceView是一个专门用户绘制的view的子类,由于内部已做处理,并且实现了双缓冲,它可以在非UI线程中进行UI操作(谷歌仅为我们提供俩个有该特异功能的控件,另外一个是ProgressBar)。这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。广泛用于游戏开发中的绘制。

SurfaceView的使用非常简单,一般与SurfaceHolder结合使用:

  1. 创建一个集成了SurfaceView的类,并实现SurfaceHolder.Callback实现后SurfaceHolder.Callback必须实现了三个方法,意思也很明白,分别是:

    surfaceCreated:
    surfaceChanged:
    surfaceDestroyed:

  2. 通过getHolder()获取该类的SurfaceHolder;
  3. 调用SurfaceHolder的addCallback(this)方法添加回调;
  4. SurfaceHolder.lockCanvas()获得Canvas对象并锁定画布,该Cavas已经被自动加了同步锁;
  5. 利用获取Canvas的绘图方法进行绘制,本文我们是绘制bitmap;
  6. SurfaceHolder.unlockCanvasAndPost(Canvas canvas)结束锁定画图,并提交改变,将图形显示。

本文就是通过SurfaceView内部开启线程动态的绘制图片。

三、普通方法实现帧动画

先介绍下安卓原生的帧动画使用方法,废话少说,直接上代码,完整源码在文章最后提供下载地址。

在res目录下新建anim文件,该文件下创建frame_demo.xml文件:

<?xml version="1.0" encoding="utf-8"?><animation-list xmlns:android="http://schemas.android.com/apk/res/android"android:oneshot="true" ><item android:drawable="@drawable/penguin_happy_3_1"   android:duration="150"/><item android:drawable="@drawable/penguin_happy_3_2"   android:duration="150"/><item android:drawable="@drawable/penguin_happy_3_3"   android:duration="150"/><item android:drawable="@drawable/penguin_happy_3_4"   android:duration="150"/><item android:drawable="@drawable/penguin_happy_3_5"   android:duration="150"/><item android:drawable="@drawable/penguin_happy_3_6"   android:duration="150"/><item android:drawable="@drawable/penguin_happy_3_7"   android:duration="150"/><item android:drawable="@drawable/penguin_happy_3_8"   android:duration="150"/><item android:drawable="@drawable/penguin_happy_3_9"   android:duration="150"/><item android:drawable="@drawable/penguin_happy_3_10"   android:duration="150"/><item android:drawable="@drawable/penguin_happy_3_11"   android:duration="150"/><item android:drawable="@drawable/penguin_happy_3_12"   android:duration="150"/><item android:drawable="@drawable/penguin_happy_3_13"   android:duration="150"/></animation-list>

布局文件activity_original.xml
很简单,就是一个ImageView,scr属性为该动画资源:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="${relativePackage}.${activityClass}" ><ImageView    android:id="@+id/iv_orginal"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:layout_centerInParent="true"    android:src="@anim/frame_demo" /></RelativeLayout>

然后在activity实现动画播放:

ImageView iv_original = (ImageView) findViewById(R.id.iv_orginal);AnimationDrawable ani = (AnimationDrawable) iv_original.getDrawable();ani.start();//播放动画//打印出程序占用的内存Log.e(TAG, Runtime.getRuntime().totalMemory()/1024 + "k");

以上就是安卓开发中利用普通方法实现帧动画的方式,代码很简单,我们输出该方式播放时整个应用占用的内存大小为6.08M。

四、利用SurfaceView打造自定义帧动画播放框架

首先是我们自定义的动画框架,继承自SurfaceView,
完整代码如下:

public class FrameAnimation extends SurfaceView implements SurfaceHolder.Callback, Runnable {private SurfaceHolder mSurfaceHolder;private boolean mIsThreadRunning = true; // 线程运行开关private boolean mIsDestroy = false;// 是否已经销毁private int[] mBitmapResourceIds;// 用于播放动画的图片资源数组private Canvas mCanvas;private Bitmap mBitmap;// 显示的图片private int mCurrentIndext;// 当前动画播放的位置private int mGapTime = 150;// 每帧动画持续存在的时间private OnFrameFinishedListener mOnFrameFinishedListener;// 动画监听事件public FrameAnimation(Context context){    this(context, null);}public FrameAnimation(Context context, AttributeSet attrs, int defStyle){    super(context, attrs, defStyle);    mSurfaceHolder = this.getHolder();    mSurfaceHolder.addCallback(this);// 注册回调方法    // 白色背景    setZOrderOnTop(true);    mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);}public FrameAnimation(Context context, AttributeSet attrs){    this(context, attrs, 0);}@Overridepublic void surfaceCreated(SurfaceHolder holder){    // 创建surfaceView时启动线程    // new Thread(this).start();}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height){}@Overridepublic void surfaceDestroyed(SurfaceHolder holder){    // 当surfaceView销毁时, 停止线程的运行. 避免surfaceView销毁了线程还在运行而报错.    mIsThreadRunning = false;    try    {        Thread.sleep(mGapTime);    } catch (InterruptedException e)    {        e.printStackTrace();    }    mIsDestroy = true;}/** * 制图方法 */private void drawView(){    // 无资源文件退出    if (mBitmapResourceIds == null)    {        Log.e("frameview", "the bitmapsrcIDs is null");        mIsThreadRunning = false;        return;    }    // 锁定画布    mCanvas = mSurfaceHolder.lockCanvas();    try    {        if (mSurfaceHolder != null && mCanvas != null)        {            mCanvas.drawColor(Color.WHITE);            // 如果图片过大可以再次对图片进行二次采样缩放处理            mBitmap = BitmapFactory.decodeResource(getResources(), mBitmapResourceIds[mCurrentIndext]);            mCanvas.drawBitmap(mBitmap, (getWidth() - mBitmap.getWidth()) / 2,                    (getHeight() - mBitmap.getHeight()) / 2, null);            // 播放到最后一张图片,停止线程            if (mCurrentIndext == mBitmapResourceIds.length - 1)            {                mIsThreadRunning = false;            }        }    } catch (Exception e)    {        e.printStackTrace();    } finally    {        mCurrentIndext++;        if (mCanvas != null)        {            // 将画布解锁并显示在屏幕上            mSurfaceHolder.unlockCanvasAndPost(mCanvas);        }        if (mBitmap != null)        {            // 收回图片            mBitmap.recycle();        }    }}@Overridepublic void run(){    if (mOnFrameFinishedListener != null)    {        mOnFrameFinishedListener.onStart();    }    // 每隔100ms刷新屏幕    while (mIsThreadRunning)    {        drawView();        try        {            Thread.sleep(mGapTime);        } catch (Exception e)        {            e.printStackTrace();        }    }    if (mOnFrameFinishedListener != null)    {        mOnFrameFinishedListener.onStop();    }}/** * 开始动画 */public void start(){    if (!mIsDestroy)    {        mCurrentIndext = 0;        mIsThreadRunning = true;        new Thread(this).start();    } else    {        // 如果SurfaceHolder已经销毁抛出该异常        try        {            throw new Exception("IllegalArgumentException:Are you sure the SurfaceHolder is not destroyed");        } catch (Exception e)        {            e.printStackTrace();        }    }}/** * 设置动画播放素材 * @param bitmapResoursIds  图片资源id */public void setBitmapResoursID(int[] bitmapResourceIds){    this.mBitmapResourceIds = bitmapResourceIds;}/** * 设置每帧时间 */public void setGapTime(int gapTime){    this.mGapTime = gapTime;}/** * 结束动画 */public void stop(){    mIsThreadRunning = false;}/** * 继续动画 */public void reStart(){    mIsThreadRunning = false;}/** * 设置动画监听器 */public void setOnFrameFinisedListener(OnFrameFinishedListener onFrameFinishedListener){    this.mOnFrameFinishedListener = onFrameFinishedListener;}/** * 动画监听器 * @author qike * */public interface OnFrameFinishedListener {    /**     * 动画开始     */    void onStart();    /**     * 动画结束     */    void onStop();}/** * 当用户点击返回按钮时,停止线程,反转内存溢出 */@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event){    // 当按返回键时,将线程停止,避免surfaceView销毁了,而线程还在运行而报错    if (keyCode == KeyEvent.KEYCODE_BACK)    {        mIsThreadRunning = false;    }    return super.onKeyDown(keyCode, event);}

}

布局文件activity_main.xml,同样很简单:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@android:color/white"    tools:context="${relativePackage}.${activityClass}" ><com.example.frameanidemo.view.FrameAnimation    android:id="@+id/ani_view"    android:layout_width="match_parent"    android:layout_height="match_parent" /></RelativeLayout>

最后是activity:

public class CustomFrameActivity extends Activity {    protected static final String TAG = "Custom";    private FrameAnimation frameView;    //动画资源文件    int[] srcId =    { R.drawable.penguin_happy_3_1, R.drawable.penguin_happy_3_2, R.drawable.penguin_happy_3_3,        R.drawable.penguin_happy_3_4, R.drawable.penguin_happy_3_5, R.drawable.penguin_happy_3_6,        R.drawable.penguin_happy_3_7, R.drawable.penguin_happy_3_8, R.drawable.penguin_happy_3_9,        R.drawable.penguin_happy_3_10, R.drawable.penguin_happy_3_11, R.drawable.penguin_happy_3_12,        R.drawable.penguin_happy_3_13 };@Override    protected void onCreate(Bundle savedInstanceState)    {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        frameView = (FrameAnimation) findViewById(R.id.ani_view);        frameView.setBitmapResoursID(srcId);        //设置监听事件        frameView.setOnFrameFinisedListener(new FrameAnimation.OnFrameFinishedListener() {            @Override            public void onStop()            {                Log.e(TAG, "stop");            }            @Override            public void onStart()            {                Log.e(TAG, "start");                Log.e(TAG, Runtime.getRuntime().totalMemory() / 1024 + "k");            }        });        frameView.start();    }}

代码已经附上,看下这种方式所占的内存只有2.8M,比原生的6.08M节省了不是一丁点,这种差距尤其是在大量图片的播放中更是明显,经测试原生帧动画播放超过20张图片就非常危险,而我们自定义的与图片的多寡并无区别:
自定义帧动画所占内存

源码下载:http://download.csdn.net/detail/q649381130/9212135

2 0
原创粉丝点击