最近有一个项目,有很多组帧动画,每一组大概60帧左右,再不同的状态下回调不同的接口,动态的切换到不同的帧动画,但是不管每一帧只有10K左右,使用软引用,提前加载帧动画,这些操作都试过了,但是再切换动画的时候还是有不时的有卡顿。由于图片过多,效果异常卡顿,所以在这篇文章中就说一下帧动画的优化问题。 首先还是先来看一下android原生的帧动画的实现,代码如下:
(1)帧动画的资源文件 放入res/drawable下<?xml version="1.0" encoding="utf-8"?>
<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"?>
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的控件
AnimationDrawable animationDrawable= (AnimationDrawable) animationImg.getDrawable();
animationDrawable.start();
以上是android原生实现帧动画的,如果图片比较少可以这样使用,但是图片过多的话在低端机上就会造成卡顿,甚至会出现OOM异常,为了解决这个问题,可以把帧动画的图片一张张的分开,读到哪一张图就加载哪一张图,加载后把上一张图片释放。具体代码如下:(1)帧动画的资源文件 放入res/values/arrays.xml文件中 如果没有arrays.xml文件就新建一个
<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否则无效,代码如下:
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;
}
}
此方案亲测可行,流畅,需注意的是fps为帧数而不是时间间隔噢。