最近公司有一个项目需求,需要在一个特定的android设备上做一组帧动画的效果,当时感觉这个功能很容易,相信大家都知道,android的原生帧动画实现方法,但是问题就出现在这,如果使用原生的帧动画,在手机上是没有问题的,但是在公司的设备上就非常卡顿,也就是低端机,由于图片过多,效果异常卡顿,所以在这篇文章中就说一下帧动画的优化问题。

      首先还是先来看一下android原生的帧动画的实现,代码如下:

      (1)帧动画的资源文件 放入res/drawable下

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@drawable/animation2"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation3"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation4"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation5"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation6"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation7"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation8"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation9"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation10"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation11"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation12"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation13"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation14"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation15"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation16"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation17"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation18"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation19"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation20"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation21"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation22"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation23"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation24"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation25"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation26"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation27"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation28"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation29"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation30"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation31"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation32"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation33"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation34"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation35"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation36"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation37"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation38"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation39"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation40"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation41"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation42"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation43"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation44"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation45"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation46"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation47"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation48"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation49"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation50"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation51"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation52"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation53"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation54"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation55"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation56"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation57"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation58"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation59"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation60"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation61"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation62"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation63"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation64"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation65"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation66"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation67"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation68"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation69"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation70"
        android:duration="30"/>
    <item
        android:drawable="@drawable/animation71"
        android:duration="30"/>
</animation-list>

(2)布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.fareast.dealframeanimation.MainActivity">
    <Button
        android:id="@+id/start_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始动画"/>
    <ImageView
        android:id="@+id/animation_img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        />

</LinearLayout>

(3)Java代码实现开启帧动画

//animationImg为ImageView的控件
animationImg.setImageResource(R.drawable.animation_list);
AnimationDrawable animationDrawable= (AnimationDrawable) animationImg.getDrawable();
animationDrawable.start();

以上是android原生实现帧动画的,如果图片比较少可以这样使用,但是图片过多的话在低端机上就会造成卡顿,甚至会出现OOM异常,为了解决这个问题,可以把帧动画的图片一张张的分开,读到哪一张图就加载哪一张图,加载后把上一张图片释放。具体代码如下:

(1)帧动画的资源文件 放入res/values/arrays.xml文件中 如果没有arrays.xml文件就新建一个

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="loading_anim">
        <item>@drawable/animation2</item>
        <item>@drawable/animation3</item>
        <item>@drawable/animation4</item>
        <item>@drawable/animation5</item>
        <item>@drawable/animation6</item>
        <item>@drawable/animation7</item>
        <item>@drawable/animation8</item>
        <item>@drawable/animation9</item>
        <item>@drawable/animation10</item>
        <item>@drawable/animation11</item>
        <item>@drawable/animation12</item>
        <item>@drawable/animation13</item>
        <item>@drawable/animation14</item>
        <item>@drawable/animation15</item>
        <item>@drawable/animation16</item>
        <item>@drawable/animation17</item>
        <item>@drawable/animation18</item>
        <item>@drawable/animation19</item>
        <item>@drawable/animation20</item>
        <item>@drawable/animation21</item>
        <item>@drawable/animation22</item>
        <item>@drawable/animation23</item>
        <item>@drawable/animation24</item>
        <item>@drawable/animation25</item>
        <item>@drawable/animation26</item>
        <item>@drawable/animation27</item>
        <item>@drawable/animation28</item>
        <item>@drawable/animation29</item>
        <item>@drawable/animation30</item>
        <item>@drawable/animation31</item>
        <item>@drawable/animation32</item>
        <item>@drawable/animation33</item>
        <item>@drawable/animation34</item>
        <item>@drawable/animation35</item>
        <item>@drawable/animation36</item>
        <item>@drawable/animation37</item>
        <item>@drawable/animation38</item>
        <item>@drawable/animation39</item>
        <item>@drawable/animation40</item>
        <item>@drawable/animation41</item>
        <item>@drawable/animation42</item>
        <item>@drawable/animation43</item>
        <item>@drawable/animation44</item>
        <item>@drawable/animation45</item>
        <item>@drawable/animation46</item>
        <item>@drawable/animation47</item>
        <item>@drawable/animation48</item>
        <item>@drawable/animation49</item>
        <item>@drawable/animation50</item>
        <item>@drawable/animation51</item>
        <item>@drawable/animation52</item>
        <item>@drawable/animation53</item>
        <item>@drawable/animation54</item>
        <item>@drawable/animation55</item>
        <item>@drawable/animation56</item>
        <item>@drawable/animation57</item>
        <item>@drawable/animation58</item>
        <item>@drawable/animation59</item>
        <item>@drawable/animation60</item>
        <item>@drawable/animation61</item>
        <item>@drawable/animation62</item>
        <item>@drawable/animation63</item>
        <item>@drawable/animation64</item>
        <item>@drawable/animation65</item>
        <item>@drawable/animation66</item>
        <item>@drawable/animation67</item>
        <item>@drawable/animation68</item>
        <item>@drawable/animation69</item>
        <item>@drawable/animation70</item>
        <item>@drawable/animation71</item>
    </string-array>
</resources>

(2)布局文件同上面的原生帧动画的布局文件一样,这里不再多做声明。创建一个类继承自Application 在这个类中获取全局上下文,构建单例,别忘记在AndroidManifest.xml中注册Application否则无效,代码如下:

public class MyApplication extends Application {

    private static Context mContext;

    @Override public void onCreate() {
        super.onCreate();
//        Fresco.initialize(this);//Fresco初始化
        MyApplication.mContext = getApplicationContext();
    }

    public static Context getAppContext() {
        return MyApplication.mContext;
    }

}

(3)功能类 在此处理帧动画的加载的问题 播放 暂停帧动画的功能,具体情况如下代码:

public class AnimationsContainer {
    public int FPS = 40;  // 每秒播放帧数,fps = 1/t,t-动画两帧时间间隔
    private int resId = R.array.loading_anim; //图片资源
    private Context mContext = MyApplication.getAppContext();
    // 单例
    private static AnimationsContainer mInstance;


    public AnimationsContainer(){
    }
    //获取单例
    public static AnimationsContainer getInstance(int resId, int fps) {
        if (mInstance == null){
            mInstance = new AnimationsContainer();
        }
        mInstance.setResId(resId, fps);
        return mInstance;
    }

    public void setResId(int resId, int fps){
        this.resId = resId;
        this.FPS = fps;
    }
//    // 从xml中读取资源ID数组
//    private int[] mProgressAnimFrames = getData(resId);

    /**
     * @param imageView
     * @return progress dialog animation
     */
    public FramesSequenceAnimation createProgressDialogAnim(ImageView imageView) {
        return new FramesSequenceAnimation(imageView, getData(resId), FPS);
    }


    /**
     * 循环读取帧---循环播放帧
     */
    public class FramesSequenceAnimation {
        private int[] mFrames; // 帧数组
        private int mIndex; // 当前帧
        private boolean mShouldRun; // 开始/停止播放用
        private boolean mIsRunning; // 动画是否正在播放,防止重复播放
        private SoftReference<ImageView> mSoftReferenceImageView; // 软引用ImageView,以便及时释放掉
        private Handler mHandler;
        private int mDelayMillis;
        private OnAnimationStoppedListener mOnAnimationStoppedListener; //播放停止监听

        private Bitmap mBitmap = null;
        private BitmapFactory.Options mBitmapOptions;//Bitmap管理类,可有效减少Bitmap的OOM问题

        public FramesSequenceAnimation(ImageView imageView, int[] frames, int fps) {
            mHandler = new Handler();
            mFrames = frames;
            mIndex = -1;
            mSoftReferenceImageView = new SoftReference<ImageView>(imageView);
            mShouldRun = false;
            mIsRunning = false;
            mDelayMillis = 1000 / fps;//帧动画时间间隔,毫秒

            imageView.setImageResource(mFrames[0]);

            // 当图片大小类型相同时进行复用,避免频繁GC
            if (Build.VERSION.SDK_INT >= 11) {
                Bitmap bmp = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
                int width = bmp.getWidth();
                int height = bmp.getHeight();
                Bitmap.Config config = bmp.getConfig();
                mBitmap = Bitmap.createBitmap(width, height, config);
                mBitmapOptions = new BitmapFactory.Options();
                //设置Bitmap内存复用
                mBitmapOptions.inBitmap = mBitmap;//Bitmap复用内存块,类似对象池,避免不必要的内存分配和回收
                mBitmapOptions.inMutable = true;//解码时返回可变Bitmap
                mBitmapOptions.inSampleSize = 1;//缩放比例
            }
        }
        //循环读取下一帧
        private int getNext() {
            mIndex++;
            //设置只播放一次动画
//            if(mIndex==mFrames.length-1){
//                stop();
//            }
            if (mIndex >= mFrames.length)
                mIndex = 0;
            return mFrames[mIndex];
        }

        /**
         * 播放动画,同步锁防止多线程读帧时,数据安全问题
         */
        public synchronized void start() {
            mShouldRun = true;
            if (mIsRunning)
                return;

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

                    mIsRunning = true;
                    //新开线程去读下一帧
                    mHandler.postDelayed(this, mDelayMillis);

                    if (imageView.isShown()) {
                        int imageRes = getNext();
                        if (mBitmap != null) { // so Build.VERSION.SDK_INT >= 11
                            Bitmap bitmap = null;
                            try {
                                bitmap = BitmapFactory.decodeResource(imageView.getResources(), imageRes, mBitmapOptions);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            if (bitmap != null) {
                                imageView.setImageBitmap(bitmap);
                            } else {
                                imageView.setImageResource(imageRes);
                                mBitmap.recycle();
                                mBitmap = null;
                            }
                        } else {
                            imageView.setImageResource(imageRes);
                        }
                    }

                }
            };

            mHandler.post(runnable);
        }

        /**
         * 停止播放
         */
        public synchronized void stop() {
            mShouldRun = false;
        }

        /**
         * 设置停止播放监听
         * @param listener
         */
        public void setOnAnimStopListener(OnAnimationStoppedListener listener){
            this.mOnAnimationStoppedListener = listener;
        }
    }

    /**
     * 从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;
    }

    /**
     * 停止播放监听
     */
    public interface OnAnimationStoppedListener{
        void AnimationStopped();
    }

}

(4)调用优化后的帧动画:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button startBtn;
    private ImageView animationImg;
    private AnimationsContainer.FramesSequenceAnimation animation;
    private boolean start = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startBtn=findViewById(R.id.start_btn);
        animationImg=findViewById(R.id.animation_img);
        startBtn.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if(animation == null){
            //优化后的帧动画
            animation = AnimationsContainer.getInstance(R.array.loading_anim, 40).createProgressDialogAnim(animationImg);
        }
        if(!switchBtn()){
            animation.start();
        }else {
            animation.stop();
        }
    }

    //控制开关
    private boolean switchBtn(){
        boolean returnV = start;
        start = !start;
        return returnV;
    }

}

以上为解决帧动画卡顿的总结和解决方案,如有问题请留言,谢谢各位。