经过上一章的摸索,我们已经了解了Android中View的绘制流程分别是measure、layout和draw,那么对Android有一些了解的话,一定知道View中有这样几个方法invalidate、postInvalidate以及requestLayout,我们知道这些方法调用后会触发View的重绘(不一定正确的说法),那么它们的用法是什么,有什么区别以及使用时候有哪些注意事项,这就是我们这一篇文章力求弄明白的东西,好了,废话不多说,开始今天的分析。
我们从View源码中追本溯源,可以看到在View中方法的调用情况是这样的:
/**
* 使当前View失效,若View是可见的那么onDraw方法会在未来的某个时间点被调用
* 该方法必须在UI线程被调用,若需要在非UI线程调用,调用postInvalidate
*/
public void invalidate() {
invalidate(true);
}
/**
* 这个方法是invalidate整整产生作用的地方,一个invalidate请求会将绘制缓存设为失效
* 不过这个方式可以设置参数为false来禁止绘制缓存失效
*/
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
...
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)) {
// 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);
//调用父控件的invalidateChild方法
p.invalidateChild(this, damage);
}
...
}
}
从这里可以看出invalidate方法的作用是令当前View的绘制缓存失效,并且会调用onDraw方法,不过我们并没有在上述代码中看到onDraw(注意invalidate方法只能在UI线程中调用,工作线程调用该方法会报错),我们知道该方法最终调用了父控件的invalidateChild方法,那么我们来查看下ViewGroup中的invalidateChild:
/**
* 不要主动调用该方法
*/
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
...
do {
...
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;
}
}
parent = parent.invalidateChildInParent(location, dirty);
} while (parent != null);
}
}
该方法执行了一段循环代码,这里层层调用父控件的invalidateChildInParent方法,知道返回值为空,我们知道绘制的起点是在ViewRootImpl中,那么打开ViewRootImpl,查看其invalidateChildInParent代码:
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
invalidateRectOnScreen(dirty);
return null;
}
private void invalidateRectOnScreen(Rect dirty) {
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
这里在返回null之后上述循环终止,我们注意到在循环终止前ViewRootImpl调用了scheduleTraversals方法,这个方法跟我们之前讲过的performTraversals方法很像,我们来看看里面有什么:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//这行代码在Choreographer类内部使用Handler机制最终调用了doTraversal方法
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//调用此方法判断是否需要重新测量放置以及绘制
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
可以看到在绕了一圈之后,ViewRootImpl最终会调用我们上篇讲到的performTraversals方法,重复判断流程。至此,View的invalidate方法执行流程完毕。
我们在前面的注释里可以看出,invalidate方法是不可以在非UI线程执行的,而postInvalidate则可以,结合我们之前学习的Handler知识,我们猜想可能是使用了handler机制,最终仍然是调用了invalidate,那我们来源码中验证一下我们的猜想:
public void postInvalidate() {
postInvalidateDelayed(0);
}
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);
}
}
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
}
}
可以看到,跟我们猜想的一样,经过一系列列调用,最后使用Handler机制,调用了invalidate方法,也就是说这两个方法区别基本只在线程之间。
requestLayout也是我么View中一个很重要的方法,我们在之前讲绘制流程的时候也有看到这个方法,那么我们来探究一下它究竟做了什么:
public void requestLayout() {
...
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
...
}
这方法与之前invalidateInternal方法类似,也是一层层调用父控件的requestLayout方法,直到ViewRootImpl(ViewGroup没有复写该方法,这里不再关注):
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
又看到了熟悉的scheduleTraversals方法,这里跟之前invalidate的区别仅仅只是标志位可能有所改变,后续流程一致。
一般引起invalidate调用的情景如下:
好了,这就是本篇的全部内容了,这里还是偏理论的东西较多,掌握脉络即可。下一篇文章我们会来聊一聊Android中的事件分发机制,敬请期待~
如果您觉得文章不错,可以来我的个人博客查看更多内容~