View的绘制过程

View的绘制过程

  • Q:知道了activity_main.xml是如何添加到DecorView的,那这个DecorView是如何添加到Window的呢?
  • 简单回顾下Phonewindow#setCountView的过程
    1.创建好DecorView
    2.先确定好activtiy的父布局即mContentParent是 #generateLayout得到的
    3.在generateLayout方法中通过判断确定好layoutResource(系统预设的xml布)局
    4.找到在layoutResource中id为com.android.internal.R.id.content的ViewGroup赋值给mContentParent
    5.layoutResource通过mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)方法layoutResource把添加进了DecorView
    6.最后将activity_main添加到mContentParent,到这里就结束了整个流程,并没有将DecorView添加到window的步骤。
  • 关于LayoutInflater#inflate
  • 大体可以理解将xml解析出来,通过LayoutInflater#createViewFromTag方法根据名称反射实例化view后添加到ViewGroup root,子view添加通过rInflateChildren循环找到view添加到ViewGroup

正式开始分析

  • 直接进入ActivityThread#handleResumeActivity
 @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;

        // TODO Push resumeArgs into the activity for consideration
        //这个步骤去掉nResume方法 
        //->r.activity.performResume ->
        //--> mInstrumentation.callActivityOnResume
        //这也就是onResume是页面可见了的说法了
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        if (r == null) {
            // We didn't actually resume the activity, so skipping any follow-up actions.
            return;
        }
        if (mActivitiesToBeDestroyed.containsKey(token)) {
            // Although the activity is resumed, it is going to be destroyed. So the following
            // UI operations are unnecessary and also prevents exception because its token may
            // be gone that window manager cannot recognize it. All necessary cleanup actions
            // performed below will be done while handling destruction.
            return;
        }

        final Activity a = r.activity;

        if (localLOGV) {
            Slog.v(TAG, "Resume " + r + " started activity: " + a.mStartedActivity
                    + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished);
        }

        final int forwardBit = isForward
                ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;

        // If the window hasn't yet been added to the window manager,
        // and this guy didn't finish itself or start another activity,
        // then go ahead and add the window.
        boolean willBeVisible = !a.mStartedActivity;
        if (!willBeVisible) {
            try {
                willBeVisible = ActivityTaskManager.getService().willActivityBeVisible(
                        a.getActivityToken());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        //r.window 都是在handleLaunchActivity方法中添加中进去的
        //第一次启动这个activity这个r.window就是null所以进入下面的判断进行添加
        if (r.window == null && !a.mFinished && willBeVisible) {
        //拿到phoneWindow
            r.window = r.activity.getWindow();
            //拿到phoneWindow的DecorView
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //关键在这个addview 将decorview添加到window里面去
                    //Window#通过兜兜转转找到wm就是WindowManagerImpl
                    //-》 ViewManager wm = a.getWindowManager();
                    //Activity#attach方法--》mWindowManager = mWindow.getWindowManager();
                    //Window#setWindowManager---》mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);

                    wm.addView(decor, l);
                } else {
                    //....
                }
        }

    //..
    }
  • 进入WindowManagerImpl#addView,这里又调用了WindowManagerGlobal#addView
@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    //设置默认token
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }
  • 进入WindowManagerGlobal#addView
 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        //...省略
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
           //...省略
           
          //创建ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);
            //给DecorView设置LayoutParams信息
            view.setLayoutParams(wparams);
            //保存DecorView
            mViews.add(view);
            //保存ViewRootImpl
            mRoots.add(root);
            //保存WindowManager.LayoutParams
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                //ViewRootImpl#setView
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
  • 进入ViewRootImpl#setView了,篇幅太长只保留了关键的代码requestLayout()和mWindowSession.addToDisplayAsUser
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                //..省略一堆设置过程
                // any other events from the system.
                requestLayout();
                //...省略
               try {
                    mOrigWinpatibility(mWindowAttributes);
                    //将窗口添加到WMS上面 这里打个标记留意下
                    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mDisplayCutout, inputChannel,
                            mTempInsets, mTempControls);
                    setFrame(mTmpFrame);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    inputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
                //这里给DecorView设置mParent
                view.assignParent(this);
                //...省略
                
        }
    }
}
  • ViewRootImpl#requestLayout()方法,requestLayout()这个一下子就熟悉起来了,自定义view的时候应该都用到过特别是viewgroup,实际最后调用的方法就是这里了,先丢个坑在这,后续讲解下
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
     
  • checkThread这有个经典的问题,为啥不能在子线程更新ui。
  • 这里判断了当前ViewRootImpl是不是在同一个线程执行,不是就抛出异常,而ViewRootImpl刚好是在主线程创建的#Activity#main方法创建了主线程->Looper.prepareMainLooper();,刚好handleResumeActivity创建ViewRootImpl就是在主线程,严格意义上来说,是不能不同线程更新ui。
 void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
  • ViewRootImpl#scheduleTraversals,接着分析scheduleTraversals
  • 这样也比较眼熟,网上找屏幕刷新机制都会提到这个。这个方法是屏幕刷新最重要的方法,推荐个相关文章
 @UnsupportedAppUsage
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //这里创建了一个同步屏障消息
            //[关于同步屏障](https://blog.csdn.net/start_mao/article/details/98963744)
        
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
  • 回到正文这里,这里传入了一个Runnable,所以最终会掉到这个doTraversal()
    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() {
        //代码太多了 只保留关键代码
        windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
        //...
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        //...
        performLayout(lp, mWidth, mHeight);
        //...
        performDraw()
        //...
        
    }
  • measureHierarchy 这里进行了3次预测量,当view的宽度设置为WRAP_CONTENT时会预测量2次

 private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        int childWidthMeasureSpec;
        int childHeightMeasureSpec;
        boolean windowSizeMayChange = false;

        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
                "Measuring " + host + " in display " + desiredWindowWidth
                + "x" + desiredWindowHeight + "...");

        boolean goodMeasure = false;
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            // On large screens, we don't want to allow dialogs to just
            // stretch to fill the entire width of the screen to display
            // one line of text.  First try doing the layout at a smaller
            // size to see if it will fit.
            final DisplayMetrics packageMetrics = res.getDisplayMetrics();
            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
            int baseSize = 0;
            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                baseSize = (int)mTmpValue.getDimension(packageMetrics);
            }
            if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
                    + ", desiredWindowWidth=" + desiredWindowWidth);
            if (baseSize != 0 && desiredWindowWidth > baseSize) {
                //如果给的desiredWindowWidth比得到baseSize大 那就说明尺寸不行 给一个值测量下
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                //第一次测量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                        + host.getMeasuredWidth() + "," + host.getMeasuredHeight()
                        + ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
                        + " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    goodMeasure = true;
                } else {
                    //第一次给的尺寸还不行继续测量
                    // Didn't fit in that size... try expanding a bit.
                    baseSize = (baseSize+desiredWindowWidth)/2;
                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
                            + baseSize);
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                    //第二次测量
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                            + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                        if (DEBUG_DIALOG) Log.v(mTag, "Good!");
                        goodMeasure = true;
                    }
                }
            }
        }
        
        if (!goodMeasure) {
        //如果还不满意就继续最后一次测量
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            //第三次测量
           performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                windowSizeMayChange = true;
            }
        }

        if (DBG) {
            System.out.println("======================================");
            System.out.println("performTraversals -- after measure");
            host.debug();
        }

        return windowSizeMayChange;
    }
  • 根据上面3次的测量条件可以在加上下面的一次最多测量4次 最少测量2次,按照预测的方法来说,将width不要设置成WRAP_CONTENT会快一点(手动狗头)
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
        //这里会执行mView.measure->在执行View#onMeasure()方法
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

  • 这里mView.measure实际上是DecorView,所以onMeasure的时候实际调用的是DecorView#onMeasure然后DecorView继承自FrameLayout,先通过自己宽高模式啥的的一番计算调用了 super.onMeasure(widthMeasureSpec, heightMeasureSpec);所以直接去看FrameLayout的onMeasure
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        //这里去onMeasure是有个前提的 强制绘制布局或者需要刷新布局
        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            //这里看看缓存的数量小于0或者忽略缓存(小于sdk19都会忽略缓存)
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                //真正测量
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                //如果有缓存
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                //这里会抛个异常,重写onMeasure方法需要执行setMeasuredDimension或者super.onMeasure就会抛异常,因为在setMeasuredDimension设置了mPrivateFlags,不设置这个flags就会进入这个异常
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
        //添加到缓存列表
        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }
    
//这里是FrameLayout的onMeasure方法
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();

        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();

        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
              //...通过子view的Margin去累加计算出最大宽高值
            }
        }

        //...省略计算容器宽高过程
        
        //设置容器自己的宽高
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));

        count = mMatchParentChildren.size();
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                //...省略通过子view的getLayoutParams计算宽高设定子view大小
                //通过MATCH_PARENT判断给容器的宽高或者通过getChildMeasureSpec方法去计算
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

  • 上门就是测量的过程,先预测量宽高,不管结果如何再去测量一次确定一下啊,然后循环去调用子view的测量
  • 测量看完,看看performLayout,这里思路和上面测量一样的
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mScrollMayChange = true;
        mInLayout = true;

        final View host = mView;
        if (host == null) {
            return;
        }
        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());
            //..
            }
            //..
}
  • host就是DecorView而他继承了FrameLayout,FrameLayout就是viewGroup
  • viewGroup#layout->View#layotu->View#onLayout
  • 所以最终执行的是FrameLayout#onlayout
//view#layout
 public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
            
        //这里setFrame把自己的位置摆好
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            //...
           }
           //...
    }
    
    //View#setFrame
 protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (DBG) {
            Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }

        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;

            //...
             //这里提一嘴 如果布局发生了改变就会执行invalidate触发onDraw
            // Invalidate our old position
            invalidate(sizeChanged);

          //..
        }
        return changed;
    }
    
    
//FrameLayout#onlayout
@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }
    
//FrameLayout#layoutChildren
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();

        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();

        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();
        //循环去调用了子view的layout
        for (int i = 0; i < count; i++) {
             final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
            //...省略计算Margin、pading部分
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
                }
            
        }
    }

  • 这里绘制流程和布局流程都是一个套路,看看performDraw
private void performDraw() {
 if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
            return;
        } else if (mView == null) {
            return;
        }
        //...
        try {
            //关键代码
            boolean canUseAsync = draw(fullRedrawNeeded);
            if (usingAsyncReport && !canUseAsync) {
                mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
                usingAsyncReport = false;
            }
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        //...
    }
    
//ViewRootImpl#draw
private boolean draw(boolean fullRedrawNeeded) {
        Surface surface = mSurface;
        
        //...
      
        mAttachInfo.mTreeObserver.dispatchOnDraw();

       //...
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
            //...
            //1.这里会最终会走view.onDraw
            mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
            } else {
               //2.这里会最终会走view.onDraw
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
                }
            }
        }

        if (animating) {
            mFullRedrawNeeded = true;
            scheduleTraversals();
        }
        return useAsyncReport;
    }
  • 上面ViewRootImpl#draw方法打了2个注释会走view.onDraw方法,先分析第一个
//ThreadedRenderer#draw
 void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
        final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
        choreographer.mFrameInfo.markDrawStart();
        //..重点这个 这个方法主要走了 updateViewTreeDisplayList(view)
        //
        updateRootDisplayList(view, callbacks);
    //...
    }
//省点篇幅 直接跳到updateViewTreeDisplayList
private void updateViewTreeDisplayList(View view) {
        view.mPrivateFlags |= View.PFLAG_DRAWN;
        view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
                == View.PFLAG_INVALIDATED;
        view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
        //主要看这里此时的view还是DecorView 
        view.updateDisplayListIfDirty();
        view.mRecreateDisplayList = false;
    }
//DecorView和FrameLayout以及viewgroup都没有对updateDisplayListIfDirty方法重写,所以直接去看View里面的
//View#updateDisplayListIfDirty
 public RenderNode updateDisplayListIfDirty() {
       //...
            final RecordingCanvas canvas = renderNode.beginRecording(width, height);

            try {
                if (layerType == LAYER_TYPE_SOFTWARE) {
                 //关闭了硬件加速
                 //直接把view绘制成bitmap
                    buildDrawingCache(true);
                    Bitmap cache = getDrawingCache(true);
                    if (cache != null) {
                        canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                    }
                } else {
                    computeScroll();

                    canvas.translate(-mScrollX, -mScrollY);
                    mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;

                    // Fast path for layouts with no backgrounds
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        //有SKIP_DRAW这个就是有没有设置背景色没有就直接去绘制子view
                        dispatchDraw(canvas);
                        drawAutofilledHighlight(canvas);
                        if (mOverlay != null && !mOverlay.isEmpty()) {
                            mOverlay.getOverlayView().draw(canvas);
                        }
                        if (isShowingLayoutBounds()) {
                            debugDrawFocus(canvas);
                        }
                    } else {
                        //重点
                        draw(canvas);
                    }
                }
            } finally {
                renderNode.endRecording();
                setDisplayListProperties(renderNode);
            }
        } else {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        }
        return renderNode;
    }
    
  • 去看看draw方法干啥了
 public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        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)
         *      7. If necessary, draw the default focus highlight
         */

        // Step 1, draw the background, if needed
        int saveCount;

        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
            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 (isShowingLayoutBounds()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }
      //..
    }
    
  • onDraw这个就不用说了,看下dispatchDraw怎么绘制子view的,dispatchDraw是抽象的方法所以先找下DecorView和FrameLayout有没有实现,没有找到所以直接接着找ViewGroup
@Override
    protected void dispatchDraw(Canvas canvas) {
       //..
        for (int i = 0; i < childrenCount; i++) {
            //...
              //这里循环去调用了子view的draw方法
            final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
          
                more |= drawChild(canvas, child, drawingTime);
            }
        }
     
    }
//drawChild
  protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

    
  • 上面ViewRootImpl#draw提到调用drawSoftware的方法,一样的套路
  private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
    //...
        try {
          //...
            mView.draw(canvas);

            drawAccessibilityFocusedDrawableIfNeeded(canvas);
        } finally {
        //..
           
        }
        return true;
    }
  • 到这里测量绘制的流程就完结了,setview中mWindowSession.addToDisplayAsUser就是把window添加到wms显示了。通过view.assignParent(this);给DecorView设置了父类,所以说ViewRootImpl是整个界面的爸爸。

分析下requestLayout和invalidate

当调用xxxView#requestLayout的时候会发生什么
 //View#requestLayout
 public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }
        
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;
        //重点在这
        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }
  • 看到这个重点就有一个递归的过程了,上面分析知道了ViewRootImpl是整个页面的爸爸,所以这个重点代码其实就是递归找ViewRootImpl并执行requestLayout方法了。
  • 可以抽象看成:子view找爸爸viewgroup->viewgroup(DecorView)找爸爸ViewRootImpl,在让这个最后的爸爸执行requestLayout方法
  • 至于requestLayout上面就有分析了不多重复了,有一个注意的一点的是不一定会去执行onDraw(),触发onDraw一般是因为layout过程中发现l,t,r,b和以前不一样或者动画在执行
  • requestLayout流程图


    requestLayout
当调用xxxView#invalidate的时候会发生什么
//view#invalidate
 public void invalidate() {
        invalidate(true);
    }

    
    @UnsupportedAppUsage
    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;
        }

        // Reset content capture caches
        mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
        mContentCaptureSessionCached = false;

        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)) {
           //...
            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);
            }

           //...
        }
    }
    
        //viewgroup#invalidateChild
     @Deprecated
    @Override
    public final void invalidateChild(View child, final Rect dirty) {
            //...
                    ViewParent parent = this;

//...
            do {
                View view = null;
                if (parent instanceof View) {
                    view = (View) parent;
                }

              //...重点这里
                parent = parent.invalidateChildInParent(location, dirty);
                //..
            } while (parent != null);
        }
    }
    
  • 调用了view.invalidate最终会循环调用parent.invalidateChildInParent。所以会找到页面的爸爸ViewRootImpl执行invalidateChildInParent和上面逻辑相似。至于viewgroup的invalidateChildInParent只是在设置脏区大小啥的
    //ViewRootImpl#invalidateChildInParent
  public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);
        if (dirty == null) {
            //会走scheduleTraversals
            invalidate();
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }

        //...会走scheduleTraversals
        invalidateRectOnScreen(dirty);

        return null;
    }
  • scheduleTraversals后面也不用说啥了又是doTraversal->performTraversals然后又是一通判断是否要执行measure、layout和draw
  • invalidate流程图
invalidate
  • 到此就完结了
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容