View-setContentView源码解析

本文深入探讨了Android中DecorView的创建流程,从setContentView出发,解析了View的映射过程,详细阐述了DecorView的测量机制。通过跟踪ActivityThread、WindowManagerImpl、ViewRootImpl等组件,揭示了View从XML布局文件到屏幕显示的全过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

我们经常编写的xml文件用于界面显示,但在我们设置了setContentView()后,你是否知道View是这么被解析,然后被绘制显示出来。这篇文章主要从setContentView切入,去查看DecorView加载、以及measure,layout等操作,如何分发到子View。相关源码来自android Api 28

setContentView相关流程

这是来自Activity的一段代码,Activity源码中setContentView()本身做了很多事,setContentView中主要是初始化DecorView,然后设置DecorView中的ActionBar。getWindow()获取的是其唯一子类PhoneWindow,所以setContentView其实是由PhoneWindow在执行。

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

PhoneWindow

首先知道PhoneWindow是Window的唯一继承类;而每一个Activity都需要一个窗口去承载layout。那么具体的很多事就都由PhoneWindow来完成

setContentView

首先DecorView分为两部分一个是上面的ActionBar,还有一个就是ContentParent,ContentParent是一个ViewGroup,主要作为我们设置layout的容器。他们两个具体的创建位置就在PhoneWindow中。
下面代码来自PhoneWindow,其中关键就两步,

  1. 在mContentParent==null的时候、创建mContentParent和DecorView
  2. 将我们设置的layout添加到DecorView的mContentParent中

接下来我们继续看比较关键的installDecormLayoutInflater.inflate方法。、
installDecor主要用于初始化DecorView和ContentParent,而mLayoutInflater.inflate主要用于将添加到ContentParent的layout映射成View,并添加到他们的父View中,也就是为什么我们能在ViewGroup中使用getChildView(i)


    @Override
    public void setContentView(int layoutResID) {
        //onCreate方法一般情况下只会走一次,在第一次进入的时候mContentParent==null,那么要这时候初始化DecorView,
        if (mContentParent == null) {
            //初始化DecorView
            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 {
            //  将Activity的视图添加到DecorView的mContentParent中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
     ...
    }
installDecor

下面三段代码很好理解。在installDecor中,如果mDecor == null,在generateDecor()方法中New 一个DecorView;然后判断如果 mContentParent==null,为空则在generateLayout()方法中根据R.id.content 去findViewById创建一个ViewGroup,这个ViewGroup就是mContentParent 。到这里DecorView的创建就完成了,接下来就是将layout中的View映射出来,并添加到父View

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content

  private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
          ...
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ...
                }
            }
        }
    }

	protected DecorView generateDecor(int featureId) {
        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());
    }

 
    protected ViewGroup generateLayout(DecorView decor) {
	...
  ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
  	...
}
	
mLayoutInflater.inflate

LayoutInflater.inflate其实我们并不陌生、但如果不懂知道它做了什么,可能就不明白在viewGroup.getChildView(i),其中的子View是怎么来的。

res.getLayout(resource)是将LayoutRes转换成XmlResourceParser,
XmlResourceParser是一个动态解析XML文件的工具,它将layout布局上的每一个节点进行分类,标记节点的深度,元素的名称空间URI,元素的开始和结束等,具体看官方文档

下面代码来自LayoutInflater:
主要步骤有三步

  1. 找到ContentView中的根布局的名字,根据name创建根View: temp
  2. 调用rInflateChildren()->rInflate()方法,遍历temp将ContentView中的xml映射成View
  3. 映射完成后将ContentView中的根布局添加到ContentParent中,而具体的映射过程在rInflate(...)中完成
public abstract class LayoutInflater {

...
	 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
 
...
	 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        ...
        //将LayoutRes转换成XmlResourceParser
          final XmlResourceParser parser = res.getLayout(resource);
            return inflate(parser, root, attachToRoot);
        ...
    }
    
...


	public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
	
        synchronized (mConstructorArgs) {
            	...
            View result = root;
            try {
                ...
                //1 获取layout中根布局的名字
                final Sting name = parser.getName();
     			//判断xml节点是否使用的merge标签,将其中布局进行解析
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    //2 Temp is the root view that was found in the xml
                    //根据找到layout中的根布局名字,根据name创建layout中的根布局
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;
					...
					
                    if (root != null) {
                        params = root.generateLayoutParams(attrs);
                    }
                    ...
                    //关键:深度遍历layout,将其中的View进行解析
                    rInflateChildren(parser, temp, attrs, true);
                    
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                    	//关键:::为ContentParent添加layout生产的View:temp
                        root.addView(temp, params);
                    }

                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                final InflateException ie = new InflateException(e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (Exception e) {
                final InflateException ie = new InflateException(parser.getPositionDescription()
                        + ": " + e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            return result;
        }
    }
...
rInflate(…)

具体的映射过程分3步:

  1. 外层是一个 while循环,将根节点下的子节点进行遍历
  2. 得到节点名称,根据节点名称调用createViewFromTag()创建View;得到View后让View执行这个映射1、2、3的过程
  3. 将得到的View添加到父布局

最后调用View的onFinishInflate()方法,提示layout映射完成这里要明白,为什么是这样的一个映射过程,其实就是循环遍历,特别是在第2点上,当创建的View是一个容器类ViewGroup时,这个时候是需要再遍历ViewGroup中的子View,所以会重复1、2、3的过程

之前看DecorView的onMeasure过程的时候不明白,子View是什么时候添加到ViewGroup的,看到这里就明白了。其实所有的子View添加到ViewGroup都是在LayoutInflater中映射的时候完成。并且ViewGroup提供了很多对View的添加、删除,根据index操作View的方法。

...
	final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
...
	void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }
			//得到节点名称
            final String name = parser.getName();
            if (TAG_REQUEST_FOCUS.equals(name)) {
             //如果该节点为requestFocus
                pendingRequestFocus = true;
                consumeChildElements(parser); 
            } else if (TAG_TAG.equals(name)) {
            //如果该节点为tag
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
             //如果该节点为include标签
                throw new InflateException("<merge /> must be the root element");
            } else {
            	//根据name创建View
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                //如果当前view是一个容器,那么再做进一步的映射
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }  
...      

}
initWindowDecorActionBar()

让我们再回到setContentView里面,在上面我们成功创建了DecorView,并将layout里面的View添加到了ContentParent里面,DecorView就完成了一部分,另一部分就是ActionBar。下面就是WindowDecorActionBar的创建过程,具体可以看WindowDecorActionBar的构造方法。

	   private void initWindowDecorActionBar() {
        Window window = getWindow();
        window.getDecorView();

        if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
            return;
        }

        mActionBar = new WindowDecorActionBar(this);
        mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);

        mWindow.setDefaultIcon(mActivityInfo.getIconResource());
        mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
    }

到这里我们可以得到一张关于DecorView的图:
当我们启动一个Activity的同时,会创建一个window的子类PhoneWindow;在Activity的onCreate中设置layout的时候,会在PhoneWindow中创建DecorView并映射layout布局文件。
在这里插入图片描述
图片来自网络

DecorView的测量与绘制

上面部分代码介绍了decorView的创建以及layout映射成View的过程,整个过程在onCreate中完成。但我们并没有看到对View进行测量等操作,并且我们知道在onResume之前我们是获取不到View的宽高的,所以带着问题我们去看看Activity生命周期中onResume方法。在ActivityThread中有调用生命周期的方法handleResumeActivity(),并且在其中发现了关于测量View的关键代码wm.addView(decor, l)

ActivityThread

performResumeActivity()就是调用了Activity生命周期方法onResume。在调用onResume()后调用wm.addView(decor, l),关键逻辑就在addView中。

在这个过程中涉及到其他几个类ViewManagerWindowManagerWindowManagerImpl,
WindowManagerImpl实现接口WindowManager,WindowManager继承接口ViewManager

  • ViewManager:只有三个方法addView、updateViewLayout、removeView,主要用于向Activity添加和删除子视图的界面
  • WindowManager:通过Binder与WindowManagerService进行跨进程通信,把具体的实现工作交给WindowManagerService来完成
 public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
...
	 final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, 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;
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }
        } 
...

}

WindowManagerImpl

WindowManager和ViewManager的实现类,负责Window的添加、更新和删除操作。通过WindowManagerGlobal与WMS通信,这里将addView交给WindowManagerGlobal

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

WindowManagerGlobal

WindowManagerGlobal主要用于管理ViewRoot、创建并提供IWindowManager的proxy实例,在这里会创建ViewRootImpl类,由ViewRootImpl控制View的测量和绘制。

 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
      ...
        
        ViewRootImpl root;
        View panelParentView = null;
             
            root = new ViewRootImpl(view.getContext(), display);
			//为DecorView设置LayoutParams
            view.setLayoutParams(wparams);

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

            try {
            	//关键地方,调用ViewRootImpl的方法
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

ViewRootImpl

ViewRootImpl是View的根,它控制了View的测量和绘制,同时持有WindowSession通过Binder与WMS通信,同时持有IWindow作为WSM的回调接口,用于例如touch事件的回调
在ViewRootImpl中经过一系列的方法调用,最后关键代码在performTraversals()中,performTraversals里有三个方法performMeasure()performLayout()performDraw(),接下来我们具体看关键方法performTraversals()

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
                requestLayout();
...
}
  @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            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);
   final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
performTraversals

在performTraversals里面看到有调用两次performMeasure:在第一次 performMeasure() 方法调用后, 如果子View 需要的空间大于父容器为它测量的大小,那么对应的 verticalWeight 和 horizontalWeight 将会大于0,即这两个字段分别对应垂直和水平的情况下子 View 需要的额外空间。这时候会将 measureAgain 设置为 true, 并且开始第二次测量。
下面是measure、layout、draw的调用流程:

ViewRootImpl.performMeasure —> View.measure —> DecorView.onMeasure

ViewRootImpl.performLayout—>View.layout—>DecorView.onLayout

ViewRootImpl.performDraw—>ViewRootImpl.draw—>ThreadedRenderer.draw—>ThreadedRenderer.updateRootDisplayList—>ThreadedRenderer.updateViewTreeDIsplayList—>View.updateDisplayListIfDirty—>PhoneWindow$DecorView.draw—>View.draw—>ViewGroup.dispatchDraw—>ViewGroup.drawChild—>View.draw

并且在这里面我们看到了getRootMeasureSpec方法返回childWidthMeasureSpec,这个就是获取根视图的测量规范,我们可以看到这是用于对子View的宽度的约束,约束的子View就是DecorView。在performMeasure中调用mView.measure(childWidthMeasureSpec, childHeightMeasureSpec)开始测量,这里的mView就是DecorView。相关的performLayout、performDraw也是如此。
到这里就开始了DecorView的测量,并且会从DecorView开始从上往下开始对子View进行测量。那么我们接下来看看这个流程是怎么传递下去的

 private void performTraversals() {
 ...
  if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    //获取父DecorView的宽高MeasureSpec
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
                    
                     // Ask host how big it wants to be
                     //对DecorView进行测量
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Implementation of weights from WindowManager.LayoutParams
                    // We just grow the dimensions as needed and re-measure if
                    // needs be
                    int width = host.getMeasuredWidth();
                    int height = host.getMeasuredHeight();
                    boolean measureAgain = false;
   				    //在第一次 performMeasure() 方法调用后, 如果子View 需要的空间大于父容器为它测量的大小,那么对应的 verticalWeight 和 horizontalWeight 将会大于0,
   				    //即这两个字段分别对应垂直和水平的情况下子 View 需要的额外空间。这时候会将 measureAgain 设置为 true, 并且开始第二次测量


                    if (lp.horizontalWeight > 0.0f) {
                        width += (int) ((mWidth - width) * lp.horizontalWeight);
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
					
                    if (lp.verticalWeight > 0.0f) {
                        height += (int) ((mHeight - height) * lp.verticalWeight);
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                                MeasureSpec.EXACTLY);
                        measureAgain = true;
                    }
					//重新进行测量
                    if (measureAgain) {
                        if (DEBUG_LAYOUT) Log.v(mTag,
                                "And hey let's measure once more: width=" + width
                                + " height=" + height);
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }

                    layoutRequested = true;
                }
            }
	...
            performLayout(lp, mWidth, mHeight);
	...
            performDraw();             
...
}                    

DecorView开始测量,调用measure方法,measure方法是一个静态方法不能重写,只有View类有这个方法。

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

调用onMeasure()方法,这个方法DecorView有重写,那么会走DecorView的onMeasure()

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
...
	 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
	...
	 onMeasure(widthMeasureSpec, heightMeasureSpec);
	...
	}
...
}

看下面DecorView中onMeasure()代码,在前面我们调用int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); 为DecorView生成过MeasureSpec,当MeasureSpec为AT_MOST模式的时候就对原来的widthMeasureSpec和heightMeasureSpec进行转换成精确模式EXACTLY,重新生成新的MeasureSpec,并调用其父类的FrameLayout的onMeasure()。
对于MeasureSpec的讲解在下一章View的Measure中会讲到。


public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {

	 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
        final boolean isPortrait =
 ...
 //如果不是EXACTLY的,这里还是要获取下视图的大小,让测量规格是EXACTLY
 
        if (widthMode == AT_MOST) {
            final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor : mWindow.mFixedWidthMajor;
            if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
                final int w;
                if (tvw.type == TypedValue.TYPE_DIMENSION) {
                    w = (int) tvw.getDimension(metrics);
                } else if (tvw.type == TypedValue.TYPE_FRACTION) {
                    w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
                } else {
                    w = 0;
                }
                if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed width: " + w);
                final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
                if (w > 0) {
                // 这里重新设置了测量规格
                // 大小是performMeasure中给出的大小和自己获取的大小的最小值
                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            Math.min(w, widthSize), EXACTLY);
                    fixedWidth = true;
                } else {
                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            widthSize - mFloatingInsets.left - mFloatingInsets.right,
                            AT_MOST);
                    mApplyFloatingHorizontalInsets = true;
                }
            }
        }

        mApplyFloatingVerticalInsets = false;
        if (heightMode == AT_MOST) {
            final TypedValue tvh = isPortrait ? mWindow.mFixedHeightMajor
                    : mWindow.mFixedHeightMinor;
            if (tvh != null && tvh.type != TypedValue.TYPE_NULL) {
                final int h;
                if (tvh.type == TypedValue.TYPE_DIMENSION) {
                    h = (int) tvh.getDimension(metrics);
                } else if (tvh.type == TypedValue.TYPE_FRACTION) {
                    h = (int) tvh.getFraction(metrics.heightPixels, metrics.heightPixels);
                } else {
                    h = 0;
                }
                if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed height: " + h);
                final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
                if (h > 0) {
                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            Math.min(h, heightSize), EXACTLY);
                } else if ((mWindow.getAttributes().flags & FLAG_LAYOUT_IN_SCREEN) == 0) {
                    heightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            heightSize - mFloatingInsets.top - mFloatingInsets.bottom, AT_MOST);
                    mApplyFloatingVerticalInsets = true;
                }
            }
        }

        getOutsets(mOutsets);
        if (mOutsets.top > 0 || mOutsets.bottom > 0) {
            int mode = MeasureSpec.getMode(heightMeasureSpec);
            if (mode != MeasureSpec.UNSPECIFIED) {
                int height = MeasureSpec.getSize(heightMeasureSpec);
                heightMeasureSpec = MeasureSpec.makeMeasureSpec(
                        height + mOutsets.top + mOutsets.bottom, mode);
            }
        }
        if (mOutsets.left > 0 || mOutsets.right > 0) {
            int mode = MeasureSpec.getMode(widthMeasureSpec);
            if (mode != MeasureSpec.UNSPECIFIED) {
                int width = MeasureSpec.getSize(widthMeasureSpec);
                widthMeasureSpec = MeasureSpec.makeMeasureSpec(
                        width + mOutsets.left + mOutsets.right, mode);
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);       
...

	}

}
FrameLayout

在FrameLayout中才根据子类的相关信息以及
新生成新的MeasureSpec确定了DecorView的宽高值。同时遍历子View,为子View生成childWidthMeasureSpec和childHeightMeasureSpec。最后调用子View的测量child.measure(childWidthMeasureSpec, childHeightMeasureSpec);到这里DecorView的测量以及子View的MeasureSpec都已经生成。到后面不管ChildView是什么,都能在onMeasure中得到了布局约束,然后在布局约束指导下进行测量。

	
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        //如果宽高中有一个不是精确模式,那么就返回true;如果是DecorView传递过来的,必定都是精确模式,为false
        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) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                //这里如果是DecorView过来的,那么并不会走下面方法,只有当使用FrameLayout,并且宽高中使用了warp_content,但子布局为MATCH_PARENT下面才会执行
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        // Account for padding too
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

        // Check against our foreground's minimum height and width
        final Drawable drawable = getForeground();
        if (drawable != null) {
            maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
            maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
        }


        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));

		//计算childWidthMeasureSpec和childHeightMeasureSpec并调用child.measure
        count = mMatchParentChildren.size();
        if (count > 1) {
            for (int i = 0; i < count; i++) {
                final View child = mMatchParentChildren.get(i);
                final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

                final int childWidthMeasureSpec;
                if (lp.width == LayoutParams.MATCH_PARENT) {
                    final int width = Math.max(0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                            width, MeasureSpec.EXACTLY);
                } else {
                    childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                            getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                            lp.width);
                }

                final int childHeightMeasureSpec;
                if (lp.height == LayoutParams.MATCH_PARENT) {
                    final int height = Math.max(0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                            height, MeasureSpec.EXACTLY);
                } else {
                    childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                            getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                            lp.height);
                }

                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
    }

到这里我们就讲解完了DecorView的创建和测量过程,将关键点和方法做一个小节

  1. PhoneWindow在PhoneWindow的setContentView方法中创建
  2. View映射过程是在LayoutInflater的rInflate()方法中,映射完成后将View添加到DecorView的ContentParent里
  3. DecorView开始测量的关键代码在ActivityThread的performResumeActivity()中,调用的ViewManager.addView(decor, l)
  4. 最后开始测量是在ViewRootImpl的performTraversals()里,,performTraversals中有三个方法performMeasure()、performLayout()、performDraw()
  5. DecorView继承FrameLayout,在FrameLayout的onMeasure()中调用measureChildWithMargins()对子View进行测量。

View测量机制详解—从DecorView说起
ActivityThread的理解和APP的启动过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值