• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

界面刷新的2种方式

武飞扬头像
囧_囧
帮助1

我们都知道在平常开发的过程中,如果一个view关于布局的属性改变了,想要在界面上看到效果,需要我们手动去调用view#requestLayout或者view#invalidate方法。可是有些时候又不需要我们直接调用,比如ImageView.setImageDrawable()。那这到底这是为什么呢?背后到底发生了什么?下面开始我们的解密之旅。

View的间接调用

我们先来看看上面举的ImageView.setImageDrawable的例子。

ImageView#setImageDrawable

public void setImageDrawable(@Nullable Drawable drawable) {
        if (mDrawable != drawable) {
            mResource = 0;
            mUri = null;

            final int oldWidth = mDrawableWidth;
            final int oldHeight = mDrawableHeight;

            updateDrawable(drawable);

            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
                requestLayout();
            }
            //注意这行代码
            invalidate();
        }
    }

通过代码我们发现,虽然我们没有显示的去调用2种方式中的一种,但是方法内部却间接的调用了invalidate()。当然还存在另一种情况,就是当我们的控件的布局(位置或者大小)发生改变的时候。举个例子:当我们往往会通过view的内部类LayoutParams改变view的大小或者布局,然后再通过View#setLayoutParams最终来改变view的布局,下面让我们来看下代码。

View#setLayoutParams

public void setLayoutParams(ViewGroup.LayoutParams params) {
        if (params == null) {
            throw new NullPointerException("Layout parameters cannot be null");
        }
        mLayoutParams = params;
        resolveLayoutParams();
        if (mParent instanceof ViewGroup) {
            ((ViewGroup) mParent).onSetLayoutParams(this, params);
        }
        //注意这行代码
        requestLayout();
    }

很明显setLayoutParams的内部调用了requestLayout。 我们上面只举了比较常用的2个例子,当然还有其他的,大家可以自己去查找一下,只要是跟view的布局属性相关的改变,都会间接的调用到那两个方法。可是,这2个方法有什么区别呢,在什么情况下该调用invalidate或者requestLayout?下面让进一步深入这2个方法。

View#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.
* <p>
* This must be called from a UI thread. To call from a non-UI thread, call
* {@link #postInvalidate()}.
*/

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        if (mGhostView != null) {
            mGhostView.invalidate(true);
            return;
        }
      //这里判断该子View是否可见或者是否处于动画中
        if (skipInvalidate()) {
            return;
        }
         //根据View的标记位来判断该子View是否需要重绘,假如View没有任何变化,那么就不需要重绘,这里很重要一定要注意,通过标志位来决定是否需要重新绘制
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
                || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
            if (fullInvalidate) {
                mLastIsOpaque = isOpaque();
                mPrivateFlags &= ~PFLAG_DRAWN;
            }

            mPrivateFlags |= PFLAG_DIRTY;

            if (invalidateCache) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }

            // Propagate the damage rectangle to the parent view.
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            //当父容器不为null把需要重绘的区域传递给父容器
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                //注意这行代码
                p.invalidateChild(this, damage);
            }

            // Damage the entire projection receiver, if necessary.
            if (mBackground != null && mBackground.isProjected()) {
                final View receiver = getProjectionReceiver();
                if (receiver != null) {
                    receiver.damageInParent();
                }
            }
        }
    }

意思就是当整个view可见的时候,会使整个view无效,会间接调用到onDraw方法,并且只能在UI线程中调用,如果要在非UI线程中调用推荐使用postInvalidate。 首先来看下 mParent.invalidateChild方法。在view中invalidateChild是个抽象方法,那我们进入ViewGroup来看下他的invalidateChild。

ViewGroup#invalidateChild

   @Override
    public final void invalidateChild(View child, final Rect dirty) {
        final AttachInfo attachInfo = mAttachInfo;
        ……
        ViewParent parent = this;
   
            do {
                View view = null;
                // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
                // flag coming from the child that initiated the invalidate
                if (view != null) {
                    if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
                            view.getSolidColor() == 0) {
                        opaqueFlag = PFLAG_DIRTY;
                    }
                    if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                        view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
                    }
                }
                //注意这行代码  //调用ViewGrup的invalidateChildInParent,如果已经达到最顶层view,则调用ViewRootImpl
            //的invalidateChildInParent。
                parent = parent.invalidateChildInParent(location, dirty);
              ……
            } while (parent != null);
        }
    }

ViewGroup#invalidateChildInParent

@Override
    public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
            // either DRAWN, or DRAWING_CACHE_VALID
            if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
                    != FLAG_OPTIMIZE_INVALIDATE) {
                //将dirty中的坐标转化为父容器中的坐标,考虑mScrollX和mScrollY的影响
                dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
                        location[CHILD_TOP_INDEX] - mScrollY);
                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
                  //求并集,结果是把子视图的dirty区域转化为父容器的dirty区域
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }

                final int left = mLeft;
                final int top = mTop;

                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
                        dirty.setEmpty();
                    }
                }

                location[CHILD_LEFT_INDEX] = left;
                location[CHILD_TOP_INDEX] = top;
            } else {

                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
                } else {
                    // in case the dirty rect extends outside the bounds of this container
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }
                location[CHILD_LEFT_INDEX] = mLeft;
                location[CHILD_TOP_INDEX] = mTop;

                mPrivateFlags &= ~PFLAG_DRAWN;
            }
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            if (mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_INVALIDATED;
            }
          //返回当前视图的父容器
            return mParent;
        }

        return null;
    }

从上文中我们知道parent.invalidateChildInParent会一致向上调用到ViewRootImpl#invalidateChildInParent方法,其实这就是责任链模式,在android的view绘制体系中大量存在。根据我以前分析过得文章中可知View#mParent是在ViewGroup#addView中绑定的。

ViewRootImpl#invalidateChildInParent

 @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: "   dirty);

        if (dirty == null) {
            invalidate();
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }

        if (mCurScrollY != 0 || mTranslator != null) {
            mTempRect.set(dirty);
            dirty = mTempRect;
            if (mCurScrollY != 0) {
                dirty.offset(0, -mCurScrollY);
            }
            if (mTranslator != null) {
                mTranslator.translateRectInAppWindowToScreen(dirty);
            }
            if (mAttachInfo.mScalingRequired) {
                dirty.inset(-1, -1);
            }
        }

        invalidateRectOnScreen(dirty);

        return null;
    }

void invalidate() {
        mDirty.set(0, 0, mWidth, mHeight);
        if (!mWillDrawSoon) {
            
            scheduleTraversals();
        }
    }

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //这里会往UI线程发送一个runnable请求就是mTraversalRunnable
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            //进入遍历绘制操作
            doTraversal();
        }
    }

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
        try {
            //执行遍历绘制操作 performTraversals代码太长,我们就不分析,反正会根据标志位进行meaure、layout、draw操作
            performTraversals();
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

好了invalidate到这就结束,我们再回过头看下postInvalidate方法。

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

ViewRootImpl#dispatchInvalidateDelayed

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

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

我们可以看到其实就是进入ViewRootImpl进行了一次线程切换然后又回到了View#invalidate流程。

好了,关于invalidate流程到这就结束了,我们来简单的总结下

  1. invalidate:首先会进行本身的标志位设置,看看是否需要进行mease、layout、draw操作,需要需要会进入ViewRootImpl#invalidateChildInParent,最终进入到performTraversals执行遍历绘制操作。
  2. postInvalidate:postInvalidate比invalidate多了一步在ViewRootImpl的线程切换,然后下面的流程跟invalidate一摸一样。

View#requestLayout的真相

View#requestLayout

public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }
        //注意 只有设置了PFLAG_FORCE_LAYOUT标致位才会进行mease操作,文末会附上mesure和layout的代码
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            //进入ViewGroup的requestLayout操作
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

根据上面的分析我们应该能想到最终也会进入到ViewRootImpl的requestLayout,大家可以自己去跟一遍代码。

ViewRootImpl#requestLayout

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

首先进行了UI线程检查,然后同样进入到了scheduleTraversals()操作,跟invalidate一样,我们这里就不解释了。

总结

其实invalidate相比requestLayout就是在进入ViewRootImpl之前进行了一系列的标致位设置。防止view进行一系列不必要的测量和布局,当然draw流程是两者都需要进行的。然后进入ViewRootImpl的函数调用流程是这样的:
scheduleTraversals-》doTraversal-》performTraversals,其中scheduleTraversals到doTraversal是通过Handler机制进行调用的。所以,当我们在编码时,当明确的知道没有进行控件的大小和布局改变的时候调用invalidate可以减少不必要的测量和布局,提升性能

参考

View#measure

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
     ...

    if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
            widthMeasureSpec != mOldWidthMeasureSpec ||
            heightMeasureSpec != mOldHeightMeasureSpec) {
        ...
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        } 
        ...
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }
}

首先是判断一下标记位,如果当前View的标记位为PFLAG_FORCE_LAYOUT,那么就会进行测量流程,调用onMeasure,对该View进行测量,接着最后为标记位设置为PFLAG_LAYOUT_REQUIRED,这个标记位的作用就是在View的layout流程中,如果当前View设置了该标记位,则会进行布局流程

View#layout

public void layout(int l, int t, int r, int b) {
    ...
    //判断标记位是否为PFLAG_LAYOUT_REQUIRED,如果有,则对该View进行布局
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        //onLayout方法完成后,清除PFLAG_LAYOUT_REQUIRED标记位
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                    (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners;   i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }

    //最后清除PFLAG_FORCE_LAYOUT标记位
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

其中Draw流程是2这都要进行的,这里就不贴代码了,而且view的onDraw方法是空实现,具体的实现流程都交给了子view。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhckejah
系列文章
更多 icon
同类精品
更多 icon
继续加载