如何解决Android帧动画出现的内存溢出

这几天在做动画的时候,遇到了一个OOM的问题,特此记录下来。

普通实现

实现一个帧动画,最先想到的就是用animation-list将全部图片按顺序放入,并设置时间间隔和播放模式。然后将该drawable设置给ImageView或Progressbar就OK了。

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
                android:oneshot="false">

    <item android:drawable="@drawable/smile0" android:duration="30"/>
    <item android:drawable="@drawable/smile1" android:duration="30"/>
    <item android:drawable="@drawable/smile2" android:duration="30"/>
    <item android:drawable="@drawable/smile3" android:duration="30"/>
    <item android:drawable="@drawable/smile4" android:duration="30"/>
</animation-list>

但是如果图片太多了,而且每张图片几百K的情况,就会出现OOM的问题。可以参考Stack Overflow上的这个问题Causing OutOfMemoryError in Frame by Frame Animation in Android

造成OOM的原因是因为帧动画从xml中读取图片的时候,一次性读取了所有的图片,并设置给ImageView,所以在图片数量过多和图片过大的时候,就会出现OOM。既然知道了原因,那么解决思路也很简单,就是在进行帧动画显示的时候,不要一下子读取所有的图片,而是需要谁就读取谁。

在github上找到一个例子,tigerjj/FasterAnimationsContainer,具体实现代码如下

public class FasterAnimationsContainer {
	private class AnimationFrame{
		private int mResourceId;
		private int mDuration;
		AnimationFrame(int resourceId, int duration){
			mResourceId = resourceId;
			mDuration = duration;
		}
		public int getResourceId() {
			return mResourceId;
		}
		public int getDuration() {
			return mDuration;
		}
	}
	private ArrayList<AnimationFrame> mAnimationFrames; // list for all frames of animation
	private int mIndex; // index of current frame

	private boolean mShouldRun; // true if the animation should continue running. Used to stop the animation
	private boolean mIsRunning; // true if the animation prevents starting the animation twice
	private SoftReference<ImageView> mSoftReferenceImageView; // Used to prevent holding ImageView when it should be dead.
	private Handler mHandler; // Handler to communication with UIThread

	private Bitmap mRecycleBitmap;  //Bitmap can recycle by inBitmap is SDK Version >=11

	// Listeners
	private OnAnimationStoppedListener mOnAnimationStoppedListener;
	private OnAnimationFrameChangedListener mOnAnimationFrameChangedListener;

	private FasterAnimationsContainer(ImageView imageView, Context mContext) {
		this.mContext = mContext;
		init(imageView, mContext);
	};

	// single instance procedures
	private static FasterAnimationsContainer sInstance;

	private Context mContext;

	public static FasterAnimationsContainer getInstance(ImageView imageView, Context mContext) {
		if (sInstance == null)
			sInstance = new FasterAnimationsContainer(imageView, mContext);
		sInstance.mRecycleBitmap = null;
		return sInstance;
	}

	/**
	 * initialize imageview and frames
	 * @param imageView
	 * @param mContext
	 */
	public void init(ImageView imageView, Context mContext){
		mAnimationFrames = new ArrayList<AnimationFrame>();
		mSoftReferenceImageView = new SoftReference<ImageView>(imageView);

		mHandler = new Handler();
		if(mIsRunning == true){
			stop();
		}

		mShouldRun = false;
		mIsRunning = false;

		mIndex = -1;
	}

	/**
	 * add a frame of animation
	 * @param index index of animation
	 * @param resId resource id of drawable
	 * @param interval milliseconds
	 */
	public void addFrame(int index, int resId, int interval){
		mAnimationFrames.add(index, new AnimationFrame(resId, interval));
	}

	/**
	 * add a frame of animation
	 * @param resId resource id of drawable
	 * @param interval milliseconds
	 */
	public void addFrame(int resId, int interval){
		mAnimationFrames.add(new AnimationFrame(resId, interval));
	}

	/**
	 * add all frames of animation
	 * @param resId resource id of drawable
	 * @param interval milliseconds
	 */
	public void addAllFrames(int resId, int interval){
		int[] drawableIds = getData(resId);
		for(int drawableId : drawableIds){
			mAnimationFrames.add(new AnimationFrame(drawableId, interval));
		}
	}

	/**
	 * 从xml中读取帧数组
	 * @param resId
	 * @return
	 */
	private int[] getData(int resId){
		TypedArray array = mContext.getResources().obtainTypedArray(resId);

		int len = array.length();
		int[] intArray = new int[array.length()];

		for(int i = 0; i < len; i++){
			intArray[i] = array.getResourceId(i, 0);
		}
		array.recycle();
		return intArray;
	}

	/**
	 * remove a frame with index
	 * @param index index of animation
	 */
	public void removeFrame(int index){
		mAnimationFrames.remove(index);
	}

	/**
	 * clear all frames
	 */
	public void removeAllFrames(){
		mAnimationFrames.clear();
	}

	/**
	 * change a frame of animation
	 * @param index index of animation
	 * @param resId resource id of drawable
	 * @param interval milliseconds
	 */
	public void replaceFrame(int index, int resId, int interval){
		mAnimationFrames.set(index, new AnimationFrame(resId, interval));
	}

	private AnimationFrame getNext() {
		mIndex++;
		if (mIndex >= mAnimationFrames.size())
			mIndex = 0;
		return mAnimationFrames.get(mIndex);
	}

	/**
	 * Listener of animation to detect stopped
	 *
	 */
	public interface OnAnimationStoppedListener{
		public void onAnimationStopped();
	}

	/**
	 * Listener of animation to get index
	 *
	 */
	public interface OnAnimationFrameChangedListener{
		public void onAnimationFrameChanged(int index);
	}


	/**
	 * set a listener for OnAnimationStoppedListener
	 * @param listener OnAnimationStoppedListener
	 */
	public void setOnAnimationStoppedListener(OnAnimationStoppedListener listener){
		mOnAnimationStoppedListener = listener;
	}

	/**
	 * set a listener for OnAnimationFrameChangedListener
	 * @param listener OnAnimationFrameChangedListener
	 */
	public void setOnAnimationFrameChangedListener(OnAnimationFrameChangedListener listener){
		mOnAnimationFrameChangedListener = listener;
	}

	/**
	 * Starts the animation
	 */
	public synchronized void start() {
		mShouldRun = true;
		if (mIsRunning)
			return;
		mHandler.post(new FramesSequenceAnimation());
	}

	/**
	 * Stops the animation
	 */
	public synchronized void stop() {
		mShouldRun = false;
	}

	private class FramesSequenceAnimation implements Runnable{

		@Override
		public void run() {
			ImageView imageView = mSoftReferenceImageView.get();
			if (!mShouldRun || imageView == null) {
				mIsRunning = false;
				if (mOnAnimationStoppedListener != null) {
					mOnAnimationStoppedListener.onAnimationStopped();
				}
				return;
			}
			mIsRunning = true;

			if (imageView.isShown()) {
				AnimationFrame frame = getNext();
				GetImageDrawableTask task = new GetImageDrawableTask(imageView);
				task.execute(frame.getResourceId());
				// TODO postDelayed after onPostExecute
				mHandler.postDelayed(this, frame.getDuration());
			}
		}
	}

	private class GetImageDrawableTask extends AsyncTask<Integer, Void, Drawable> {

		private ImageView mImageView;

		public GetImageDrawableTask(ImageView imageView) {
			mImageView = imageView;
		}

		@SuppressLint("NewApi")
		@Override
		protected Drawable doInBackground(Integer... params) {
			if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB){
				return mContext.getResources().getDrawable(params[0]);
			}
			BitmapFactory.Options options = new BitmapFactory.Options();
			options.inMutable = true;
			if (mRecycleBitmap != null)
				options.inBitmap = mRecycleBitmap;
			mRecycleBitmap = BitmapFactory.decodeResource(mContext.getResources(), params[0], options);
			BitmapDrawable drawable = new BitmapDrawable(mContext.getResources(),mRecycleBitmap);
			return drawable;
		}

		@Override
		protected void onPostExecute(Drawable result) {
			super.onPostExecute(result);
			if(result!=null) mImageView.setImageDrawable(result);
			if (mOnAnimationFrameChangedListener != null)
				mOnAnimationFrameChangedListener.onAnimationFrameChanged(mIndex);
		}

	}

原文地址:https://www.cnblogs.com/xl-phoenix/p/6922519.html