拒绝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结合使用:
创建一个集成了SurfaceView的类,并实现SurfaceHolder.Callback实现后SurfaceHolder.Callback必须实现了三个方法,意思也很明白,分别是:
surfaceCreated:
surfaceChanged:
surfaceDestroyed:- 通过getHolder()获取该类的SurfaceHolder;
- 调用SurfaceHolder的addCallback(this)方法添加回调;
- SurfaceHolder.lockCanvas()获得Canvas对象并锁定画布,该Cavas已经被自动加了同步锁;
- 利用获取Canvas的绘图方法进行绘制,本文我们是绘制bitmap;
- 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
- 拒绝OOM,打造自定义帧动画
- Android帧动画OOM
- Android 逐帧动画oom解决办法
- android播放帧动画OOM问题解决
- Android 帧动画OOM问题优化
- Android 逐帧动画OOM的解决方法
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- Android 自定义 HorizontalScrollView 打造再多图片(控件)也不怕 OOM 的横向滑动效果
- perl - 单引号和双引号字符串
- 特别码字
- 【小白装系统】——BIOS简介
- 栈的知识
- 待更正
- 拒绝OOM,打造自定义帧动画
- javascript 简单导航菜单设计
- setter和getter方法
- C++利用栈进行十进制与二进制的转换
- hihoCoder之hiho一下 第六十九周 解题
- linux 下C编程(七) 之 杂杂的程序
- 五子棋项目开发日志
- Linux下修改主机IP地址的三种方法
- ViewPager封装工具类: 轻松实现APP导航或APP中的广告栏