美文网首页面试题
高级UI<第九篇>:源码分析UI的绘制流程(Android 9.

高级UI<第九篇>:源码分析UI的绘制流程(Android 9.

作者: NoBugException | 来源:发表于2019-11-24 11:24 被阅读0次

    本文其实是第七篇的续篇,第七篇主要讲解Activity的绘制流程,本文主要讲解View本身是如何绘制的,即UI的绘制流程。

    花了几个小时的源码分析,最终确认,UI实际上从Activity的onResume之后才真正开始绘制,或者说,UI绘制的入口是ActivityThread类中的handleResumeActivity方法。

    源码如下:

    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
    
            //...(省略)
    
        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();
            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);
                }
            }
            //...(省略)
    }
    

    以上代码给我们的信息如下:

    【通过Activity获取Window对象】

    r.window = r.activity.getWindow();
    

    Window对象是UI的顶级视图,一般所谓的Window就是PhoneWindow。

    【通过Window获取DecorView对象】

    View decor = r.window.getDecorView();
    

    DecorView是显示Activity的根视图,其作用是现实状态栏、显示ActionBar、显示setContentView设置的布局以及底部导航栏。

    【通过DecorView获取ViewRootImpl对象】

    ViewRootImpl impl = decor.getViewRootImpl();
    

    ViewRootImpl对象主要负责View的绘制,DecorView和ViewRootImpl 互相绑定,一个DecorView只能有一个ViewRootImpl 实例。

    【将DecorView添加到Window中】

    wm.addView(decor, l);
    

    那么,ViewRootImpl是如何绘制View的呢?

    当DecorView添加到Window中时,View就开始了绘制,那么就跟踪以下其过程吧,请看下面的跟踪流程:

    【第一步】

     wm.addView(decor, l);
    

    【第二步】执行WindowManagerImpl中的addView方法

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

    【第三步】执行WindowManagerGlobal的addView方法

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
    
            //...(省略)
    
            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);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
    

    代码中最核心代码是:

    root.setView(view, wparams, panelParentView);
    

    这里的root就是ViewRootImpl对象。

    【第四步】执行ViewRootImpl的setView方法

    /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    
                //...(省略)
    
                // 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();
    
                //...(省略)
      
    }
    

    requestLayout:在添加到窗口管理器之前安排第一个布局,以确保在从系统接收任何其他事件之前执行重新布局。

    【第五步】执行ViewRootImpl的requestLayout方法,以完成初步布局

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

    【第六步】执行ViewRootImpl的scheduleTraversals方法,向主线程发送消息请求遍历。

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

    核心代码如下:

            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    

    所以,执行TraversalRunnable的run方法

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

    【第七步】执行ViewRootImpl的doTraversal方法

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
    
            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
    
            performTraversals();
    
            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
    

    【第八步】执行ViewRootImpl的performTraversals方法,开始进入UI的绘制

    private void performTraversals() {
    
                //...(省略)
    
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
                //...(省略)
    
            performLayout(lp, mWidth, mHeight);
    
                //...(省略)
    
            performDraw();
    
                //...(省略)
    
    }
    

    最终会依次执行performMeasureperformLayoutperformDraw这三个方法。

    查看这三个方法的源码之后发现,绘制UI,其实就是分别执行了以下三个方法:

            //布局的测量
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    
            //布局的摆放
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    
            //布局的绘制
            mAttachInfo.mTreeObserver.dispatchOnDraw()
            listeners.get(i).onDraw();
    

    最终执行View的以下三个方法:

            //布局的测量
            onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            //布局的摆放
            onLayout(changed, l, t, r, b);
    
            //布局的绘制
            onDraw()
    
    总结:

    当执行到handleResumeActivity时,Activity的onResume方法被调用,然后WindowManager会将DecorView设置给ViewRootImpl,这样,DecorView就被加载到Window中了,此时界面还没有显示出来,还需要经过View的measure,layout和draw方法,才能完成View的工作流程。我们需要知道View的绘制是由ViewRoot来负责的,每一个DecorView都有一个与之关联的ViewRoot,这种关联关系是由WindowManager维护的,将DecorView和ViewRoot关联之后,ViewRootImpl的requestLayout会被调用以完成初步布局,通过scheduleTraversals方法向主线程发送消息请求遍历,最终调用ViewRootImpl的performTraversals方法,这个方法会执行View的measure、layout和draw流程。

    [本章完...]

    相关文章

      网友评论

        本文标题:高级UI<第九篇>:源码分析UI的绘制流程(Android 9.

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