記錄一下前段時間學習的當調用invalidate() 的時候,目前View的onDraw()方法會被調用的原因;通過追蹤源碼可以發現(我這邊看的源碼版本是25的):
當我們調用了View的invalidate()時候,invalidate()往下走調用了 invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) 方法,其中在這個方法裡面會調用了ViewGroup.invalidateChild(View child, Rect r) 的方法
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
......
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
// ViewParent是個接口,ViewGroup 中執行個體化了這個接口
// 是以這步調用的是 ViewGroup.invalidateChild(View child, Rect r) 的方法
p.invalidateChild(this, damage);
}
......
}
其中這個方法裡面會不斷的調用了 parent.invalidateChildInParent(location, dirty);
觸發ViewParent#invalidateChildInParent方法,傳回上層父節點ViewParent,若父節點不空,繼續此方法,最終觸發頂層節點ViewRootImpl#invalidateChildInParent方法。
@Override
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this;
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
......
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) parent).mIsAnimating = true;
}
}
// 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;
}
}
parent = parent.invalidateChildInParent(location, dirty);
......
} while (parent != null);
}
}
在ViewRootImpl#invalidateChildInParent(final int[] location, final Rect dirty) 第一行會先檢查我們的目前線程是否主線程,如果不是則抛異常
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
checkThread();
......
invalidateRectOnScreen(dirty);
return null;
}
void checkThread() {
if (mThread != Thread.currentThread()) {
// 一般非主線程更新UI就報這個
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
沿着ViewRootImpl#invalidateChildInParent(final int[] location, final Rect dirty)方法往下走,最後調用了ViewRootImpl#invalidateRectOnScreen(dirty);,這個方法裡面調用了ViewRootImpl#scheduleTraversals(); 這個方法
private void invalidateRectOnScreen(Rect dirty) {
......
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
......
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
}
ViewRootImpl#scheduleTraversals()中mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);這個方法傳了三個參數,其中第二個是一個Runnable對象,這個Runnable對象中隻調用了一個 doTraversal(); 方法,在這個方法裡面則調用了ViewRootImpl#performTraversals();
void doTraversal() {
if (mTraversalScheduled) {
......
performTraversals();
......
}
}
ViewRootImpl#performTraversals()這個方法往下走,在2215行裡調用了ViewRootImpl#performDraw();的方法

ViewRootImpl#performDraw()裡面往下走調用了ViewRootImpl#draw(boolean fullRedrawNeeded)方法
private void performDraw() {
......
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
......
}
在ViewRootImpl#draw(boolean fullRedrawNeeded)裡做的先擷取了mDirty值,該值儲存了需要重繪的區域的資訊,關于視圖重繪,後面會有文章專門叙述,這裡先熟悉一下。接着根據fullRedrawNeeded來判斷是否需要重置dirty區域,最後調用了ViewRootImpl#drawSoftware方法,并把相關參數傳遞進去,包括dirty區域
private void draw(boolean fullRedrawNeeded) {
...
//擷取mDirty,該值表示需要重繪的區域
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
if (animating) {
if (mScroller != null) {
mScroller.abortAnimation();
}
disposeResizeBuffer();
}
return;
}
//如果fullRedrawNeeded為真,則把dirty區域置為整個螢幕,表示整個視圖都需要繪制
//第一次繪制流程,需要繪制所有視圖
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
......
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
往下走最終調了ViewRootImpl#drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty)的方法,這個方法裡面調了 mView.draw(canvas); 其中這個mView是我們最外層的View也就是DecorView,再由最外層的View往裡面走調用了dispatchDraw(canvas);,一層一層的繪制,最終畫到目前的調用了invalidate()方法的View的onDraw
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
//鎖定canvas區域,由dirty區域決定
canvas = mSurface.lockCanvas(dirty);
// The dirty rectangle can be modified by Surface.lockCanvas()
//noinspection ConstantConditions
if (left != dirty.left || top != dirty.top || right != dirty.right
|| bottom != dirty.bottom) {
attachInfo.mIgnoreDirtyState = true;
}
canvas.setDensity(mDensity);
}
try {
......
//正式開始繪制
mView.draw(canvas);
......
}
}
return true;
}