Android中View的相关知识(8)

来源:互联网 发布:vscode c 开发环境 编辑:程序博客网 时间:2024/04/30 05:38

Android中View的相关知识(8)

上一章节,我们分析了View的绘制过程,在本章节,我们根据View的绘制过程,分析其中的一个小小细节

View类中的invalidate()和postInvalidate()方法的分析:

对于这两个方法,我们大概知道的情况是它两的作用是用于重绘,并且一个只能在UI线程中使用,一个用于其他线程,由于View的三步绘制流程中最后都调用了invalidate()方法,可见此方法的重要性,我们首先来看下invalidate()方法的源码:

//Invalidate the whole view. If the view is visible, 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 postInvalidate().public void invalidate() {        invalidate(true);    }/*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). *Parameters: *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. */void invalidate(boolean invalidateCache) {        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);    }/*Mark the area defined by dirty as needing to be drawn. If the view is visible, 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 postInvalidate(). */public void invalidate(Rect dirty) {        final int scrollX = mScrollX;        final int scrollY = mScrollY;        invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,                dirty.right - scrollX, dirty.bottom - scrollY, true, false);    }/*Mark the area defined by the rect (l,t,r,b) as needing to be drawn. The coordinates of the dirty rect are relative to the view. If the view is visible, 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 postInvalidate(). */public void invalidate(int l, int t, int r, int b) {        final int scrollX = mScrollX;        final int scrollY = mScrollY;        invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);    }/*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). *Parameters: *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. */ void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,            boolean fullInvalidate) {        ......            // Propagate the damage rectangle to the parent view.            final AttachInfo ai = mAttachInfo;            final ViewParent p = mParent;            if (p != null && ai != null && l < r && t < b) {                final Rect damage = ai.mTmpInvalRect;                //设置刷新区域                damage.set(l, t, r, b);                //传递调运Parent ViewGroup的invalidateChild方法                p.invalidateChild(this, damage);            }            ......    }

可以看到,invalidate();方法有多个重载方法,但是,它最终会跑到invalidateInternal();方法中,方法内部,又调用了invalidateChild();方法,这是一个从当前向上级父View回溯的过程:

public final void invalidateChild(View child, final Rect dirty) {        ViewParent parent = this;        final AttachInfo attachInfo = mAttachInfo;        ......        do {            ......            //循环层层上级调运,直到ViewRootImpl会返回null            parent = parent.invalidateChildInParent(location, dirty);            ......        } while (parent != null);    }

这个过程最后传递到ViewRootImpl的invalidateChildParent();方法结束:

public ViewParent invalidateChildInParent(int[] location, Rect dirty) {        ......        //View调运invalidate最终层层上传到ViewRootImpl后最终触发了该方法        scheduleTraversals();        ......        return null;    }

方法中最终调用了scheduleTraversals();我们之前分析过View绘制的流程,scheduleTraversals()方法会通过Handler的Runnable方法发送一个异步消息,调用doTraversal()方法,而doTraversal()方法最终调用了performTraversals()方法,performTraversals()是整个View树开始绘制的起始调运地方,所以说View调运invalidate()方法的实质是层层上传到父级,直到传递到ViewRootImpl后触发了scheduleTraversals()方法,然后整个View树开始重新按照上面分析的View绘制流程进行重绘任务。(scheduleTraversals()->doTraversal()->performTraversals())到这里,View的invalidate()方法的原理就分析完了。

分析完了只能在UI 线程中执行的方法invalidate();接下来,我们看看在其他线程中需要使用的方法postInvalidate();方法源码:

//Cause an invalidate of the specified area to happen on a subsequent cycle through the event loop. Use this to invalidate the View from a non-UI thread.public void postInvalidate(int left, int top, int right, int bottom) {        postInvalidateDelayed(0, left, top, right, bottom);    }

上面的注释也说明了,这个方法在非UI线程中调用,方法内部又调用了postInvalidateDelayed();方法:

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

可以看到,有个AttachInfo类,这个类是用于存储View和Window之间的信息,是View的内部类。将View中的mattachInfo传递进来,如果不为空,就调用ViewRootImpl.dispatchInvalidateDelayed();方法:

 public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);        mHandler.sendMessageDelayed(msg, delayMilliseconds);    }

方法内部,通过ViewRootImpl类的Handler发送了一条MSG_INVALIDATE消息,继续追踪这条消息的处理可以发现:

public void handleMessage(Message msg) {    ......    switch (msg.what) {    case MSG_INVALIDATE:        ((View) msg.obj).invalidate();        break;    ......    }    ......}

实质就是又在UI Thread中调运了View的invalidate();方法,那接下来View的invalidate();如同之前讲过的了。
画个图~
Alt text

invalidate系列方法请求重绘View树(也就是draw方法),如果View大小没有发生变化就不会调用layout过程,并且只绘制那些“需要重绘的”View,也就是哪个View(View只绘制该View,ViewGroup绘制整个ViewGroup)请求invalidate系列方法,就绘制该View。

原创粉丝点击