一口气看完View的绘制流程

先构建一个全局的函数调用链

首先看一下整个调用链:
涉及的主要类有:Activity、PhoneWindow、LayoutInflate、ViewGroup、View、
ViewRootImpl。

Activity.onCreate->Activity.setContentView->PhoneWindow.setContentView->LayoutInflate.inflate->ViewGroup.addView->ViewGroup.addViewInner->View.setLayoutParams->View.requestLayout->
ActivityThread.handleResumeActivity
ViewRootImpl.requestLayout->ViewRootImpl.scheduleTraversals->ViewRootImpl.doTraversal->ViewRootImpl.performTraversals->ViewRootImpl.measureHierarchy->ViewRootImpl.performMeasure->View.measure->View.onMeasure

1.先从Activity开始

当我们创建Activity时,会重写onCreate方法,然后调用setContentView方法,下面看一下Activity的setContentView方法:

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

可以看到我们传进来的layoutResID最终传进 getWindow().setContentView(layoutResID);内

2.PhoneWindow

这里getWindow()获取的window就是PhoneWindow,下面看一下getWindow()方法:

    public Window getWindow() {
        return mWindow;
    }

可以看到返回了mWindow,那么mWindow在哪初始化了?我们点击mWindow,可以在Activity的onAttach方法中看到:

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
		// 这里初始化了PhoneWindow
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        // 省略部分代码
        }

然后我们看一下PhoneWindow的setContentView方法:

    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
        	// 这里调用了LayoutInflater的inflate方法填充布局
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

3. LayoutInflater

上面看到调用了mLayoutInflater.inflate(layoutResID, mContentParent);看一下inflate方法:

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                  + Integer.toHexString(resource) + ")");
        }
		// 这里调用了tryInflatePrecompiled
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

注意tryInflatePrecompiled方法,接受四个参数:
resource:就是layoutResID
res:是Resource
root:是ViewGroup
attachToRoot:当前为true

    private @Nullable
    View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
        boolean attachToRoot) {
        if (!mUseCompiledView) {
            return null;
        }

        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");

        // Try to inflate using a precompiled layout.
        String pkg = res.getResourcePackageName(resource);
        String layout = res.getResourceEntryName(resource);

        try {
            Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
            Method inflater = clazz.getMethod(layout, Context.class, int.class);
            // 这里通过反射创建了我们xml的view
            View view = (View) inflater.invoke(null, mContext, resource);

            if (view != null && root != null) {
                // We were able to use the precompiled inflater, but now we need to do some work to
                // attach the view to the root correctly.
                XmlResourceParser parser = res.getLayout(resource);
                try {
                    AttributeSet attrs = Xml.asAttributeSet(parser);
                    advanceToRootNode(parser);
                    ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
					// 这里attachToRoot为true
                    if (attachToRoot) {
                        root.addView(view, params);
                    } else {
                        view.setLayoutParams(params);
                    }
                } finally {
                    parser.close();
                }
            }

            return view;
        } catch (Throwable e) {
            if (DEBUG) {
                Log.e(TAG, "Failed to use precompiled view", e);
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        return null;
    }

可以看到调用了root.addView(view, params);方法,这里的root是ViewGroup

4.ViewGroup

来看一下ViewGroup的addView方法:

    public void addView(View child, int index, LayoutParams params) {
        if (DBG) {
            System.out.println(this + " addView");
        }

        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }

        // addViewInner() will call child.requestLayout() when setting the new LayoutParams
        // therefore, we call requestLayout() on ourselves before, so that the child's request
        // will be blocked at our level
        requestLayout();
        invalidate(true);
        // 注意这里,调用了addViewInner方法
        addViewInner(child, index, params, false);
    }

上面注释很清楚,说的是,addViewInner将调用chid.requestLayout(),当设置新的LayoutParams,然而,我们在这之前调用requestLayout方法,所以child的请求将在我们这个级别阻塞。大概意思是,我们在调用addViewInner之前调用了requestLayout方法,那么child调用requestLayout方法设置新的LayoutParams将不起作用。
先看addViewInner方法:

    private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        if (mTransition != null) {
            // Don't prevent other add transitions from completing, but cancel remove
            // transitions to let them complete the process before we add to the container
            mTransition.cancel(LayoutTransition.DISAPPEARING);
        }

        if (child.getParent() != null) {
            throw new IllegalStateException("The specified child already has a parent. " +
                    "You must call removeView() on the child's parent first.");
        }

        if (mTransition != null) {
            mTransition.addChild(this, child);
        }

        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }
		// 这里preventRequestLayout=false
        if (preventRequestLayout) {
            child.mLayoutParams = params;
        } else {
            child.setLayoutParams(params);
        }

因为preventRequestLayout=false,所以会调用 child.setLayoutParams(params);方法,我们看一下child调用setLayoutParams方法做了什么:

5.View

这里走到了View类里面的setLayoutParams方法里面

    public void setLayoutParams(ViewGroup.LayoutParams params) {
        if (params == null) {
            throw new NullPointerException("Layout parameters cannot be null");
        }
        mLayoutParams = params;
        resolveLayoutParams();
        if (mParent instanceof ViewGroup) {
            ((ViewGroup) mParent).onSetLayoutParams(this, params);
        }
        requestLayout();
    }

可以看到child的mLayoutParams属性被赋值了,然后调用了判断mParent是否是ViewGroup,最终都会调用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;
        }
    }

这里我的理解是因为我们在ViewGroup中调用addViewInner之前调用了requestLayout,所以当child再次调用requestLayout的时,mParent.isLayoutRequested=true,所以不会再走mParent.requestLayout方法。

   if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }

那什么时候requestLayout了呢,因为此时Activity还处于onCreate生命周期,布局并没有显示在屏幕上,当Activity处于onResume生命周期时,才会对View进行绘制。所以我们看一下Activity什么时候调用了onResume,答案是在ActivityThread调用handleResumeActivity方法时,看关键代码:

6.ActivityThread

        if (r.window == null && !a.mFinished && willBeVisible) {
        	// 获取PhoneWindow赋值给r.window
            r.window = r.activity.getWindow();
            // 获取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;
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
        } 

主要看这段代码:

            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }

a.mVisibleFromClient和a.mWindowAdded都是Activity的属性,一个默认为true,一个默认为false,所以一定会走wm.addView方法,wm是什么?
wm是Activity在attach方法调用时,被赋值的WindowManagerService,最终调用了WindowManagerService的实现类WindowManagerServiceImp的addView方法,下面看一下该方法:

7.WindowManagerServiceImp

    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

可以看到最终调用了mGlobal.addView方法,mGlobal就是WindowManagerGlobal,它是一个以单例形式创建在WindowManagerServiceImpl中的,看一下它的addView方法:

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }

可以看到,在这创建了ViewRootImpl,并把view设置到root当中,快看一下ViewRootImpl的setView方法:

8.ViewRootImpl

                if (panelParentView != null) {
                    mAttachInfo.mPanelParentWindowToken
                            = panelParentView.getApplicationWindowToken();
                }
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
                InputChannel inputChannel = null;
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    inputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;

关键代码,调用了自己的requestLayout方法,看一下ViewRootImpl的requestLayout方法:

    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

看到里面调用了scheduleTraversals方法:

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

先通过hander发送了一个同步屏障,然后调用了mTraversalRunnable,

    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

执行了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方法:

            // Ask host how big it wants to be
            windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);

方法太长,看关键代码,调用了measureHierarchy方法:

  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

方法太长,里面调用了performMeasure方法,将childWidthMeasureSpec, childHeightMeasureSpec传递了进去:

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

最后调用了View.measure方法,然后里面调用了onMeasure方法,用来测量view的宽高,最后调用setMeasuredDimension方法确定了view的宽高。
最后,在measureHierarchy方法中,调用了performMeasure方法之后,也调用了performLayout方法和performDraw方法,确定了view的位置,以及将view绘制在了屏幕上。
以上就是View的绘制流程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值