View的绘制流程

面试中必问的问题说一下View的绘制流程,有时候说着总是说着忘记了,然后告知面试官有的我忘记了,面试官不计较也就过去了,但是要说的大概清楚,今晚总结一下,画个个图,然后分析Activity的布局绘制。

  •       先上图吧:View的绘制流程_第1张图片

 

  • 接下来我们进入继承Activity的类中:看到setContentView()的方法中(注意:继承AppCompatActivity的setContentView()方法里面的实现是有区别的,后续我会继续总结出AppCompatActivity的绘制)看下代码:
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();只有两个方法,再次进入setContentView()方法,我们看到的是进入了Window中的setContentView()中,这是一个接口类,找到他的实现类:PhoneWindow()
  • PhoneWindow中的SetcontentView()的如下:
    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // 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)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
  • 这个时候,我们进入到installDector()方法中:这个方法里面代码太多,我们捡重要看:mDecor = generateDecor(-1);看看这个方法里面的具体实现:
    protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }               创建一个DectorView对象
  • 接下来会创建一个ViewGroup对象:mContentParent = generateLayout(mDecor);这个里面的代码比较多,读者需要自行进入查看一下,贴出有凑字数的嫌疑
  • 我们注意看到DectorView是继承FrameLayout会提供一个占位坑,最终的布局会扔给ViewGroup去处理
  • 这下我们走完了PhoneView中的installDector()的方法
  • 接下去走了:mLayoutInflater.inflate(layoutResID, mContentParent);
  • 随后我们通过之前创建的ViewGroup,调用他的mContentParent.requestApplyInsets();我们进入这个方法中,发现之后走的是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中会调用:
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();//检查绘制的线程是否是UI线程,这就是为什么子线程不能更新UI的来源
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
  • 接下来我们会进入scheduleTraversals():
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
  • 我们进入:mTraversalRunnable中发现它执行的是: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(); 这个里面的代码超级多,我们不需要全部看完:注意到三个调用了三个方法:
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //测量 
    performLayout(lp, mWidth, mHeight);//排版 
    performDraw();//绘制
  • 这个走完后,View就能显示在屏幕上了,里面一些细节大家需要自己走一遍源码,这个只是自己的一些总结,比不上一些大佬的博客,如果有错误,欢迎指正。

你可能感兴趣的:(移动开发)