目录
大家都知道,调用 invalidate 会不断的调用 onDraw 方法 重新绘制,今天,看一下 invalidate 里面大概干了什么?
源码版本:android-27
依然是不停的往下找。。。。
invalidate
public void invalidate() {
invalidate(true);
}
public 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 (skipInvalidate()) {
return;
}
......
// 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);
// 将要重绘的区域传递给父 View,也就是 ViewGroup 的 invalidateChild 方法
p.invalidateChild(this, damage);
}
......
}
然后我们看一下 ViewGroup 的 invalidateChild 方法
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// HW accelerated fast path
// 开启了硬件加速,开启后,可以提高重绘的效率
onDescendantInvalidated(child, child);
return;
}
......
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
// 调用了 parent 的 invalidateChildInParent 方法,也就是 ViewGroup 的
// invalidateChildInParent 方法
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
}
} while (parent != null);
}
}
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.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY);
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
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;
}
invalidateChild 方法中的 do.....while 循环,会不断的 去执行父 View 的 invalidateChildInParent 方法,最后会找到最顶层的 ViewParent ,也就是 ViewRootImpl,所以最后会在 ViewRootImpl 中执行 invalidateChildInParent 方法:
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
// 先执行 checkThread
checkThread();
......
// 最后执行 这个方法
invalidateRectOnScreen(dirty);
// 最后返回 null,所以执行完 invalidateChildInParent() 方法后 parent 被赋值为
// null,退出 do-while 循环
return null;
}
checkThread
先看一下 checkThread() 方法,我们在子线程中更新UI,报错的地方,最后会指向 这个地方
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
这个错误,大家很熟悉吧,判断当前线程是否是主线程,不是的话,就抛异常报错。
所以 invalidate()
方法必须在主线程中调用!!
再继续看 最后调用的 invalidateRectOnScreen(dirty) 方法
private void invalidateRectOnScreen(Rect dirty) {
......
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
// 最后调用 这个方法
scheduleTraversals();
}
}
scheduleTraversals() 方法中,通过 postCallback 最后执行 runnable 中的 doTraversal() 方法
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 这里 调用 mTraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 最终调用 doTraversal 方法
doTraversal();
}
}
doTraversal() 方法中 会调用 performTraversals()
方法,然后开始 View 的三大流程
void doTraversal() {
......
// 进入 performTraversals
performTraversals();
......
}
private void performTraversals() {
......
//
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
......
// Ask host how big it wants to be
// 调用performMeasure()方法开始measure流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
// 开始layout流程
performLayout(lp, mWidth, mHeight);
.....
// 开始 draw 流程
performDraw();
.....
}
因为此时没有给 mLayoutRequested 赋值,默认值为false,所以不会调用 performMeasure
和 performLayout
方法,只调用了 performDraw
方法,也就是说调用 invalidate 时,不会执行 measure 和 layout 流程,只执行 draw,之后就会调用DecorView 的 draw
方法,遍历 DecorView 的子 View,逐层完成对子 View 的绘制。
我们继续往下看:
private void performDraw() {
......
try {
// 调用 draw 方法
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
......
}
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
......
// draw 方法中再调用 drawSoftware
/** 第一找真的不好找到这个方法!!onDraw 的逻辑竟然在这里!! **/
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
......
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
......
// 最后还是通过 Surface 来处理的, 锁定画布
canvas = mSurface.lockCanvas(dirty);
......
// 这里的 mView是 最上层的 ViewGroup, 他从最上层 ViewGroup 一直调用到最底层 view,不
// 断的从 draw 方法调用 drawBackground -> onDraw-> dispatchDraw -> onDrawForeground
mView.draw(canvas);
......
}
从这里我们可以看到,每次 invalidate ,都会从最上层 的 ViewGroup 一直到 最低层的 view 都进行绘制,如果嵌套太多的话,真的会消耗性能。
但是,开启硬件加速可以提高重绘的效率:
关闭硬件加速:绘制是从 DecorView 开始,对所有 View 都会重新绘制
开启硬件加速:只有在调用 invalidate
方法的 View中(包括它的子View)才会完成重新绘制