Activity布局加载流程源码解析

我们每次写Activity都必须写到setContentView(int resoursid)这个方法,写完这个我们Activity就可以把布局加载进来了。那大家有没有好奇android到底是怎么个流程把布局就这么加载进来的呢?嗯,我就好奇了,然后手贱点进去看源码了,然后……(fuck source)。[笑哭]

在进行深入学习前,我们先了解几个概念:

  • Window是一个抽象类,提供了绘制窗口的一组通用API。
  • PhoneWindow是Window的具体继承实现类。而且该类内部包含了一个DecorView对象,该DectorView对象是所有应用窗口(Activity界面)的根View。
  • DecorView是PhoneWindow的内部类,是FrameLayout的子类,是对FrameLayout进行功能的修饰(所以叫DecorXXX),是所有应用窗口的根View

我们再上个图帮助大家先有个大概的布局的层级概念:

好了,接下来我们就来Read the fucking source!

那么首先点进setContentView(int resoursid)方法里面瞅瞅,

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

可以看到什么都没有,我们看看getWindow()方法,

public Window getWindow() {
        return mWindow;
}

mWindow又是啥?mWindow是一个Window对象,再看看mWindow的赋值:

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) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        //这里赋值的!!赋值为一个PhoneWindow对象;
        mWindow = new PhoneWindow(this);
        ···
    }

从上面我们可以看出,mWindow被赋值了PhoneWindow对象,所以我们的Activity的setContentView()方法实际上调用的是PhoneWindow的setContentView(),我们马上来看看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 {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

我们分析下,第五行首先判断mContentParent是否为null,也就是第一次调用;如果是第一次调用,则调用installDecor()方法(这个方法我们待会再说),否则判断是否设置FEATURE_CONTENT_TRANSITIONS Window属性(默认false),如果没有就移除该mContentParent内所有的所有子View;
接着16行mLayoutInflater.inflate(layoutResID,mContentParent);将我们的资源文件通过LayoutInflater对象解析并转换为View树(Android是通过pull解析xml文件的),并且添加至mContentParent视图中(其中mLayoutInflater是在PhoneWindow的构造函数中得到实例对象的LayoutInflater.from(context);)。

好,我们再来看看installDecor()方法里面有什么

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            //根据窗口的风格修饰,选择对应的修饰布局文件,并且将id为content的FrameLayout赋值给mContentParent
            mContentParent = generateLayout(mDecor);
            //......
            //初始化一些属性值
        }
    }

这里我省略了一些内容。
第2到9行可以看出,首先判断mDecor对象是否为空,如果为空则调用generateDecor()创建一个DecorView对象(该类是FrameLayout子类,即一个ViewGroup视图),然后设置一些属性,我们看下PhoneWindow的generateDecor方法,如下:

    protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

可以看见generateDecor方法仅仅是new一个DecorView的实例。

回到installDecor方法继续往下看,第10行开始到方法结束都需要一个if (mContentParent == null)判断为真才会执行,当mContentParent对象为空则调用generateLayout()方法去创建mContentParent对象。所以我们看下generateLayout方法源码,如下:

    protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        TypedArray a = getWindowStyle();

        //......
        //依据主题style设置一堆值进行设置

        // Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();

        //根据设定好的features值选择不同的窗口布局文件,得到layoutResource值
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
           layoutResource = ...;
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            layoutResource = ...;
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            layoutResource = ...;
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            layoutResource = ...;
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            layoutResource = R.layout.screen_simple;
        }

        //把选中的窗口修饰布局文件添加到DecorView对象里,并且指定contentParent值
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        //......
        //继续一堆属性设置,完事返回contentParent
        return contentParent;
    }

以上我省略了部分代码,若要看详细请自行查阅源码。根据以上代码,我们可以看出,android根据不同的features值选择去加载不同的布局文件,而这些不同的布局文件(如:R.layout.screen_simple,R.layout.screen_simple_overlay_action_mode,R.layout.screen_custom_title)中你点进去看,都会看到含有id为content的view,然后android把id为content的view加载赋值给contentParent并返回。我们这里注意到还有个mContentRoot,这个view对应layoutResource布局,也即R.id.content的父布局。综上我们可以看到这里有三个层级关系:decor->mContentRoot->contentParent(包含关系)。

mDecor做为根视图将该窗口根布局layoutResource添加进去,然后把其内id为content的view返回给mContentParent对象。

好了,到这里我们差不多也读完了,最后我们再来梳理一下流程:
Activity传入一个R.layout.main给PhoneWindow,PhoneWindow创建一个DevorView,然后根据features不同加载不同的根布局赋值给mContentRoot这个ViewGroup,decorview则把mContentRoot通过addview()加入到自己里面,decorview把根布局放到自己内部,然后从根布局中加载出id为content的viewgroup,并把R.layout.main布局给放到content中。最后我们画个图理解一下。

猜你喜欢

转载自blog.csdn.net/zzzypp/article/details/78065035
今日推荐