美文网首页View绘制流程
Android invalidate流程分析

Android invalidate流程分析

作者: 曾大稳丶 | 来源:发表于2017-06-11 19:33 被阅读491次

    我们刚接触android开发的时候,应该都是从写布局开始的,在写布局的时候一般组长都要求我们少嵌套,这个是为什么呢?这个就要从我们今天要分析的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()}.
         */
        public void invalidate() {
            invalidate(true);
        }
    
    
        /**
         * This is where the invalidate() work actually happens. A full invalidate()
         * causes the drawing cache to be invalidated, but this function can be
         * called with invalidateCache set to false to skip that invalidation step
         * for cases that do not need it (for example, a component that remains at
         * the same dimensions with the same content).
         *
         * @param invalidateCache Whether the drawing cache for this view should be
         *            invalidated as well. This is usually true for a full
         *            invalidate, but may be set to false if the View's contents or
         *            dimensions have not changed.
         */
        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);
                    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();
                    }
                }
    
                // Damage the entire IsolatedZVolume receiving this view's shadow.
                if (isHardwareAccelerated() && getZ() != 0) {
                    damageShadowReceiver();
                }
            }
        }
    

    可以看到我们最终会进入invalidateInternal这个函数可以看到这段代码:

                final ViewParent p = mParent;
                if (p != null && ai != null && l < r && t < b) {
                    final Rect damage = ai.mTmpInvalRect;
                    damage.set(l, t, r, b);
                    p.invalidateChild(this, damage);
                }
    
    

    很明显的就是进入了父布局的invalidateChild函数,我们就从ViewGroup里面看

    /**
         * Don't call or override this method. It is used for the implementation of
         * the view hierarchy.
         */
        @Override
        public final void invalidateChild(View child, final Rect dirty) {
              ViewParent parent = this;
              //......
              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);
                    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);
                //...
        }
    
        /**
         * Don't call or override this method. It is used for the implementation of
         * the view hierarchy.
         *
         * This implementation returns null if this ViewGroup does not have a parent,
         * if this ViewGroup is already fully invalidated or if the dirty rectangle
         * does not intersect with this ViewGroup's bounds.
         */
        @Override
        public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
            if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
                    (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_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();
                        }
                    }
                    mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
    
                    location[CHILD_LEFT_INDEX] = left;
                    location[CHILD_TOP_INDEX] = top;
    
                    if (mLayerType != LAYER_TYPE_NONE) {
                        mPrivateFlags |= PFLAG_INVALIDATED;
                    }
    
                    return mParent;
    
                } else {
                    mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;
    
                    location[CHILD_LEFT_INDEX] = mLeft;
                    location[CHILD_TOP_INDEX] = mTop;
                    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);
                    }
    
                    if (mLayerType != LAYER_TYPE_NONE) {
                        mPrivateFlags |= PFLAG_INVALIDATED;
                    }
    
                    return mParent;
                }
            }
    
            return null;
        }
    
    
    

    从这里我们可以看到,在这个函数里面,主要是对当前viewgroup在次验证是否还有父布局,使用do while循环得到parent,等到最上层没有parent的时候才执行下一步,从这就可以知道,如果嵌套太多层的话,就会在这消耗性能。这样的话我们就可以知道,肯定是调用到了最外层的ViewGroup,也就是ViewRootImpl,我们查看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);
                }
            }
    
            invalidateRectOnScreen(dirty);
    
            return null;
        }
    
       void checkThread() {
            if (mThread != Thread.currentThread()) {
                throw new CalledFromWrongThreadException(
                        "Only the original thread that created a view hierarchy can touch its views.");
            }
        }
    
      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)) {
                scheduleTraversals();
            }
        }
    
         void scheduleTraversals() {
            if (!mTraversalScheduled) {
                mTraversalScheduled = true;
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                if (!mUnbufferedInputDispatch) {
                    scheduleConsumeBatchedInput();
                }
                notifyRendererOfFramePending();
                pokeDrawLockIfNeeded();
           
        //.....
    

    从这里我们就知道了,首先会检测线程,也就是为什么在子线程更新UI为什么会崩溃的原因,然后经过一系列的判断进入到scheduleTraversals函数,在这个函数中可以看到会调用mTraversalRunnable这个Runnable

    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    
     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");
                }
    
                performTraversals();
    
                if (mProfile) {
                    Debug.stopMethodTracing();
                    mProfile = false;
                }
            }
        }
    

    可以看到,实际上就是调用了performTraversals()函数,这个函数很长,我们主要看关键点:

        private void performTraversals() {
          //......
          // Ask host how big it wants to be
          performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
          //......
           performLayout(lp, mWidth, mHeight);
          //.....
           performDraw();
          //.....
       }
    

    可以看到,进入这里面了,会依次调用performMeasure performLayout
    performDraw三个函数,依次调用了view的绘制流程。

    顾名思义,在performMeasure中主要会实现测量

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
            try {
                mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
        }
    

    mView就是ViewGroup,然后会调用ViewGrouponMeasure函数,然后测量,就从最上层父布局一直到测量到最底层的view
    performLayout主要负责子view摆放

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                int desiredWindowHeight) {
            mLayoutRequested = false;
            mScrollMayChange = true;
            mInLayout = true;
    
            final View host = mView;
            if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
                Log.v(mTag, "Laying out " + host + " to (" +
                        host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
            }
    
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
            try {
                host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    
                mInLayout = false;
                int numViewsRequestingLayout = mLayoutRequesters.size();
                if (numViewsRequestingLayout > 0) {
                    // requestLayout() was called during layout.
                    // If no layout-request flags are set on the requesting views, there is no problem.
                    // If some requests are still pending, then we need to clear those flags and do
                    // a full request/measure/layout pass to handle this situation.
                    ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                            false);
                    if (validLayoutRequesters != null) {
                        // Set this flag to indicate that any further requests are happening during
                        // the second pass, which may result in posting those requests to the next
                        // frame instead
                        mHandlingLayoutInLayoutRequest = true;
    
                        // Process fresh layout requests, then measure and layout
                        int numValidRequests = validLayoutRequesters.size();
                        for (int i = 0; i < numValidRequests; ++i) {
                            final View view = validLayoutRequesters.get(i);
                            Log.w("View", "requestLayout() improperly called by " + view +
                                    " during layout: running second layout pass");
                            view.requestLayout();
                        }
                        measureHierarchy(host, lp, mView.getContext().getResources(),
                                desiredWindowWidth, desiredWindowHeight);
                        mInLayout = true;
                        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    
                        mHandlingLayoutInLayoutRequest = false;
    
                        // Check the valid requests again, this time without checking/clearing the
                        // layout flags, since requests happening during the second pass get noop'd
                        validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                        if (validLayoutRequesters != null) {
                            final ArrayList<View> finalRequesters = validLayoutRequesters;
                            // Post second-pass requests to the next frame
                            getRunQueue().post(new Runnable() {
                                @Override
                                public void run() {
                                    int numValidRequests = finalRequesters.size();
                                    for (int i = 0; i < numValidRequests; ++i) {
                                        final View view = finalRequesters.get(i);
                                        Log.w("View", "requestLayout() improperly called by " + view +
                                                " during second layout pass: posting in next frame");
                                        view.requestLayout();
                                    }
                                }
                            });
                        }
                    }
    
                }
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            mInLayout = false;
        }
    

    同样的道理,又是从最上层viewGroup到最底层的view
    performDraw负责绘制,performDraw()会 调用draw,在调用drawSoftware

    /**
         * @return true if drawing was successful, false if an error occurred
         */
        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 = 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;
                }
    
                // TODO: Do this in native
                canvas.setDensity(mDensity);
            } catch (Surface.OutOfResourcesException e) {
                handleOutOfResourcesException(e);
                return false;
            } catch (IllegalArgumentException e) {
                Log.e(mTag, "Could not lock surface", e);
                // Don't assume this is due to out of memory, it could be
                // something else, and if it is something else then we could
                // kill stuff (or ourself) for no reason.
                mLayoutRequested = true;    // ask wm for a new surface next time.
                return false;
            }
    
            try {
                if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                    Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
                            + canvas.getWidth() + ", h=" + canvas.getHeight());
                    //canvas.drawARGB(255, 255, 0, 0);
                }
    
                // If this bitmap's format includes an alpha channel, we
                // need to clear it before drawing so that the child will
                // properly re-composite its drawing on a transparent
                // background. This automatically respects the clip/dirty region
                // or
                // If we are applying an offset, we need to clear the area
                // where the offset doesn't appear to avoid having garbage
                // left in the blank areas.
                if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                    canvas.drawColor(0, PorterDuff.Mode.CLEAR);
                }
    
                dirty.setEmpty();
                mIsAnimating = false;
                mView.mPrivateFlags |= View.PFLAG_DRAWN;
    
                if (DEBUG_DRAW) {
                    Context cxt = mView.getContext();
                    Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
                            ", metrics=" + cxt.getResources().getDisplayMetrics() +
                            ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
                }
                try {
                    canvas.translate(-xoff, -yoff);
                    if (mTranslator != null) {
                        mTranslator.translateCanvas(canvas);
                    }
                    canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                    attachInfo.mSetIgnoreDirtyState = false;
    
                    mView.draw(canvas);
    
                    drawAccessibilityFocusedDrawableIfNeeded(canvas);
                } finally {
                    if (!attachInfo.mSetIgnoreDirtyState) {
                        // Only clear the flag if it was not set during the mView.draw() call
                        attachInfo.mIgnoreDirtyState = false;
                    }
                }
            } finally {
                try {
                    surface.unlockCanvasAndPost(canvas);
                } catch (IllegalArgumentException e) {
                    Log.e(mTag, "Could not unlock surface", e);
                    mLayoutRequested = true;    // ask wm for a new surface next time.
                    //noinspection ReturnInsideFinallyBlock
                    return false;
                }
    
                if (LOCAL_LOGV) {
                    Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
                }
            }
            return true;
        }
    
    

    mView.draw(canvas);可以看到,又是一样的从最上层ViewGroup一直调用最底层view,不断的从draw方法调用drawBackground->onDraw->dispatchDraw->onDrawForeground
    流程为下图:

    流程.png

    从这更加验证了我们嵌套多层之后会消耗性能的真理。
    invalidate()就分析到这里了,有什么意见或者文中有什么错误的希望可以在下方评论。希望大家可以在看我的文章中可以学习到知识。

    相关文章

      网友评论

        本文标题:Android invalidate流程分析

        本文链接:https://www.haomeiwen.com/subject/bjohqxtx.html