• Android属性动画优化

    2018-09-01 11:16:40
    属性动画优化 思路: ①使用PropertyValuesHolder  ②使用Keyframe  ③animator.setRepeatCount(ValueAnimator.INFINITE)及时cancel() ④动画卡顿,可以考虑使用自定义控件实现,如果一个自定义不行,那...

    属性动画优化

    思路:

    ①使用PropertyValuesHolder 

    ②使用Keyframe 

    ③animator.setRepeatCount(ValueAnimator.INFINITE)及时cancel()

    ④动画卡顿,可以考虑使用自定义控件实现,如果一个自定义不行,那就是两个。。。(待续)

    一,PropertyValuesHolder 

            
            float currentX = textView.getTranslationX();
            float currentY = textView.getTranslationY();
            //传统写法
            ObjectAnimator tX = ObjectAnimator.ofFloat(textView, "translationX", currentX, -300, currentX);
            ObjectAnimator tY = ObjectAnimator.ofFloat(textView, "translationY", currentY, 200, currentY);
            AnimatorSet set = new AnimatorSet();
            set.setDuration(1000);
            set.playTogether(tX, tY);//或者  set.play(tX).with(tY);
            set.start();
    
            //一个view同时发生多种效果时,建议使用PropertyValuesHolder,这样ObjectAnimator只有一个对象
            PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("translationX", currentX, 500, currentX);
            PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("translationY", currentY, 400, currentY);
            ObjectAnimator.ofPropertyValuesHolder(textView, pvhX, pvhY).setDuration(1000).start();

    二,Keyframe 

    //传统写法
            ObjectAnimator transYFirstAnim = ObjectAnimator.ofFloat(textView, "translationY", 0, 100);
            ObjectAnimator transYSecondAnim = ObjectAnimator.ofFloat(textView, "translationY", 100, 0);
            AnimatorSet animSet = new AnimatorSet();
            animSet.playSequentially(transYFirstAnim, transYSecondAnim);
    
    
            //一个view的单个属性先后发生一系列变化时,建议使用Keyframe达到效果。从词义上来理解Keyframe是关键帧
            Keyframe k0 = Keyframe.ofFloat(0f, 0); //第一个参数为“何时”,第二个参数为“何地”
            Keyframe k1 = Keyframe.ofFloat(0.5f, 100);
            Keyframe k2 = Keyframe.ofFloat(1f, 0);
            PropertyValuesHolder p = PropertyValuesHolder.ofKeyframe("translationY", k0, k1, k2);
            ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(textView, p);
            objectAnimator.start();
            /*
                效果就是:
             开始时 位置为0;
             动画开始1/2时 位置为100;
             动画结束时 位置为0。
             */

    总结就是,如果是同一个view的一系列动画,均可使用以上组合方式达到只使用一个ObjectAnimator的效果 。多个view的动画用AnimatorSet进行动画组合和排序,代码架构和执行效率基本能达到最优化。

    三,及时取消无限循环动画

    许多时候用到了无限循环的动画,我们会这样写:animator.setRepeatCount(ValueAnimator.INFINITE); 
    这样写如果没有及时取消,会导致此属性动画持有被动画对象的引用而导致内存泄露,故在activity生命周期结束时,如onDestroy方法中,及时的cancel掉这种动画。 
    补间动画无此问题,它是给view做动画,在view的onDetachedFromWindow方法会取消掉动画,所以不会导致内存泄露。

     

    补充建议:

    1. 尽量不要在刷新时做耗时操作,必须准备数据,创建图片,图片变换等,数据和图片都应该在之前就加载到内存中,图片变换用canvas的变换来实现。
    2. 同一个界面中多个动画重叠出现时,尽量将动画的刷新过程统一进行刷新,避免频繁的invalidate,尤其是多个动画有时序上的关系时更应该统一。
    3. 尽量使用带有参数的invalidate来刷新,这样可以减少很多运算量。
    4. 合理的环境下使用surfaceview来操作,比如播放视频等,这种刷新耗时比较大的情况。
    5. 开启硬件加速,硬件加速由于采用了显示列表的概念,所以刷新过程也有很大的优化,但是会增加额外的8M内存占用。

    属性动画使用方法详解 可以参考郭霖大神的博客:https://blog.csdn.net/guolin_blog/article/details/43536355

    展开全文
  • 实现一个帧动画,最先想到的就是用animation-list将全部图片按顺序放入,并设置时间间隔和播放模式。然后将该drawable设置给ImageView或Progressbar就OK了。 首先创建帧动画资源文件drawable/anim.xml,oneshot=...

    普通实现
    实现一个帧动画,最先想到的就是用animation-list将全部图片按顺序放入,并设置时间间隔和播放模式。然后将该drawable设置给ImageView或Progressbar就OK了。 
    首先创建帧动画资源文件drawable/anim.xml,oneshot=false为循环播放模式,ture为单次播放;duration为每帧时间间隔,单位毫秒。

    <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
        android:oneshot="false" >
    
        <item
            android:drawable="@drawable/img0"
            android:duration="17" />
        <item
            android:drawable="@drawable/img1"
            android:duration="17" />
        <item
            android:drawable="@drawable/img2"
            android:duration="17" />
    </animation-list>

    然后在代码中找到播放该动画的ImageView,将资源赋给该控件就可以控制动画开始与结束了,是不是超简单。

    AnimationDrawable animationDrawable;
    if (imageView.getDrawable() == null) {
                         imageView.setImageResource(R.drawable.loading_anim);
                            animationDrawable = (AnimationDrawable) imageView.getDrawable();
    }
    animationDrawable.start();//开始
    animationDrawable.stop();//结束       
    • OOM问题及优化

    . 内存溢出咋办

    用普通方法实现帧动画用到普通场景是没问题的,如果碰到几十甚至几百帧图片,而且每张图片几百K的情况,呵呵。例如,做一个很炫的闪屏帧动画,要保证高清且动作丝滑,就需要至少几十张高清图片。这时,OOM问题就出来了,闪屏进化成了一闪~ 

    在StackOverflow上找到了解决办法: 
    http://stackoverflow.com/questions/8692328/causing-outofmemoryerror-in-frame-by-frame-animation-in-android

    . 解决思路 
    先分析下普通方法为啥会OOM,从xml中读取到图片id列表后就去硬盘中找这些图片资源,将图片全部读出来后按顺序设置给ImageView,利用视觉暂留效果实现了动画。一次拿出这么多图片,而系统都是以Bitmap位图形式读取的(作为OOM的常客,这锅Bitmap来背);而动画的播放是按顺序来的,大量Bitmap就排好队等待播放然后释放,然而这个排队的地方只有10平米,呵呵~发现问题了吧。 
    按照大神的思路,既然来这么多Bitmap,一次却只能临幸一个,那么就翻牌子吧,轮到谁就派个线程去叫谁,bitmap1叫到了得叫上下一位bitmap2做准备,这样更迭效率高一些。为了避免某个bitmap已被叫走了线程白跑一趟的情况,加个Synchronized同步下数据信息,实现代码如下:
     

    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);
            }

    进一步优化 
    为了快速读取SD卡中的图片资源,这里用到了Apache的IOUtils。在位图处理上使用了BitmapFactory.Options()相关设置,InBitmap,当图片大小类型相同时,虚拟机就对位图进行内存复用,不再分配新的内存,可以避免不必要的内存分配及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;//缩放比例
    }
    • 实现过程

    将大神的代码整理搬运后,整合成demo到自己GitHub上,具体项目请移步 
    https://github.com/VDshixiaoming/AnimationTest 
    使用很简单:

    /**
     * 将帧动画资源id以字符串数组形式写到values/arrays.xml中
     * FPS为每秒播放帧数,FPS = 1/T,(T--每帧间隔时间秒)
     */
    
    AnimationsContainer.FramesSequenceAnimation animation 
            = AnimationsContainer.getInstance(R.array.XXX, FPS).createProgressDialogAnim(imageView);
    
    animation.start();//动画开始
    animation.stop();//动画结束

    注意图片资源ID需要以String数组形式放入xml中,然后再利用TypedArray将字符串转为资源ID。如果直接用@drawable/img1这样的形式放入Int数组中,是没法读取到正真的资源ID的。 
    从xml中读取资源ID数组代码:

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

     

    展开全文
  • Android 动画 逐帧动画(Drawable Animation):让图片动起来 一系列静态图片-》控制依次显示及时长,视觉暂留,通常XML: <animation-list xmlns:android=...

    Android 动画

    1. 逐帧动画(Drawable Animation):让图片动起来
      一系列静态图片-》控制依次显示及时长,视觉暂留,通常XML:
      <animation-list xmlns:android="http://schemas.android.com/apk/res/android"
                        android:oneshot="true|false">
            <item android:drawable="" android:duration=""/>
       </animation-list>
      <ImageView
              android:layout_marginTop="50dp"
              android:layout_centerHorizontal="true"
              android:id="@+id/frame_image"
              android:layout_width="200dp"
              android:layout_height="200dp"
              android:background="@drawable/frame_animation"
              />

      Activity 中控制播放与停止:

      // 获取 AnimationDrawable 对象
      animationDrawable = (AnimationDrawable) frame_image.getBackground();
      animationDrawable.start();

      属性:duration

      应用场景:做一个“GIF”

    2. 补间动画(Tween Animiation/ View Animation)
      指定动画的开始、动画的结束的"关键帧",而动画变化的"中间帧"由系统计算,并补齐。
      可以在一个视图容器内执行一系列简单变换。
      alpha,translate,scale,rotate。
      建议使用 XML 文件,更具可读性、可重用性。

      常用属性:

      通用:duration,fillAfter,interpolator,repeatCount,repeatMode

      不通用:from**,to**

      AnimationSet:一个持有其它动画元素 alpha、scale、translate、rotate 或者其它 set 元素的容器。

      对 set 标签使用 Animation 的属性时会对该标签下的所有子控件都产生影响。

      特点:他的动画仅仅是动的 View 的绘制地方,View 真正的位置并没有改变。
      应用场景:进场动画,过场动画、
       
       
       
    3. 属性动画(Property Animation)
      功能最全面,包含补件动画的功能。

      ValueAnimator:时间驱动,管理着动画时间的开始、结束属性值,相应时间属性值计算方法等。

      ObjectAnimator:继承自 ValueAnimator,允许你指定要进行动画的对象以及该对象的一个属性。该类会根据计算得到的新值自动更新属性。 大多数的情况使用 ObjectAnimator 就足够了。
      ofInt、ofFloat、ofObject(ofFloat常用于view的动画,原因是精度足够,形成流畅的动画效果)
      ObjectAnimator.ofFloat(view, "rotationY", 0.0f, 360.0f).setDuration(1000).start();

      AnimatorSet:动画集合,提供把多个动画组合成一个组合的机制,并可设置动画的时序关系,如同时播放、顺序播放或延迟播放。

      ObjectAnimator a1 = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0f);  
      ObjectAnimator a2 = ObjectAnimator.ofFloat(view, "translationY", 0f, viewWidth);  
      ......
      AnimatorSet animSet = new AnimatorSet();  
      animSet.setDuration(5000);  
      animSet.setInterpolator(new LinearInterpolator());   
      //animSet.playTogether(a1, a2, ...); //两个动画同时执行  
      animSet.play(a1).after(a2); //先后执行
      ......//其他组合方式
      animSet.start();  
      特点:修改控件的属性值实现;无法设置结束后view的位置停留在哪里;
      ASet.addListener(new AnimatorListenerAdapter() {
                  float x;
                  float y;
                  float rotate;
                  float alpha;
                  @Override
                  public void onAnimationStart(Animator animation) {
                      super.onAnimationStart(animation);
      //                LogUtil.d("showRabbit, ASet开始");
                      imageView.setVisibility(VISIBLE);
                      textView.setVisibility(VISIBLE);
                      x = imageView.getTranslationX();
                      y = imageView.getTranslationY();
                      rotate = imageView.getRotation();
                      alpha = textView.getAlpha();
                  }
      
                  @Override
                  public void onAnimationEnd(Animator animation) {
                      super.onAnimationEnd(animation);
      //                LogUtil.d("showRabbit, ASet结束");
                      imageView.setVisibility(INVISIBLE);
                      imageView.setLayerType(View.LAYER_TYPE_NONE,null);
                      imageView.setTranslationX(x);
                      imageView.setTranslationY(y);
                      imageView.setRotation(rotate);
                      textView.setAlpha(alpha);
                      ASet.removeAllListeners();
      
                  }
              });

      如果想要target在结束动画之后再回到原来的位置,其中一种解决办法如上:

      注意:1. TranslationX与X的区别。

      所以可以简化上述代码,直接setTranslationX为0:

      ASet.addListener(new AnimatorListenerAdapter() {
                  float rotate;
                  float alpha;
                  @Override
                  public void onAnimationStart(Animator animation) {
                      super.onAnimationStart(animation);
      //                LogUtil.d("showRabbit, ASet开始");
                      imageView.setVisibility(VISIBLE);
                      textView.setVisibility(VISIBLE);
                      rotate = imageView.getRotation();
                      alpha = textView.getAlpha();
                  }
      
                  @Override
                  public void onAnimationEnd(Animator animation) {
                      super.onAnimationEnd(animation);
      //                LogUtil.d("showRabbit, ASet结束");
                      imageView.setVisibility(INVISIBLE);
                      imageView.setLayerType(View.LAYER_TYPE_NONE,null);
                      imageView.setTranslationX(0);
                      imageView.setTranslationY(0);
                      imageView.setRotation(rotate);
                      textView.setAlpha(alpha);
                      ASet.removeAllListeners();
      
                  }
              });

      2. Listener的初始化一定要写在动画开始之前。

      属性动画的优化方法:

      1. 硬件加速

      • 在开始动画时调用View.setLayerType(View.LAYER_TYPE_HARDWARE, null)
      • 运行动画
      • 动画结束时调用View.setLayerType(View.LAYER_TYPE_NONE, null).

      提高动画绘制速度:增加GPU绘制工作,减少CPU绘制工作;但是增加了CPU把图层缓存到GPU的工作开销。

      现在项目的动画问题最主要出在动画部分临时变量多,GC触发频繁,内存泄漏。

      2. 减少临时变量

      ObjectAnimator的源码,我们可以知道它原理是使用一个成员变量,PropertyValuesHolder,来管理单个属性。

      PropertyValuesHolder:可以用在多属性动画同时工作管理。

      一个view同时发生多种属性效果时,建议这种写法。

      效果具体如下:

      不用PropertyValuesHolder,只用ObjectAnimator:

      ObjectAnimator a1 = PropertyValuesHolder.ofFloat(view, "alpha", 0f, 1f);
      ObjectAnimator a2 = PropertyValuesHolder.ofFloat(view, "translationY", 0, viewWidth);
      ......
      AnimatorSet set = new AnimatorSet();
      set.playTogether(a1,a2,.....);
      set.setDuration(1000)
      set.start();

      用PropertyValuesHolder管理属性:

      PropertyValuesHolder a1 = PropertyValuesHolder.ofFloat("alpha", 0f, 1f);
      PropertyValuesHolder a2 = PropertyValuesHolder.ofFloat("translationY", 0, viewWidth);
      ......
      ObjectAnimator.ofPropertyValuesHolder(view, a1, a2, ......).setDuration(1000).start();

      Keyframe

      Keyframe是PropertyValuesHolder的成员,用来管理每一个关键帧的出现时间。

      一个view的单个属性先后发生一系列变化时,建议使用Keyframe达到效果。

      效果如下:

      不用Keyframe,只用ObjectAnimator:

      AnimatorSet animSet = new AnimatorSet(); 
      ObjectAnimator transYFirstAnim = ObjectAnimator.ofFloat(mView, "translationY", 0, 100); 
      ObjectAnimator transYSecondAnim = ObjectAnimator.ofFloat(mView, "translationY", 100, 0); 
      animSet.playSequentially(transYFirstAnim, transYSecondAnim);

      用Keyframe管理关键帧的时序性:

      Keyframe k0 = Keyframe.ofFloat(0f, 0); //第一个参数为“何时”,第二个参数为“何地” 
      Keyframe k1 = Keyframe.ofFloat(0.5f, 100); 
      Keyframe k2 = Keyframe.ofFloat(1f, 0); 
      PropertyValuesHolder p = PropertyValuesHolder.ofKeyframe("translationY", k0, k1, k2); 
      ObjectAnimator objectAnimator =ObjectAnimator.ofPropertyValuesHolder(mView, p);
      objectAnimator.start();

      总的来说就是:ObjectAnimator把属性值的更新委托给PropertyValuesHolder执行,PropertyValuesHolder再把关键帧的时序性计算委托给Keyframe。

      最后,不同的view再用不同的ObjectAnimator管理。

      减少了ObjectAnimator的数量,减少了内存使用,复用可以复用的部分。

       

      3.内存泄漏

      (1)如果用到了无限循环的动画,会这么写:animator.setRepeatCount(ValueAnimator.INFINITE);

      如果没有及时取消,会导致此属性动画持有被动画对象的引用而导致内存泄露。

      取消不能用clearAnimation(); 要用animator.end();或者animator.cancel();

      (两者区别是cancel方法会立即停止动画,并且停留在当前帧。end方法会立即停止动画,并且将动画迅速置到最后一帧的状态。)

      (2)

      ObjectAnimator持有target:

      private ObjectAnimator(Object target, String propertyName) {
          setTarget(target);
          setPropertyName(propertyName);
      }
      @Override
      public void setTarget(@Nullable Object target) {
          final Object oldTarget = getTarget();
          if (oldTarget != target) {
              if (isStarted()) {
                  cancel();
              }
              mTarget = target == null ? null : new WeakReference<Object>(target);
              // New target should cause re-initialization prior to starting
              // 记录尚未初始化,ValueAnimator的初始化标志位
              mInitialized = false; 
          }
      }
      ObjectAnimator持有target是一个弱引用,只要target不被其他不能释放的对象持有,就不会出现target导致内存泄漏的问题;但是反过来target有没有可能持有animator,如果target生命周期持续下去,而导致animator不被释放,越积累越多呢,我的项目中就出现了内存持续上涨这个问题,主动的end和cancel好像也不能释放他们,而且在复用了ObjectAnimator后得到解决,所以怀疑是ObjectAnimator没有释放;还有一个因素值得怀疑:
      public void setValues(PropertyValuesHolder... values) {
              int numValues = values.length;
              mValues = values;
              mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
              for (int i = 0; i < numValues; ++i) {
                  PropertyValuesHolder valuesHolder = values[i];
                  mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
              }
              // New property/values/target should cause re-initialization prior to starting
              mInitialized = false;
          }

      ObjectAnimator源码中有如上这一段,创建了一个hashmap,名字叫mValuesMap,然而这个map没有找到销毁它的地方,所以如果不停创建ObjectAnimator,但是ObjectAnimator不会被回收,也有可能是这个原因。

      (3)

      AnimatorSet可以多次装载Animator,所以AnimatorSet如果写成静态或全局的,想要复用它,那在复用前要清空set,不然会重复执行。

       

       

    4. 还有一种补间动画的特殊用法:Animation的applyTransformation()方法是空实现,具体实现它的是Animation的四个子类,而该方法正是真正的处理动画变化的过程。applyTransformation()方法就是动画具体的实现,系统会以一个比较高的频率来调用这个方法,一般情况下60FPS,是一个非常流畅的画面了,也就是16ms。我们可以覆写这个方法,快速的制作自己的动画。

      飘心在上升过程中的缩放,旋转和透明度变化,是用这种方式做的.

      @Override
              protected void applyTransformation(float factor, Transformation transformation) {
                  Matrix matrix = transformation.getMatrix();
                  mPm.getMatrix(mDistance * factor, matrix, PathMeasure.POSITION_MATRIX_FLAG);
                  mView.setRotation(mRotation * (factor/2f+0.5f));
                  float scale = 1F;
                  if (3000.0F * factor < 200.0F) {
                      scale = scale(factor, 0.0D, 0.06666667014360428D, 0.20000000298023224D, 1.100000023841858D);
                  } else if (3000.0F * factor < 300.0F) {
                      scale = scale(factor, 0.06666667014360428D, 0.10000000149011612D, 1.100000023841858D, 1.0D);
                  }
                  mView.setScaleX(scale);
                  mView.setScaleY(scale);
                  transformation.setAlpha(1.0F - factor);
              }

         

        

     

     

    • 贝塞尔曲线:


    1、moveTo:不会进行绘制,只用于移动移动画笔。

    2、quadTo :用于绘制圆滑曲线,即贝塞尔曲线。

    mPath.quadTo(x1, y1, x2, y2) (x1,y1) 为控制点,(x2,y2)为结束点。同样地,我们还是得需要moveTo来协助控制。

    mPath.moveTo(100, 500);
    mPath.quadTo(300, 100, 600, 500);
    canvas.drawPath(mPath, mPaint);

    效果如图:

    3、cubicTo:同样是用来实现贝塞尔曲线的。

    mPath.cubicTo(x1, y1, x2, y2, x3, y3) (x1,y1) 为控制点,(x2,y2)为控制点,(x3,y3) 为结束点。多了一个控制点而已。

    mPath.moveTo(100, 500);
    mPath.cubicTo(100, 500, 300, 100, 600, 500);

    结果和上图一样。

    如果我们不加 moveTo 呢?则以(0,0)为起点,(100,500)和(300,100)为控制点绘制贝塞尔曲线:

     

     

    • 用作图像处理的Matrix

    Android中的Matrix是一个3 x 3的矩阵,其内容如下:

    Matrix的对图像的处理可分为四类基本变换:

    Translate           平移变换

    Rotate                旋转变换

    Scale                  缩放变换

    Skew                  错切变换

     

     

    几种实际操作例子:

    1. 进度条

    https://www.jianshu.com/p/5bee6048ed22

     

    转载于:https://www.cnblogs.com/sialianzi/p/11309543.html

    展开全文
  • Android 系统提供了两种帧动画实现方式 1.xml 文件定义 animation-list 2.java 文件设置 AnimationDrawable # [缺点] - 系统会把每一帧图片读取到内存中 - 当图片很多且每张都很大的情况下,容易出现卡顿,甚至 ...

    Android 系统提供了两种帧动画实现方式

    1.xml 文件定义 animation-list
    2.java 文件设置 AnimationDrawable
    
    # [缺点] 
    - 系统会把每一帧图片读取到内存中
    - 当图片很多且每张都很大的情况下,容易出现卡顿,甚至 OOM
    复制代码

    解决问题的关键在于避免一次性读取所有图片

    [方案] 在每一帧绘制之前,才加载图片到内存中,并且释放前一帧图片的资源
    复制代码

    优化点

    • 由于图片存储在 res / assets 资源目录或者 sd 卡中,需要通过子线程读取图片避免ANR
    • BitmapFactory 加载图片通过 Options 配置参数优化
      • inPreferredConfig 设置颜色模式,不带透明度的 RGB_565 内存只有默认的 ARGB_8888 的一半
      • inSampleSize 根据显示控件的大小对图像采样,返回较小的图像以节省内存

    通过以上处理,可以实现帧动画的流畅播放

    内存问题解决了?

    通过 Android Profiler,看到频繁的 IO 操作(每读取一张图片的同时释放一张图片)导致内存剧烈抖动。内存频繁的分配和回收容易产生内存碎片,存在 OOM 的风险,频繁的 GC 也容易导致UI卡顿。

    • 通过 Options 参数继续优化
      • inMutable 设置解码得到的 bitmap 可变
      • inBitmap 复用前一帧图片,避免内存抖动(效果如下图)

    暂时处理了内存问题后继续思考,频繁的 IO 也会导致 CPU 的使用率高

    1. 当前 App 对帧动画的使用场景多,使用频率高
    2. 存储在 sd 卡的图片做了加密,解码每一帧图片前需要解密,对 CPU 的考验又加剧了
    • 对于单次播放的帧动画,每一帧图片使用之后及时复用或者回收是合理的
    • 对于不限次数循环播放的帧动画,假如 1 秒播放 25 帧,那么每 40 毫秒需要解码 1 帧,触发 1 次 IO,如果同一页面有多个帧动画同时播放,那么情况更加糟糕

    在某华为荣耀9手机上,测试简单页面播放 sd 卡里某一帧动画循环播放的 CPU 情况

    很容易想到缓存 —— 这时候又回到了「以内存空间换取cpu时间」的思路

    App 中循环或多次播放的帧动画大部分情况是局部的小图(什么地方需要无限播放全屏的帧动画呢?),对这类小图添加缓存就挺合适的。优化效果如下图:

    另外: 什么业务场景需要帧动画无限循环播放呢?用户会盯着手机上的某个动画多长时间?是否可以针对大部分情况,设置一个上限,播放 n 次之后就停止动画,只保留最后一帧的画面

    缓存的实现

    Android 提供了 LruCache,根据最近最少使用优先清理的原则缓存数据。

    public class FrameAnimationCache extends LruCache<String, Bitmap> {
        private static int mCacheSize = (int) (Runtime.getRuntime().maxMemory() / 8);
        
        public FrameAnimationCache() {
    	    super(mCacheSize);
        }
    
        @Override
        protected int sizeOf(@NonNull String key, @NonNull Bitmap value) {
            return value.getByteCount();
        }
    }
    复制代码

    对于内存使用不太紧张的 App, 这样一个缓存就够用了,图片缓存最多只会占用 mCacheSize 大小的内存。

    进一步优化

    当缓存里的帧动画图片长时间没有使用,如何释放?

    SoftReference(软引用)如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足,就会回收这些对象的内存(系统自动帮你回收,不用操心多好)

    public class FrameAnimationCache extends LruCache<String, SoftReference<Bitmap>> {
        private static int mCacheSize = (int) (Runtime.getRuntime().maxMemory() / 8);
        
        public FrameAnimationCache() {
    	    super(mCacheSize);
        }
    
        @Override
        protected int sizeOf(@NonNull String key, @NonNull SoftReference<Bitmap> value) {
            if (value.get() != null) {
                return value.get().getByteCount();
            } else {
                return 0;
            }
        }
    }
    复制代码

    大功告成??

    当 GC 自动回收 SoftReference,会导致缓存的 sizeOf 计算出错,日志里可能看到这样的警告

    W/System.err: java.lang.IllegalStateException: xxx.xxxAnimationCache.sizeOf() is reporting inconsistent results!
    W/System.err: at android.support.v4.util.LruCache.trimToSize(LruCache.java:167)
    W/System.err: at android.support.v4.util.LruCache.put(LruCache.java:150)
    复制代码

    假如我们通过 get(K key) 获取的之前已缓存过的 Bitmap 软引用,而恰好它已被 GC 回收,那么返回 null,需要重新解码图片,调用 put(K key, V value) 缓存起来。

    public final V put(@NonNull K key, @NonNull V value) {
        if (key != null && value != null) {
            Object previous;
            synchronized(this) {
                ++this.putCount;
                this.size += this.safeSizeOf(key, value);
                previous = this.map.put(key, value);
                if (previous != null) {
                    this.size -= this.safeSizeOf(key, previous);
                }
            }
    
            if (previous != null) {
                this.entryRemoved(false, key, previous, value);
            }
    
            this.trimToSize(this.maxSize);
            return previous;
        } else {
            throw new NullPointerException("key == null || value == null");
        }
    }
    复制代码
    • 查看 LruCache 的源码可知,每个 put 操作,对于 key 相同的情况(我们刚好如此),会对 size 减去 previous(前一个缓存数据)的大小, 而因为数据被回收,导致 previous 为 null,此时 size 大于实际缓存数据的大小
    • 若类似情况长期发生下去,最终可能出现 size 达到 maxSize,而实际上所有缓存数据都被回收
    public void trimToSize(int maxSize) {
        while(true) {
            Object key;
            Object value;
            synchronized(this) {
                if (this.size < 0 || this.map.isEmpty() && this.size != 0) {
                    throw new IllegalStateException(this.getClass().getName() + ".sizeOf() is reporting inconsistent results!");
                }
    
                if (this.size <= maxSize || this.map.isEmpty()) {
                    return;
                }
    
                Entry<K, V> toEvict = (Entry)this.map.entrySet().iterator().next();
                key = toEvict.getKey();
                value = toEvict.getValue();
                this.map.remove(key);
                this.size -= this.safeSizeOf(key, value);
                ++this.evictionCount;
            }
    
            this.entryRemoved(true, key, value, (Object)null);
        }
    }
    复制代码
    • 而且每个 put 会执行 trimToSize
    • 当 size > maxSize 的情况, 会将缓存队列的数据逐个remove,然后修改 size
    • 可惜数据被回收 safeSizeOf 得到的大小为 0,相当于没有修改 size
    • 一直 remove,直到队列为空
    • 符合 map.isEmpty() && size != 0,抛出日志打印的警告

    如何处理

    问题已知 —— 数据回收导致大小计算出错,那么解决这个问题就可以了。

    ReferenceQueue

    • 当 GC 回收了 SoftReference,会通知与其绑定的 ReferenceQueue 队列,可通过这个方式检测到内存回收,主动正确修改缓存数据 size

    而我用了如下方式

    public class FrameAnimationCache extends LruCache<String, SizeSoftReferenceBitmap> {
        private static int mCacheSize = (int) (Runtime.getRuntime().maxMemory() / 8);
    
        public FrameAnimationCache() {
        	super(mCacheSize);
        }
    
        @Override
        protected int sizeOf(@NonNull String key, @NonNull SizeSoftReferenceBitmap value) {
        	return value.getSize();
        }
    }
    
    private class SizeSoftReferenceBitmap {
        private SoftReference<Bitmap> mBitmap;
        private int mSize;
        
        private SizeSoftReferenceBitmap(SoftReference<Bitmap> bitmap, int size) {
        	mBitmap = bitmap;
        	mSize = size;
        }
        
        private int getSize() {
        	return mSize;
        }
        
        private SoftReference<Bitmap> getBitmap() {
        	return mBitmap;
        }
    }
    
    public Bitmap getBitmapFromCache(String key) {
        SizeSoftReferenceBitmap value = mFrameAnimationCache.get(key);
        return value != null && value.getBitmap() != null ? value.getBitmap().get() : null;
    }
    
    public void addBitmapToCache(String key, Bitmap value) {
        mFrameAnimationCache.put(key, new SizeSoftReferenceBitmap(new SoftReference<>(value), value.getByteCount()));
    }
    复制代码

    用一个 SizeSoftReferenceBitmap 类,做了简单的对象组合,在创建缓存的时候提前存下 size。

    更多

    • 目前只对循环播放的帧动画小图做自动缓存,小图的判断依据?
    • 对多次播放的帧动画缓存依赖于业务判断,自行调用缓存,是否可添加计数器,自动判断播放一定次数后添加缓存,计数器多占了内存?

    转载于:https://juejin.im/post/5d31a18fe51d454d544ac028

    展开全文
  • 最近公司有一个项目需求,需要在一个特定的android设备上做一组帧动画的效果,当时感觉这个功能很容易,相信大家都知道,android的原生帧动画实现方法,但是问题就出现在这,如果使用原生的帧动画,在手机上是没有...

          最近公司有一个项目需求,需要在一个特定的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;
        }
    
    }
    
    以上为解决帧动画卡顿的总结和解决方案,如有问题请留言,谢谢各位。


    展开全文
  • Android属性动画优化
  • android 动画流畅优化

    2016-08-12 16:47:42
    动画流畅优化 ,提高android动画帧频,然后用FPS Meter(需要rote)测试了一下帧频,结果动化只有15左右(android最高帧频现在为60)。 view.setLayerType 硬件加速
  • Android上如果使用逐帧动画的话,可以很方便地使用AnimationDrawable,无论是先声明xml还是直接代码里设置,都是几分钟的事,但使用AnimationDrawable有一个致命的弱点,那就是需要一次性加载所有图片到内存,万一帧...
  • Android 手机的分辨率越来越大,从矮穷锉到百富美都是如此.现在矮穷锉也是HD的屏, 而百富美一般是2K屏, 8核CPU, 3GRAM.然并卵,我们依然会发现大多数的开机动画并不是很流畅,而比较流畅的,大都要么画面简单有的...
  • 在开发时,在聊天室内用到属性动画,时间久了卡顿,先看一下效果图: 礼物动画是 SVGA 动画和属性动画配合使用,才达到这样的效果;这是在聊天室内,会不停的刷这种礼物,在测试时,刷到70个左右,出现明显的卡顿...
  • 在项目上碰到应用多个场景电流超标,经过分析是动画导致的,主界面动画是Opengl实现的,而opengl有两种渲染模式:连续不断的渲染和被动渲染,应用的动画直接是默认的连续不断的渲染,这样一来只要打开了应用GPU就会...
  • --矩阵(Matrix),Matrix动画 矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合,最早来自于方程组的系数及常数所构成的方阵。在物理学中,矩阵于电路学、力学、光学和量子物理中都有应用;计算机科学中,三...
  • Android自定义加载动画

    2018-03-20 17:18:58
    准备做一个Android自定义加载动画的合集,主要通过自定义View实现一些常见的加载动画,也会模仿一些主流APP好看的加载动画。项目源码同步上传到了个人github上,欢迎大家star,fork,提issues,一起交流进步。 目前...
  • Android动画优化

    2015-11-04 14:56:54
    优化重于泰山对于同样机器环境上的应用来说,抛去受CPU、屏幕和系统GUI系统的固有时间消耗外,要实现流畅的动画的核心也就是减少视图Draw的时间。这里有几点经验可以跟大家分享一下: 尽量不要在刷新时做耗时操作,...
  • android 动画卡顿分析工具    Android应用性能优化之分析工具  上一次记录了解决过度绘制的过程,这一次,想先弄清个概念性的东西,就是如何判断顺不顺畅?  这东西其实最初我自己也觉得有点废话,用起来...
  • 一、APP的启动方式: 一般来说,APP的启动方式主要分为两种:冷启动和热启动。 冷启动:启动应用时,后台没有该应用的进程,系统会重新创建一个新的进程分配给该应用,即冷启动; 热启动:启动应用时,后台已有...
  • Android性能优化总结

    2019-06-18 21:45:50
    安卓开发大军浩浩荡荡,经过近十年的发展,Android技术优化日异月新,如今Android 9.0 已经发布,Android系统性能也已经非常流畅,可以在体验上完全媲美iOS。 但是,到了各大厂商手里,改源码、自定义系统,使得...
  • Android开发之Frame动画(帧动画) Android开发之Tween(补间动画)完全解析(上)——xml文件配置的实现 Android开发之Tween(补间动画)完全解析(下)——代码实现Hello,大家好,今天又来装逼了,装逼也上瘾啊,最近公司...
  • 前言本文实战性较强,主要目的是通过一个自定义控件的开发,引出我对自定义控件性能优化的一些思考和实践,欢迎各位喜欢移动开发的小伙伴来拍砖~本文由于篇幅有限,只讲解思路,并没有放出大量源代码,如果对本项目...
  • 博主声明: 转载请在开头附加本文链接及作者信息,并... Android 提供了一种简单而好用的帧动画,所谓帧动画,就是一帧一帧的播放,你可以想象一下民国时期那种电影片的效果,它用的就是一帧一帧的播放,说白了就是...
1 2 3 4 5 ... 20
收藏数 36,499
精华内容 14,599