Android 自定义view优化方案

最近公司来了一个新的需求:将一些机车参数在app端用进度、动画的效果显示出来,于是在网上找了一大堆自定义View实现的进度条代码,最终搞出来了。界面中有四个控件是通过自定义view实现的,数据接收的频率是500ms一条数据,收到数据之后使用handler更新到UI线程直接显示数据。但是遇到一个问题,当绘制的时间比较长时,大约超过半个小时左右界面上的数值变化就会明显变慢,出现界面延迟伴有卡顿的情况。于是最近这几天一点点慢慢的优化,效果才有明显好转。接下来我来讲讲我具体优化了哪些方面的东西。

一、绘制onDraw()的优化

在每次数据更新之后,都会调用一次invalidate()或者postInvalidate()来更新UI,让onDraw()进行重新绘制。所以在onDraw()中不要创建新的局部对象。onDraw()方法执行的频率比较高,这样就会在一瞬间产生大量的临时对象,这不仅占用了过多的内存,而且还会导致系统更加频繁gc,降低了程序的执行效率。

原先的代码:

每次执行onDraw都会创建一个Matrix对象。

    @Override
    protected void onDraw(Canvas canvas) {
       Matrix mDynamicMatrix = new Matrix();
       mDynamicMatrix.setRotate(startAngele, canvas.getWidth() / 2, canvas.getHeight() / 2);
       mDynamicShader.setLocalMatrix(mDynamicMatrix);
       mPaint.setShader(mDynamicShader);
    }

优化后的代码:

进行非空判断,Matrix只创建一次。

    private Matrix mDynamicMatrix = null;
    @Override
    protected void onDraw(Canvas canvas) {
       if (mDynamicMatrix == null) {
           mDynamicMatrix = new Matrix();
       }
       mDynamicMatrix.setRotate(startAngele, canvas.getWidth() / 2, canvas.getHeight() / 2);
       mDynamicShader.setLocalMatrix(mDynamicMatrix);
       mPaint.setShader(mDynamicShader);
    }

总言之,在onDraw()中尽量避免对象的重复创建

二、动画的优化

原先的代码:

ValueAnimator.ofFloat()会多次创建ValueAnimator对象。

    private void startAnimator(float start, float end, long animTime) {
        mAnimator = ValueAnimator.ofFloat(start, end);
        mAnimator.setDuration(animTime);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mPercent = (float) animation.getAnimatedValue();
                mValue = mPercent * mMaxValue;
              
                invalidate();
            }
        });
        mAnimator.start();
    }

优化后的代码:

初始化的时候创建ValueAnimator,然后在使用的时候通过setFloatValues()赋值。

    //初始化的时候创建ValueAnimator,注册监听
    private void initAnim() {
        mAnimator = new ValueAnimator();
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mPercent = (float) animation.getAnimatedValue();
                mValue = mPercent * mMaxValue;
                invalidate();
            }
        });
    }

    private void startAnimator(float start, float end, long animTime) {
        mAnimator.setFloatValues(start, end);
        mAnimator.setDuration(animTime);
        mAnimator.start();
    }

原理是避免对象的重复创建

查看ValueAnimator.ofFloat(start, end)的源码:

    /**
     * Constructs and returns a ValueAnimator that animates between float values. A single
     * value implies that that value is the one being animated to. However, this is not typically
     * useful in a ValueAnimator object because there is no way for the object to determine the
     * starting value for the animation (unlike ObjectAnimator, which can derive that value
     * from the target object and property being animated). Therefore, there should typically
     * be two or more values.
     *
     * @param values A set of values that the animation will animate between over time.
     * @return A ValueAnimator object that is set up to animate between the given values.
     */
    public static ValueAnimator ofFloat(float... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setFloatValues(values);
        return anim;
    }

可以看出ofFloat()每执行一次,会创建一个ValueAnimator()对象。在初始化的时候创建一次ValueAnimator对象,更新数据直接使用ValueAnimator的setFloatValues()方法进行数据更新,可以避免ValueAnimator对象的重复创建。

三、invalidate()方法的执行时机

原先的代码:

onAnimationUpdate()监听1秒执行6次、4次、3次,次数不等,invalidate()方法也执行了多次,多次无必要的绘制造成大量资源的浪费。

    //初始化的时候创建ValueAnimator,注册监听
    private void initAnim() {
        mAnimator = new ValueAnimator();
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mPercent = (float) animation.getAnimatedValue();
                mValue = mPercent * mMaxValue;
                invalidate();
            }
        });
    }
    //启动动画
    private void startAnimator(float start, float end, long animTime) {
        mAnimator.setFloatValues(start, end);
        mAnimator.setDuration(animTime);
        mAnimator.start();
    }

优化之后:

增加一个动画开始、结束的监听,把invalidate()放到动画结束之后onAnimationEnd()方法中执行。前面提到数据接收的频率是500ms一条数据,动画的时间我这里设置的1,也就是1ms,可以忽略不计。1秒内动画执行两次,打印出来正好是执行了两次invalidate()。

    //初始化的时候创建ValueAnimator,注册监听
    private void initAnim() {
        mAnimator = new ValueAnimator();
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mPercent = (float) animation.getAnimatedValue();
                mValue = mPercent * mMaxValue;
               
            }
        });
        mAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                invalidate();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
    }

invalidate()每执行一次,onDraw()方法就会跟着执行一次,重绘ui会占用较多的内存。为了避免资源的浪费,我们应该尽量减少invalidate()方法的调用频率

四、invalidate和postInvalidate的区别

自定义view中实现view的更新有两种方式,一种是invalidate(),另一种是postInvalidate()。

invalidate()是在UI线程中使用,而postInvalidate()是在非UI线程中使用。

1、invalidate()

看一下invalidate()的源码:

    /**
     * Invalidate the whole view. If the view is visible,
     * {@link #onDraw(android.graphics.Canvas)} will be called at some point in
     * the future.
     * 

* This must be called from a UI thread. To call from a non-UI thread, call * {@link #postInvalidate()}. */ public void invalidate() { invalidate(true); }

  从方法中的注释中看,我们知道invalidate方法会刷新整个View,并且当这个View的可见性为VISIBLE的时候,View的onDraw()方法将会被调用。另外注意的是这个方法只能在UI线程中去调用。

上面就能够基本知道invalidate方法是干什么的了。我们往下接着看源码:

    /**
     * This is where the invalidate() work actually happens. A full invalidate()
     * causes the drawing cache to be invalidated, but this function can be
     * called with invalidateCache set to false to skip that invalidation step
     * for cases that do not need it (for example, a component that remains at
     * the same dimensions with the same content).
     *
     * @param invalidateCache Whether the drawing cache for this view should be
     *            invalidated as well. This is usually true for a full
     *            invalidate, but may be set to false if the View's contents or
     *            dimensions have not changed.
     * @hide
     */
    @UnsupportedAppUsage
    public void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }

我们看到,invalidate()方法中是调用invalidate(true),参数true的意思是需要整体刷新,当View的内容和大小没有任何变化时我们可以传入false。

2、postInvalidate()

接下来看下postInvalidate()的实现:

    /**
     * 

Cause an invalidate to happen on a subsequent cycle through the event loop. * Use this to invalidate the View from a non-UI thread.

* *

This method can be invoked from outside of the UI thread * only when this View is attached to a window.

* * @see #invalidate() * @see #postInvalidateDelayed(long) */ public void postInvalidate() { postInvalidateDelayed(0); } /** *

Cause an invalidate to happen on a subsequent cycle through the event * loop. Waits for the specified amount of time.

* *

This method can be invoked from outside of the UI thread * only when this View is attached to a window.

* * @param delayMilliseconds the duration in milliseconds to delay the * invalidation by * * @see #invalidate() * @see #postInvalidate() */ public void postInvalidateDelayed(long delayMilliseconds) { // We try only with the AttachInfo because there's no point in invalidating // if we are not attached to our window final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); } } /** *

Cause an invalidate of the specified area to happen on a subsequent cycle * through the event loop. Waits for the specified amount of time.

* *

This method can be invoked from outside of the UI thread * only when this View is attached to a window.

* * @param delayMilliseconds the duration in milliseconds to delay the * invalidation by * @param left The left coordinate of the rectangle to invalidate. * @param top The top coordinate of the rectangle to invalidate. * @param right The right coordinate of the rectangle to invalidate. * @param bottom The bottom coordinate of the rectangle to invalidate. * * @see #invalidate(int, int, int, int) * @see #invalidate(Rect) * @see #postInvalidate(int, int, int, int) */ public void postInvalidateDelayed(long delayMilliseconds, int left, int top, int right, int bottom) { // We try only with the AttachInfo because there's no point in invalidating // if we are not attached to our window final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { final AttachInfo.InvalidateInfo info = AttachInfo.InvalidateInfo.obtain(); info.target = this; info.left = left; info.top = top; info.right = right; info.bottom = bottom; attachInfo.mViewRootImpl.dispatchInvalidateRectDelayed(info, delayMilliseconds); } }

从上面的方法注释中可以知道,postInvalidate是可以在非UI线程中去调用刷新UI的,那是如何做到的呢?从上面的方法调用栈中可以看出来,调用postInvalidate方法最后会调用View中的mHander发送一个MSG_INVALIDATE的消息。mHandler是ViewRootHandler的一个实例,从ViewRootHandler的handleMessage()方法中一探究竟(方法较长,只截取部分):

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_INVALIDATE:
                    ((View) msg.obj).invalidate();
                    break;
                case MSG_INVALIDATE_RECT:
                    final View.AttachInfo.InvalidateInfo info =
                            (View.AttachInfo.InvalidateInfo) msg.obj;
                    info.target.invalidate(info.left, info.top, info.right, info.bottom);
                    info.recycle();
                    break;
                case MSG_PROCESS_INPUT_EVENTS:
                    mProcessInputEventsScheduled = false;
                    doProcessInputEvents();
                    break;

在Handler中最后还是会调用View的invalidate()方法去刷新,只不过postInvalidate()方法是通过Handler将刷新事件通知发到Handler的handlerMessage中去执行invalidate的。

"invalidate和postInvalidate的区别" 出处:https://cloud.tencent.com/developer/article/1355393

你可能感兴趣的:(Android,android)