淺談Android invalidate 分析
1. invalidate 和 postInvalidate 的關(guān)系
postInvalidate 是通過 Handler 切換回到主線程,然后在調(diào)用 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); } } // ViewRootImpl 中 public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds); } final class ViewRootHandler extends Handler { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break; ... }
2. 子線程是否可以更新 UI ?
可以的,在 Activity 的 onCreate 中直接開啟子線程并在子線程中更新 UI 是沒問題的:
public class MainActivity extends Activity { private TextView tvText; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvText = (TextView) findViewById(R.id.main_tv); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } tvText.setText("OtherThread"); } }).start(); } }
原因:校驗(yàn)線程是 ViewRootImpl 來做的,但是它的創(chuàng)建流程是在 Activity 的 onResume 的時(shí)候:
// ActivityThread 中 final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ActivityClientRecord r = mActivities.get(token); ... if (r != null) { final Activity a = r.activity; if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); ... if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; // 關(guān)鍵代碼 wm.addView(decor, l); } else { a.onWindowAttributesChanged(l); } } ... } // WindowManagerGlobal 中 public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... ViewRootImpl root; View panelParentView = null; synchronized (mLock) { ... // 在這里創(chuàng)建 ViewRootImpl root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); ... } } // 在 ViewRootImpl 中有這么段代碼,所有更新 UI 都會走到這里 void checkThread() { if (mThread != Thread.currentThread()) { // mThread 就是主線程 throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
所以子線程只要在 ViewRootImpl 創(chuàng)建之前更新 UI 就沒問題!
3. invalidate 的源碼分析
先看一張圖:
invalidate 的流程
于是自己嘗試走走源碼:
// view 中 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 (mGhostView != null) { mGhostView.invalidate(true); return; } if (skipInvalidate()) { return; } 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; if (p != null && ai != null && l < r && t < b) { final Rect damage = ai.mTmpInvalRect; damage.set(l, t, r, b); // 調(diào)用父類的 invalidateChild 方法 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 的 invalidate 最后是調(diào)用了 p.invalidateChild(this, damage); p 是 ViewParent 的對象,具體實(shí)現(xiàn)是 ViewGroup
// ViewGroup 中 @Override public final void invalidateChild(View child, final Rect dirty) { final AttachInfo attachInfo = mAttachInfo; ... ViewParent parent = this; do { View view = null; ... // 關(guān)鍵代碼 parent = parent.invalidateChildInParent(location, dirty); ... } while (parent != null); } @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.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; }
上面 invalidateChildInParent 開始時(shí)會調(diào)用 ViewGroup 自己的 invalidateChildInParent 方法,但到最后還是會調(diào)用到 ViewRootImpl 中的 invalidateChildInParent,看下 ViewRootImpl 中的具體實(shí)現(xiàn)
// ViewRootImpl 中 @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); } } // 又調(diào)用了這個(gè)方法 invalidateRectOnScreen(dirty); return null; } private void invalidateRectOnScreen(Rect dirty) { final Rect localDirty = mDirty; if (!localDirty.isEmpty() && !localDirty.contains(dirty)) { mAttachInfo.mSetIgnoreDirtyState = true; mAttachInfo.mIgnoreDirtyState = true; } // Add the new dirty rect to the current one localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); // Intersect with the bounds of the window to skip // updates that lie outside of the visible region final float appScale = mAttachInfo.mApplicationScale; final boolean intersected = localDirty.intersect(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); if (!intersected) { localDirty.setEmpty(); } if (!mWillDrawSoon && (intersected || mIsAnimating)) { // 關(guān)鍵又調(diào)用了這個(gè)方法 scheduleTraversals(); } } void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 會調(diào)用 mTraversalRunnable 中的 run 方法 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().getQueue().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } // 終于到了關(guān)鍵方法了: performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } }
ViewRootImpl 最終調(diào)用到了performTraversals 中,這個(gè)方法巨長,涉及到了 onMeasure/onLayout/onDraw 等重要方法的起源:
private void performTraversals() { ... // 這里最終會觸發(fā) view 的 onMeasure performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); performLayout(lp, mWidth, mHeight); performDraw(); ... mIsInTraversal = false; }
看到上面就是對應(yīng)著 View 的繪制流程了,繼續(xù)看 performDraw 的實(shí)現(xiàn):
private void performDraw() { ... try { draw(fullRedrawNeeded); } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } ... } private void draw(boolean fullRedrawNeeded) { if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; } } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { ... try { canvas.translate(-xoff, -yoff); if (mTranslator != null) { mTranslator.translateCanvas(canvas); } canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); attachInfo.mSetIgnoreDirtyState = false; // 最終調(diào)用了 View 的 draw 方法了 mView.draw(canvas); } finally { ... } return true; }
看到終于調(diào)用到 View 的 draw 方法來了,繼續(xù)看下 ViewGroup 和 View 在這方法中的處理方式:
// View 中的 draw 方法: public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); drawAutofilledHighlight(canvas); // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); // Step 7, draw the default focus highlight drawDefaultFocusHighlight(canvas); if (debugDraw()) { debugDrawFocus(canvas); } // we're done... return; } /* * Here we do the full fledged routine... * (this is an uncommon case where speed matters less, * this is why we repeat some of the tests that have been * done above) */ boolean drawTop = false; boolean drawBottom = false; boolean drawLeft = false; boolean drawRight = false; float topFadeStrength = 0.0f; float bottomFadeStrength = 0.0f; float leftFadeStrength = 0.0f; float rightFadeStrength = 0.0f; // Step 2, save the canvas' layers int paddingLeft = mPaddingLeft; final boolean offsetRequired = isPaddingOffsetRequired(); if (offsetRequired) { paddingLeft += getLeftPaddingOffset(); } int left = mScrollX + paddingLeft; int right = left + mRight - mLeft - mPaddingRight - paddingLeft; int top = mScrollY + getFadeTop(offsetRequired); int bottom = top + getFadeHeight(offsetRequired); if (offsetRequired) { right += getRightPaddingOffset(); bottom += getBottomPaddingOffset(); } final ScrollabilityCache scrollabilityCache = mScrollCache; final float fadeHeight = scrollabilityCache.fadingEdgeLength; int length = (int) fadeHeight; // clip the fade length if top and bottom fades overlap // overlapping fades produce odd-looking artifacts if (verticalEdges && (top + length > bottom - length)) { length = (bottom - top) / 2; } // also clip horizontal fades if necessary if (horizontalEdges && (left + length > right - length)) { length = (right - left) / 2; } if (verticalEdges) { topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength())); drawTop = topFadeStrength * fadeHeight > 1.0f; bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength())); drawBottom = bottomFadeStrength * fadeHeight > 1.0f; } if (horizontalEdges) { leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength())); drawLeft = leftFadeStrength * fadeHeight > 1.0f; rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength())); drawRight = rightFadeStrength * fadeHeight > 1.0f; } saveCount = canvas.getSaveCount(); int solidColor = getSolidColor(); if (solidColor == 0) { final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; if (drawTop) { canvas.saveLayer(left, top, right, top + length, null, flags); } if (drawBottom) { canvas.saveLayer(left, bottom - length, right, bottom, null, flags); } if (drawLeft) { canvas.saveLayer(left, top, left + length, bottom, null, flags); } if (drawRight) { canvas.saveLayer(right - length, top, right, bottom, null, flags); } } else { scrollabilityCache.setFadeColor(solidColor); } // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers final Paint p = scrollabilityCache.paint; final Matrix matrix = scrollabilityCache.matrix; final Shader fade = scrollabilityCache.shader; if (drawTop) { matrix.setScale(1, fadeHeight * topFadeStrength); matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, top, right, top + length, p); } if (drawBottom) { matrix.setScale(1, fadeHeight * bottomFadeStrength); matrix.postRotate(180); matrix.postTranslate(left, bottom); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, bottom - length, right, bottom, p); } if (drawLeft) { matrix.setScale(1, fadeHeight * leftFadeStrength); matrix.postRotate(-90); matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, top, left + length, bottom, p); } if (drawRight) { matrix.setScale(1, fadeHeight * rightFadeStrength); matrix.postRotate(90); matrix.postTranslate(right, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(right - length, top, right, bottom, p); } canvas.restoreToCount(saveCount); drawAutofilledHighlight(canvas); // Overlay is part of the content and draws beneath Foreground if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); }
這個(gè)方法的大體意思是這樣的:
@Override public void draw(Canvas canvas) { ... drawBackground(canvas); // 繪制背景 onDraw(canvas); // 調(diào)用自己的 onDraw 方法來繪制內(nèi)容 dispatchDraw(canvas); // 分發(fā)繪制 onDrawForeground(canvas); // 繪制前景 ... }
上面幾個(gè)方法中,只有 dispatchDraw 涉及到分發(fā)繪制,其他的都是對自身的繪制,所以繼續(xù)看 dispatchDraw
// View 中的實(shí)現(xiàn),是個(gè)空方法,也就是 View 沒有孩子,不需要什么分發(fā) protected void dispatchDraw(Canvas canvas) { }
View 中的實(shí)現(xiàn),是個(gè)空方法,也就是 View 沒有孩子,不需要什么分發(fā)。那么看下 ViewGroup 中是怎么分發(fā)的:
@Override protected void dispatchDraw(Canvas canvas) { ... while (transientIndex >= 0) { // there may be additional transient views after the normal views final View transientChild = mTransientViews.get(transientIndex); if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() != null) { // 看到這里,會去繪制 子View more |= drawChild(canvas, transientChild, drawingTime); } transientIndex++; if (transientIndex >= transientCount) { break; } } ... } protected boolean drawChild(Canvas canvas, View child, long drawingTime) { // 又調(diào)回了 View 的 draw 來了 return child.draw(canvas, this, drawingTime); }
上面又調(diào)會了 View 的 draw 來了,如此遞歸調(diào)用下去,直到遍歷完所有的 VIew 。
4. 總結(jié)
1 invalidate 和 postInvalidate 的關(guān)系:
postInvalidate 最終通過 Handler 切換到主線程,調(diào)用 invalidate
2 能否在子線程中更新 UI ?
只要在校驗(yàn) UI 線程前,子線程是可以更新 UI 的,也就是 Activity 的 onResume 方法前。因?yàn)樵?onResume 中創(chuàng)建了 ViewRootImpl。
3 invalidate 源碼
invalidate 會先找到父類去走繪制流程,最終遍歷所有相關(guān)聯(lián)的 View ,觸發(fā)它們的 onDraw 方法進(jìn)行繪制
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解Android Activity之間跳轉(zhuǎn)出現(xiàn)短暫黑屏的處理方法
本篇文章主要介紹了詳解Android Activity之間跳轉(zhuǎn)出現(xiàn)短暫黑屏的處理方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-06-06Android中的SpannableString與SpannableStringBuilder詳解
這篇文章主要給大家介紹了關(guān)于Android中SpannableString與SpannableStringBuilder的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10Android定時(shí)器和Handler用法實(shí)例分析
這篇文章主要介紹了Android定時(shí)器和Handler用法,實(shí)例分析了Android中的定時(shí)器與Handler相關(guān)使用技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04Android中實(shí)現(xiàn)密碼的隱藏和顯示的示例
本篇文章主要介紹了Android中實(shí)現(xiàn)密碼的隱藏和顯示的示例,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-09-09Android基于ViewPager實(shí)現(xiàn)的應(yīng)用歡迎界面完整實(shí)例
這篇文章主要介紹了Android基于ViewPager實(shí)現(xiàn)的應(yīng)用歡迎界面,結(jié)合完整實(shí)例形式分析了ViewPager類用于歡迎界面顯示圖片的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下2016-08-08android配合viewpager實(shí)現(xiàn)可滑動的標(biāo)簽欄示例分享
本文主要介紹了android實(shí)現(xiàn)可滑動的標(biāo)簽欄示例,配合viewpager作為標(biāo)簽欄,且可以設(shè)置每頁顯示的標(biāo)簽個(gè)數(shù),超出可滑動顯示,需要的朋友可以參考下2014-02-02Android實(shí)現(xiàn)給TableLayou繪制邊框的方法
這篇文章主要介紹了Android實(shí)現(xiàn)給TableLayou繪制邊框的方法,涉及Android TableLayou布局控制相關(guān)技巧,需要的朋友可以參考下2016-03-03