前言

Android 开发辅导中有这样一条主张:

谨慎运用 Alpha
当您运用setAlpha()AlphaAnimationObjectAnimator将视图设置为半通明时,该视图会在屏幕外缓冲区烘托,导致所需的填充率翻倍。在超大视图上应用 Alpha 时,请考虑将视图的层类型设置为LAYER_TYPE_HARDWARE

Android alpha动画隐形本钱优化

在Android中,关于Alpha通明度的制作是比较耗时的,一个首要的原因是Alpha图层制作需求进行两次制作:

  • 第一次是制作不通明的画面
  • 第二次是对alpha通道的颜色进行混合

为什么要这么做呢?首要原因是制作时,多层烘托逻辑会非通明度会叠加,导致一些重要的元素看不清。

如下图所示,左侧是Android混合优化的作用,右侧是叠加的作用:

Android alpha动画隐形本钱优化

处理办法

其实在Android 官方的辅导中,供给了一些计划,具体见《削减过度制作》,同时对alpha制作提出了优化主张,便是运用缓冲区,参考视频:《通明度制作的隐形本钱

削减alpha制作

在屏幕上烘托通明像素,即所谓的通明度烘托,是导致过度制作的重要因素。

在普通的过度制作中,体系会在已制作的现有像素上制作不通明的像素,从而将其彻底遮盖,与此不同的是,通明方针需求先制作现有的像素,以便到达正确的混合作用。

比如通明动画、淡出和暗影之类的视觉作用都会涉及某种通明度,因此有或许导致严重的过度制作。您能够削减要烘托的通明方针的数量,以此来改善这些情况下的过度制作。例如,如需取得灰色文本,您能够在TextView中制作黑色文本,再为其设置一个半通明的通明度值。可是,您能够经过用灰色制作文本来取得相同的作用,而且能够提高功能。

总结一下: 你能够运用color#argb设置通明色值去制作,而不是运用View#setAlpha,以此削减alpha制作范围。

复写hasOverlappingRendering

关于明确不需求剔除叠加作用的View,直接让其回来不支持叠加烘托,当然,一般的非ViewGroup子类是优化方针(由于ViewGroup默认不制作),在确认不需求叠加剔除时,直接告诉烘托器不进行混合。

class MyView extends View {
    @Override
    public boolean hasOverlappingRendering() {
        return false;
    }
}

下面是制作功能比照,当然你得取舍,如果没有View层级堆叠问题,这个仍是能够考虑的。

Android alpha动画隐形本钱优化

运用缓冲

前面提到过,为了提出非通明度叠加作用,Android团队做了优化,但这个过程中,非通明的像素缓冲制作完直接丢掉了,原因是由于根据有限的条件,无法知晓后续是不是需求此内存的数据,因此直接释放了内存,显然是明智的做法。可是,这种丢掉显然会引发内存颤动问题,由于一旦触发制作,就需求从头申请内存。

官方供给的主张是运用下面办法优化

View#setLayerType(View.LAYER_TYPE_HARDWARE, null)

留意事项

这儿咱们弥补一下关于View#setLayerType,此办法能够接受三种类型LAYER_TYPE_SOFTWARE、LAYER_TYPE_HARDWARE、LAYER_TYPE_NONE,关于前两者,都是创立相应的缓冲区,而LAYER_TYPE_NONE仅仅是销毁缓冲区。

  • 留意1: setLayerType能够开启硬件加速的是一个过错的了解,由于硬件加速在View制作前的Activity中就已经标记好了。setLayerType(LAYER_TYPE_HARDWARE,null) 相当于在此基础上创立了个缓冲区 (FBO)。
  • 留意2:setLayerType(LAYER_TYPE_SOFTWARE,null) 封闭硬件缓冲区的了解是副作用引发的,其创立软件缓冲区(Bitmap)的时分,刚好规避了硬件制作,由于LAYER_TYPE_SOFTWARE会让子View节点共享Canvas,从而到达图画组成。

官方参考代码

其实任何动画都能够优化,特别是关于alpha、rotation动画

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
  @Override
  public void onAnimationEnd(Animator animation) {
    view.setLayerType(View.LAYER_TYPE_NONE, null);
  }
});
animator.start();

相同,Android中也供给了制作办法

ViewPropertyAnimator.alpha(0.0f).withLayer();

可是通用性上来说还有些差,此外关于AnimatorSet或许产生大量重复办法,有没有愈加方便的办法呢,咱们的打破点在AnimatorListener这儿,咱们运用动画的生命周期办法进行设置会愈加方便。

public void addListener(AnimatorListener listener) {
    if (mListeners == null) {
        mListeners = new ArrayList<AnimatorListener>();
    }
    mListeners.add(listener);
}

接下来咱们对alpha动画进行优化

当然,这儿有个重要的办法,便是查找是否包含alpha动画

    static boolean shouldRunOnHWLayer(View v, Animator anim) {
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT || v == null || anim == null) {
            return false;
        }
        boolean shouldRunOnHWLayer =  v.getLayerType() == View.LAYER_TYPE_NONE
                && v.hasOverlappingRendering()
                && modifiesAlpha(anim);
        return shouldRunOnHWLayer;
    }

接着,咱们在onAnimationStart和onAnimationEnd中调用此办法

       @Override
        public void onAnimationStart(Animator animation) {
            mShouldRunOnHWLayer = shouldRunOnHWLayer(mView, animation);
            if (mShouldRunOnHWLayer) {
                oldLayerType = mView.getLayerType();
                MLog.d(TAG,"onAnimationStart post");
                View targetView = mView;
                targetView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
            }
        }
        @Override
        public void onAnimationEnd(Animator animation) {
            if (mShouldRunOnHWLayer) {
                MLog.d(TAG,"onAnimationEnd post");
                View targetView = mView;
                targetView.setLayerType(oldLayerType, null);
            }
            mView = null;
            animation.removeListener(this);
        }

运用办法办法也很简单

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(rotatioOpen,alphaHide,rotatioClose,alphaShow);
return AnimatorOptimizer.optimize(animatorSet,v);

libhwui Crash

很显然,这个优化是有副作用的,在上线之后陆陆续续收到一些libhwui中的crash问题的,经过一些测验发现是setLayerType引发的。

#02 pc 00015d7d /system/lib/libhwui.so [armeabi-v7a]
#03 pc 00014517 /system/lib/libhwui.so [armeabi-v7a]
#04 pc 0001440b /system/lib/libhwui.so [armeabi-v7a]
#05 pc 0001d133 /system/lib/libhwui.so [armeabi-v7a]
#06 pc 000679c5 /system/lib/libandroid_runtime.so [armeabi-v7a]
#07 pc 0002054c /system/lib/libdvm.so (dvmPlatformInvoke +112) [armeabi-v7a]
#08 pc 0005132f /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*) +398) [armeabi-v7a]
#09 pc 000299e0 /system/lib/libdvm.so [armeabi-v7a]
#10 pc 00030f48 /system/lib/libdvm.so (dvmMterpStd(Thread*) +76) [armeabi-v7a]
#11 pc 0002e5e0 /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*) +184) [armeabi-v7a]
#12 pc 00063af9 /system/lib/libdvm.so (dvmInvokeMethod(Object*, Method const*, ArrayObject*, ArrayObject*, ClassObject*, bool) +392) [armeabi-v7a]
#13 pc 0006ba1f /system/lib/libdvm.so [armeabi-v7a]
#14 pc 000299e0 /system/lib/libdvm.so [armeabi-v7a]
#15 pc 00030f48 /system/lib/libdvm.so (dvmMterpStd(Thread*) +76) [armeabi-v7a]
#16 pc 0002e5e0 /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*) +184) [armeabi-v7a]
#17 pc 00063815 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list) +336) [armeabi-v7a]
#18 pc 0004cf17 /system/lib/libdvm.so [armeabi-v7a]
#19 pc 0004dfcf /system/lib/libandroid_runtime.so [armeabi-v7a]
#20 pc 0004ed27 /system/lib/libandroid_runtime.so (android::AndroidRuntime::start(char const*, char const*) +354) [armeabi-v7a]
#21 pc 0000109b /system/bin/app_process
#22 pc 0000e563 /system/lib/libc.so (__libc_init +50) [armeabi-v7a]
#23 pc 00000db0 /system/bin/app_process
java:
android.view.GLES20Canvas.nDrawDisplayList(Native Method)
android.view.GLES20Canvas.drawDisplayList(GLES20Canvas.java:420)
android.view.HardwareRenderer$GlRenderer.drawDisplayList(HardwareRenderer.java:1646)

具体原因很难分析出来,可是,在stackoverflow中有个处理的办法,便是确保在View attachedToWindow之后进行setLayerType操作,他是怎样做的呢?

相同仍是在 onAnimationStart和onAnimatonEnd中进行优化的,便是在View#post办法中履行setLayerType,上线后这种问题下降到正常水平。

实际上ViewPropertyAnimator.alpha(0.0f).withLayer();中也做了相似的优化,可是,在onAnimationEnd中却没有这样做,为了稳妥起见,主张onAnimationStart和onAnimationEnd两者都做post逻辑。

至于为什么crash,目前没有定位到,首要是发生在线上版别,可是如果咱们从Choreographer和View#post的角度调查,猜测和View树是不是AttachedToWindow有关。

这儿咱们比照下两种调用的差异。

关于choreographer和view#post

咱们知道,动画的履行都是经过choreographer完成的,相同包括补间动画,可是这也使得choreographer能够在脱离View生命周期的情况下正常履行,如ValueAnimator的运用。为了简单起见,咱们换个比照办法,View#postAnimation和View#post

  • 前者和动画的更新原理相同,都是经过choreographer驱动,后者是经过Handler驱动
  • 前者脱离View生命周期依然能够履行,后者会中止履行,并缓冲仍未到TaskQueue中

很显然,View#postAnimation的履行或许在View被添加前和移除后都能履行,显然由此引发了不安全问题。而动画也是相似的情况。

完整代码

下面是本篇封装的完整代码,咱们需求首先判断是否存在alpha动画,有的话加一个监听器,然后在动画开始时设置硬件缓冲,结束时封闭缓冲。

public class AnimatorOptimizer {
    private static final String TAG = "AnimatorOptimizer";
    public  static <T extends Animator> T optimize(T animator, View view) {
        if(shouldRunOnHWLayer(view,animator)) {
            animator.addListener(new AnimatorOnHWLayerIfNeededListener(view));
        }
        return animator;
    }
    static class AnimatorOnHWLayerIfNeededListener extends AnimatorListenerAdapter {
        private boolean mShouldRunOnHWLayer = false;
        private View mView;
        private int oldLayerType;
        public AnimatorOnHWLayerIfNeededListener(final View v) {
            if (v == null) {
                return;
            }
            mView = v;
        }
        @Override
        public void onAnimationStart(Animator animation) {
            mShouldRunOnHWLayer = shouldRunOnHWLayer(mView, animation);
            if (mShouldRunOnHWLayer) {
                oldLayerType = mView.getLayerType();
                MLog.d(TAG,"onAnimationStart post");
                View targetView = mView;
                mView.post(new Runnable() {
                    @Override
                    public void run() {
                        MLog.d(TAG,"onAnimationStart");
                        targetView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
                    }
                });
            }
        }
        @Override
        public void onAnimationEnd(Animator animation) {
            if (mShouldRunOnHWLayer) {
                MLog.d(TAG,"onAnimationEnd post");
                View targetView = mView;
                mView.post(new Runnable() {
                    @Override
                    public void run() {
                        MLog.d(TAG,"onAnimationEnd");
                        targetView.setLayerType(oldLayerType, null);
                    }
                });
            }
            mView = null;
            animation.removeListener(this);
        }
    }
    static boolean shouldRunOnHWLayer(View v, Animator anim) {
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT || v == null || anim == null) {
            return false;
        }
        boolean shouldRunOnHWLayer =  v.getLayerType() == View.LAYER_TYPE_NONE
                && v.hasOverlappingRendering()
                && modifiesAlpha(anim);
        return shouldRunOnHWLayer;
    }
    static boolean modifiesAlpha(Animator anim) {
        if (anim == null) {
            return false;
        }
        if (anim instanceof ValueAnimator) {
            ValueAnimator valueAnim = (ValueAnimator) anim;
            PropertyValuesHolder[] values = valueAnim.getValues();
            for (int i = 0; i < values.length; i++) {
                if (("alpha").equals(values[i].getPropertyName())) {
                    return true;
                }
            }
        } else if (anim instanceof AnimatorSet) {
            List<Animator> animList = ((AnimatorSet) anim).getChildAnimations();
            for (int i = 0; i < animList.size(); i++) {
                if (modifiesAlpha(animList.get(i))) {
                    return true;
                }
            }
        }
        return false;
    }
}

总结

本篇就到这儿了,动画问题一直是个大问题,在低端设备上会放大的很明显,咱们常用的手法首要如下:

  • 动画合并计算
  • 削减制作区域
  • 削减过度制作
  • 运用SurfaceView或者GLSurfaceView烘托
  • 异步解码
  • Bitmap 复用

本篇是对特定动画的优化,上一篇的《同频共帧》 是削减对称动画制作区域最有效的计划,希望本篇对大家有所帮助。