从setContentView说起

  从我们学习Android开发的第一天开始,我们就知道在Activity#onCreate里调用setContentView,Activity就会根据XML布局文件来显示。那么setContentView这个方法里究竟做了什么事情,隐藏了什么秘密,这篇文章带你一探究竟。

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

  首先看一下setContentView方法。这个方法很简单,主要是调用了getWindow方法,获取mWindow对象,然后调用了它的setContentView方法。mWindow对象是attach方法中创建出来的PhoneWindow实例。接下来分析PhoneWindow#setContentView方法。

96    @Override
397    public void setContentView(int layoutResID) {
398        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
399        // decor, when theme attributes and the like are crystalized. Do not check the feature
400        // before this happens.
401        if (mContentParent == null) {
402            installDecor();//1
403        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
404            mContentParent.removeAllViews();
405        }
406
407        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
408            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
409                    getContext());
410            transitionTo(newScene);
411        } else {
412            mLayoutInflater.inflate(layoutResID, mContentParent);//2
413        }
414        mContentParent.requestApplyInsets();
415        final Callback cb = getCallback();
416        if (cb != null && !isDestroyed()) {
417            cb.onContentChanged();
418        }
419        mContentParentExplicitlySet = true;
420    }

  从①号代码出可以看出,当mContentParent为空时则调用installDecor方法。mContentParent是个ViewGroup对象,从②代码可以看出mContentParent就是我们设置的布局的父布局。

2614    private void installDecor() {
2615        mForceDecorInstall = false;
2616        if (mDecor == null) {
2617            mDecor = generateDecor(-1);//1
2618            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
2619            mDecor.setIsRootNamespace(true);
2620            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
2621                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
2622            }
2623        } else {
2624            mDecor.setWindow(this);//2
2625        }
2626        if (mContentParent == null) {
2627            mContentParent = generateLayout(mDecor);//3
                //省略代码
2747        }
2748    }

  以上是installDecor方法的部分代码,其中省略了一些初始化工作代码。从①号代码看到,如果mDecor对象为空则调用generateDecor方法创建DecorView,否则把当前类的引用传递给DecorView。DecorView是一个继承自FrameLayout的ViewGroup,包括title和content两部分,而我们设置的布局则是content里的子元素。从③号代码可以看到,调用generateLayout来给mContentParent赋值。generateLayout方法太长,在这不再贴出。主要是根据主题样式来设置DecorView的风格,然后为DecorView添加子view,这个子view就是mContentParent。
  到目前为止,通过调用了setContentView,创建出了DecorView对象,并且把我们的布局文件添加至DecorView中成为子view。但是view没有经过测量、布局和绘制流程的话,是无法显示出来的。所以为了将DecorView显示出来,在ActivityThread#handleResumeActivity方法中,会把DecorView add到Window当中。WindowManager是抽象类,实现类是WindowManagerImpl。在WindowManagerImpl#addView方法中,又调用了WindowManagerGlobal#addView方法。

263    public void addView(View view, ViewGroup.LayoutParams params,
264            Display display, Window parentWindow) {
265        //省略代码
289        ViewRootImpl root;
290        View panelParentView = null;
291
292        synchronized (mLock) {
293            // Start watching for system property changes.
294            //省略代码
331            root = new ViewRootImpl(view.getContext(), display);
333            view.setLayoutParams(wparams);
334
335            mViews.add(view);
336            mRoots.add(root);
337            mParams.add(wparams);
338        }
339
340        // do this last because it fires off messages to start doing things
341        try {
342            root.setView(view, wparams, panelParentView);
343        } catch (RuntimeException e) {
344            // BadTokenException or InvalidDisplayException, clean up.
345            synchronized (mLock) {
346                final int index = findViewLocked(view, false);
347                if (index >= 0) {
348                    removeViewLocked(index, true);
349                }
350            }
351            throw e;
352        }
353    }

  以上是addView源码,其中省略掉了部分逻辑。可以看到addView方法的关键在于根据传递进来的参数view,生成一个ViewRootImpl实例,保存在mRoot数组中,并将view保存在mView数组中,最后调用ViewRootImpl#setView方法,最终完成了将DecorView添加至Window的工作。最后WindowManagerService调用ViewRootImpl#performTraversals方法来开始View的测量、布局和绘制三大流程,从而最终将View显示出来。
  以上是我们在Activity调用了setContentView之后发生的一系列的事的简单总结。总结起来就是:调用了setContentView之后,系统会创建出一个DecorView,并且将我们设置的布局文件添加成为子元素。之后,在ActivityThread#handleResumeActivity方法中,系统将DecorView添加至Window当中,之后,系统在调用ViewRootImpl#performTraversals完成对View的测量、布局和绘制三大流程之后,View就会显示在屏幕上。

你可能感兴趣的:(Android,android,源码分析,View)