setContentView源码分析

转载请注明出处:http://blog.csdn.net/fishle123/article/details/50812266

在Activity中,常常第一件事就是在onCreate里面调用setContentView来设置布局。大家都知道setContentView用来设置Activity的布局,但是有没有研究过setContentView究竟做了哪些事呢?本文就一起看看setContentView是如何加载我们的布局的。看完之后大家就会知道为什么使用requestWindowFeature设置窗口风格的时候一定要在setContentView之前调用。

1 Window相关的基础知识

在Android里面,Activity的内容就是一个Window,Dialog和Toast也都是通过Window来展示的。在实际使用当中并不能直接访问Window,需要通过WindowManager才能访问到Window,WindowManager提供了addView、updateViewLayout和removeView三个方法来管理Window中的View。Window类是一个抽象类,它唯一的实现是PhoneWindow。在PhoneWindow里面有个属性mDecor,它的类型是DecorView,DecorView是PhoneWindow的内部类。DecorView是FrameLayout的子类,包含整个PhoneWindow所有要显示的View,包括系统的状态栏、标题栏、以及Activity的布局等,下面通过一张图来说明它们之间的关系,这张图展示了Activity的默认视图结构。

setContentView源码分析_第1张图片 

从这张图可以很清楚看到,Android中看到的视图都是通过Window来呈现,具体地是通过PhoneWindow来管理的。需要说明的是不同样式风格的Window内部视图结构可能会有所不同,比如有的Window没有TitleBar,但是mContentParent是一定要有的。DecorView对应着Widnow要呈现的内容。我们通过setContentView设置的其实是mParentConent的子View,mParentContent对应的id是com.android.internal.R.id.content,因此使用setContentView就非常贴切了。

2 setContentView源码分析

在onCreate里面调用的是Activity的setContentView,setContentView有三个重载版本,它们的逻辑都是差不多的,这里就看一下setContentView(int layoutResID)的源码:

1)Activity的setContentView最终是调用Window的setContentView

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

这里可以看到,Activity其实是调用Window的setContentView来加载布局的。

2)初始化DecorView--根据Window的样式风格选定布局并加载,然后把Activity的布局添加到DecorView中

上面有提到Window类是一个抽象类,唯一实现了Window的是PhoneWindow。下面就是看一下PhoneWindow的setContentView:

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

首次调用setContentView的时候,mContentParent为null,需要调用installDecor来初始化mDecor。然后在第20行调用mLayoutInflater.inflate(layoutResID, mContentParent)将我们指定的布局添加到mDecor即添加到DecorView中。从这里我们可以看到mContentParent就是setContentView(layoutResID)中layoutResID的父视图,后面会看到mContentParent是DecorView的一部分。这里看一下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) {
        mContentParent = generateLayout(mDecor);
    }
    //............................省略部分代码
}

installDecor的代码很长,这里只挑重要的看:首次调用installDecor的时候mDecor和mContentParent都为null,需要调用generateDecor和genreateLayout。我们先看generateDecor方法,从名字来看generateDecor里面似乎会构造一个DecorView。

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

实际上,在generateDecor里面非常简单,直接new了一个DecorView。再看一下DecorView的构造函数。

public DecorView(Context context, int featureId) {
    super(context);
    mFeatureId = featureId;

    mShowInterpolator = AnimationUtils.loadInterpolator(context,
            android.R.interpolator.linear_out_slow_in);
    mHideInterpolator = AnimationUtils.loadInterpolator(context,
            android.R.interpolator.fast_out_linear_in);

    mBarEnterExitDuration = context.getResources().getInteger(
            R.integer.dock_enter_exit_duration);
}

DecorView的构造函数只作了一些简单的初始化,这个时候DecorView还是空的。所以这个时候mParentContent还是null,因此在installDecor方法中调用完generateDecor创建mDecor后,会继续调用genreateLayout来往DecorView中添加内容。genrateLayout会根据我们设置的窗口样式来初始化DecorView。

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

    //先获取Window的风格样式
    TypedArray a = getWindowStyle();
    ...................................
    //下面的这段代码都是获取Window详细的样式设置,并赋值给相应的feature
    //是否为悬浮框
    mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
    int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
            & (~getForcedWindowFlags());
    if (mIsFloating) {
        setLayout(WRAP_CONTENT, WRAP_CONTENT);
        setFlags(0, flagsToUpdate);
    } else {
        setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
    }
    //是否添加标题栏
    if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
        requestFeature(FEATURE_NO_TITLE);
    } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
        // Don't allow an action bar if there is no title.
        requestFeature(FEATURE_ACTION_BAR);
    }
    //沉浸式状态栏
    if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
        requestFeature(FEATURE_ACTION_BAR_OVERLAY);
    }

    if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
        requestFeature(FEATURE_ACTION_MODE_OVERLAY);
    }

    //...........................省略其他样式的处理,逻辑是类似的

    // Inflate the window decor.

    int layoutResource;
    int features = getLocalFeatures();
    // System.out.println("Features: 0x" + Integer.toHexString(features));
    //根据前面获取到的样式,选择相应的布局
    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) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleIconsDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_title_icons;
        }
        // XXX Remove this once action bar supports these features.
        removeFeature(FEATURE_ACTION_BAR);
        // System.out.println("Title Icons!");
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
            && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        // Special case for a window with only a progress bar (and title).
        // XXX Need to have a no-title version of embedded windows.
        layoutResource = R.layout.screen_progress;
        // System.out.println("Progress!");
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
        // Special case for a window with a custom title.
        // If the window is floating, we need a dialog layout
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogCustomTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_custom_title;
        }
        // XXX Remove this once action bar supports these features.
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
        // If no other features and not embedded, only need a title.
        // If the window is floating, we need a dialog layout
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
            layoutResource = a.getResourceId(
                    R.styleable.Window_windowActionBarFullscreenDecorLayout,
                    R.layout.screen_action_bar);
        } else {
            layoutResource = R.layout.screen_title;
        }
        // System.out.println("Title!");
    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
        layoutResource = R.layout.screen_simple_overlay_action_mode;
    } else {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!");
    }

    mDecor.startChanging();
    //根据样式选择好布局后,将相应的布局inflate为View
    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;//mContentRoot保存了整个窗口的内容
    //找到DecorView中ID为com.android.internal.R.id.content的view并赋值给contentParent ,我们为Activity等设置的View都是contentParent 的子view,setContentView这个名字就显得很贴切了。
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }
    //...............................................省略部分代码
    //在installDecor中可以看到,generateLayout的返回值有mContentParent来接收,
    //mContentParent就是setContentView(layoutResID)中layoutResID的父容器
    return contentParent;
}

上面这段代码先获取Window的样式,然后根据样式选择相应的布局并inflate转化成ViewGroup,并返回mContentParent对应的ViewGroup。Window的样式可以通过requestWindowFeature或者给Activity设置android:theme来指定。现在应该知道requestWindowFeature一定要在setContentView之前调用才能生效了吧。

3) DecorView完成初始化之后,通知Activity布局已经成功添加到mContentParent

完成上面的处理之后DecorView就创建好了,最后在PhoneWindow的setContentView中回调Activity的onContentChanged,通知Activity它指定的布局已经添加到mContentParent(DecorView)。回调过程如下:

 final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
    cb.onContentChanged();
}

到此为止,DecorView已经创建并完成初始化,Activity的布局也添加到DecorView中,但是这个时候我们还看不到DecorView,因为它还没有通过WindowManager添加到Window中。DecorView真正显示是在ActivityThread的handleResumeActivity方法中,handleResumeActivity先调用Activity的onResume,然后调用Activity的makeVisible把DecorView添加到Window中。

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

总结

最后总结一下setContentView将布局文件添加并最终显示的过程:

1)在PhoneWindow的setContentView中先调用installDecor初始化DecorView。具体的,在installDecor里面先调用generateDecor创建一个DecorView,然后调用generateLayout根据Window的样式风格选择布局文件并初始化DecorView;

2)通过mLayoutInflater将Activity的布局添加到mContentParent中

3)回调Activity的onContentChanged,通知Activity布局文件已经成功添加到DecorView中

4)最后在ActivityThread的handleResumeActivity中先调用Activity的onResume,然后调用Activity的makeVisible把DecorView添加到Window中,并显示到手机上。

前面三步都是在Activity的setContentView返回之前完成。


你可能感兴趣的:(Android积累)