从setContentView分析Android加载布局的流程

来源:互联网 发布:大学生网络知识竞赛 编辑:程序博客网 时间:2024/06/07 04:48

一.概述

在Activity中,我们基本都会用到setContentView方法,这个方法是干啥的想必大家都知道,把我们写好的布局文件显示到界面上。今天我们就去看看底层的源码,分析一下到底是如何实现的。

二.分析

1.Activity$getWindow()

public class MainActivity extends Activity {    private TextView tvText;    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tvText = (TextView) findViewById(R.id.textview);    }

我们先点进去MainActivity 中的setContentView方法,可以进入到Activity中,看到如下的代码:

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

在Activity的setContentView中调用了两个方法,我们只分析第一个,我们看看getWindow()返回的是一个什么东西,

 public Window getWindow() {        return mWindow;    }

是一个Window对象mWindow,大家注意,Window是一个抽象类,并且Window中的setContentView是一个抽象方法,那么Activity中肯定就有一个Window抽象类的实现,我们查找代码发现 mWindow对象赋值方法如下:

mWindow = PolicyManager.makeNewWindow(this);

我们可以看到这里调用了PolicyManager中的静态方法makeNewWindow,我们继续点进去,

public final class PolicyManager {    private static final String POLICY_IMPL_CLASS_NAME =        "com.android.internal.policy.impl.Policy";    private static final IPolicy sPolicy;    static {        // Pull in the actual implementation of the policy at run-time        try {            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);            sPolicy = (IPolicy)policyClass.newInstance();        } catch (ClassNotFoundException ex) {            throw new RuntimeException(                    POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);        } catch (InstantiationException ex) {            throw new RuntimeException(                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);        } catch (IllegalAccessException ex) {            throw new RuntimeException(                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);        }    }    // Cannot instantiate this class    private PolicyManager() {}    // The static methods to spawn new policy-specific objects    public static Window makeNewWindow(Context context) {        return sPolicy.makeNewWindow(context);    }

我们看到其实是调用了sPolicy对象的makeNewWindow方法,那么这个sPolicy对象是什么呢?很明显的可以看到是在上面通过反射得到的Policy的一个对象,所以我们接下来进入到Policy这个类中,寻找makeNewWindow这个方法,我们看到如下的代码,

public Window makeNewWindow(Context context) {        return new PhoneWindow(context);    }

最终返回的是PhoneWindow这样一个对象,通过着一系列的查找,也就是说Activity中的getWindow()方法得到的其实是一个PhoneWindow对象,其实PhoneWindow是Window的唯一实现类,我们在进入到Window这个类的源码中的时候官方已经告诉我们了,

* <p>The only existing implementation of this abstract class is * android.view.PhoneWindow, which you should instantiate when needing a * Window. */public abstract class Window {

2.PhoneWindow$setContentView
由于我们平时写的setContentView最终是调用了PhoneWindow里面的
setContentView,所以我们看看这里面的代码,

  @Override    public void setContentView(int layoutResID) {        if (mContentParent == null) {            installDecor();        } else {            mContentParent.removeAllViews();        }       //将我们的布局文件添加到mContentParent中,这个mContentParent是根布局中id为content的一个FrameLayout        mLayoutInflater.inflate(layoutResID, mContentParent);        final Callback cb = getCallback();        if (cb != null && !isDestroyed()) {            cb.onContentChanged();        }    }

首先判断mContentParent是否为null,mContentParent是什么呢?接下来会分析,一开始条件成立,进入installDecor()方法。

 private void installDecor() {        if (mDecor == null) {        ///创建DecorView            mDecor = generateDecor();            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);            mDecor.setIsRootNamespace(true);            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);            }        }        if (mContentParent == null) {            mContentParent = generateLayout(mDecor);            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.            mDecor.makeOptionalFitsSystemWindows();            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);            if (mTitleView != null) {                mTitleView.setLayoutDirection(mDecor.getLayoutDirection());                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {                    View titleContainer = findViewById(com.android.internal.R.id.title_container);                    if (titleContainer != null) {                        titleContainer.setVisibility(View.GONE);                    } else {                        mTitleView.setVisibility(View.GONE);                    }                    if (mContentParent instanceof FrameLayout) {                        ((FrameLayout)mContentParent).setForeground(null);                    }                } else {                    mTitleView.setText(mTitle);                }            } else {                mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);                if (mActionBar != null) {                    mActionBar.setWindowCallback(getCallback());                    if (mActionBar.getTitle() == null) {                        mActionBar.setWindowTitle(mTitle);                    }                    final int localFeatures = getLocalFeatures();                    if ((localFeatures & (1 << FEATURE_PROGRESS)) != 0) {                        mActionBar.initProgress();                    }                    if ((localFeatures & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {                        mActionBar.initIndeterminateProgress();                    }                    boolean splitActionBar = false;                    final boolean splitWhenNarrow =                            (mUiOptions & ActivityInfo.UIOPTION_SPLIT_ACTION_BAR_WHEN_NARROW) != 0;                    if (splitWhenNarrow) {                        splitActionBar = getContext().getResources().getBoolean(                                com.android.internal.R.bool.split_action_bar_is_narrow);                    } else {                        splitActionBar = getWindowStyle().getBoolean(                                com.android.internal.R.styleable.Window_windowSplitActionBar, false);                    }                    final ActionBarContainer splitView = (ActionBarContainer) findViewById(                            com.android.internal.R.id.split_action_bar);                    if (splitView != null) {                        mActionBar.setSplitView(splitView);                        mActionBar.setSplitActionBar(splitActionBar);                        mActionBar.setSplitWhenNarrow(splitWhenNarrow);                        final ActionBarContextView cab = (ActionBarContextView) findViewById(                                com.android.internal.R.id.action_context_bar);                        cab.setSplitView(splitView);                        cab.setSplitActionBar(splitActionBar);                        cab.setSplitWhenNarrow(splitWhenNarrow);                    } else if (splitActionBar) {                        Log.e(TAG, "Requested split action bar with " +                                "incompatible window decor! Ignoring request.");                    }                    // Post the panel invalidate for later; avoid application onCreateOptionsMenu                    // being called in the middle of onCreate or similar.                    mDecor.post(new Runnable() {                        public void run() {                            // Invalidate if the panel menu hasn't been created before this.                            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);                            if (!isDestroyed() && (st == null || st.menu == null)) {                                invalidatePanelMenu(FEATURE_ACTION_BAR);                            }                        }                    });                }            }        }

在代码的第三行,我们看到

mDecor = generateDecor();

我们进入generateDecor这个方法,

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

这里创建了一个DecorView对象,DecorView是PhoneWindow里面的一个内部类,继承自FrameLayout,到目前为止,setContentView
方法里生成一个FrameLayout类型的DecorView组件,我们继续向下看,有这样的代码

 if (mContentParent == null) {            mContentParent = generateLayout(mDecor);

把 DecorView 对象 mDecor 作为参数传递给 generateLayout方法得到 mContentParent。generateLayout()方法中的代码实现如下:

 protected ViewGroup generateLayout(DecorView decor) {        // Apply data from current theme.        TypedArray a = getWindowStyle();    //省去一些代码/**以下这些是Activity 窗口属性特征的设置*/        //窗口是否浮动,一般用于Dialog窗口是否浮动:是否显示在布局的正中间。        mIsFloating = a.getBoolean(com.android.internal.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(com.android.internal.R.styleable.Window_windowNoTitle, false)) {            requestFeature(FEATURE_NO_TITLE);        } else if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBar, false)) {            // Don't allow an action bar if there is no title.            requestFeature(FEATURE_ACTION_BAR);        }//ActionBar导航栏是否不占布局空间叠加显示在当前窗口之上。        if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBarOverlay, false)) {            requestFeature(FEATURE_ACTION_BAR_OVERLAY);        }        if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionModeOverlay, false)) {            requestFeature(FEATURE_ACTION_MODE_OVERLAY);        }//当前Activity是否支持全屏        if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));        }        if (a.getBoolean(com.android.internal.R.styleable.Window_windowOverscan, false)) {            setFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags()));        }        if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {            setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));        }        if (a.getBoolean(com.android.internal.R.styleable.Window_windowEnableSplitTouch,                getContext().getApplicationInfo().targetSdkVersion                        >= android.os.Build.VERSION_CODES.HONEYCOMB)) {            setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));        }        a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);        a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);        if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedWidthMajor)) {            if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();            a.getValue(com.android.internal.R.styleable.Window_windowFixedWidthMajor,                    mFixedWidthMajor);        }        if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedWidthMinor)) {            if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();            a.getValue(com.android.internal.R.styleable.Window_windowFixedWidthMinor,                    mFixedWidthMinor);        }        if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedHeightMajor)) {            if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();            a.getValue(com.android.internal.R.styleable.Window_windowFixedHeightMajor,                    mFixedHeightMajor);        }        if (a.hasValue(com.android.internal.R.styleable.Window_windowFixedHeightMinor)) {            if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();            a.getValue(com.android.internal.R.styleable.Window_windowFixedHeightMinor,                    mFixedHeightMinor);        }        final Context context = getContext();        final int targetSdk = context.getApplicationInfo().targetSdkVersion;        final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;        final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;        final boolean targetHcNeedsOptions = context.getResources().getBoolean(                com.android.internal.R.bool.target_honeycomb_needs_options_menu);        final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);        if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {            addFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY);        } else {            clearFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY);        }        if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion                >= android.os.Build.VERSION_CODES.HONEYCOMB) {            if (a.getBoolean(                    com.android.internal.R.styleable.Window_windowCloseOnTouchOutside,                    false)) {                setCloseOnTouchOutsideIfNotSet(true);            }        }        WindowManager.LayoutParams params = getAttributes();        if (!hasSoftInputMode()) {            params.softInputMode = a.getInt(                    com.android.internal.R.styleable.Window_windowSoftInputMode,                    params.softInputMode);        }        if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled,                mIsFloating)) {            /* All dialogs should have the window dimmed */            if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {                params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;            }            if (!haveDimAmount()) {                params.dimAmount = a.getFloat(                        android.R.styleable.Window_backgroundDimAmount, 0.5f);            }        }        if (params.windowAnimations == 0) {            params.windowAnimations = a.getResourceId(                    com.android.internal.R.styleable.Window_windowAnimationStyle, 0);        }        // The rest are only done if this window is not embedded; otherwise,        // the values are inherited from our container.        if (getContainer() == null) {            if (mBackgroundDrawable == null) {                if (mBackgroundResource == 0) {                    mBackgroundResource = a.getResourceId(                            com.android.internal.R.styleable.Window_windowBackground, 0);                }                if (mFrameResource == 0) {                    mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0);                }                if (false) {                    System.out.println("Background: "                            + Integer.toHexString(mBackgroundResource) + " Frame: "                            + Integer.toHexString(mFrameResource));                }            }            mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000);        }        // Inflate the window decor.        int layoutResource;        int features = getLocalFeatures();        // System.out.println("Features: 0x" + Integer.toHexString(features));        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {            if (mIsFloating) {                TypedValue res = new TypedValue();                getContext().getTheme().resolveAttribute(                        com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);                layoutResource = res.resourceId;            } else {                layoutResource = com.android.internal.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 = com.android.internal.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(                        com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true);                layoutResource = res.resourceId;            } else {                layoutResource = com.android.internal.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(                        com.android.internal.R.attr.dialogTitleDecorLayout, res, true);                layoutResource = res.resourceId;            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {                layoutResource = com.android.internal.R.layout.screen_action_bar;            } else {                layoutResource = com.android.internal.R.layout.screen_title;            }            // System.out.println("Title!");        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {            layoutResource = com.android.internal.R.layout.screen_simple_overlay_action_mode;        } else {            //默认布局文件.            layoutResource =            com.android.internal.R.layout.screen_simple;            // System.out.println("Simple!");        }        mDecor.startChanging();//通过布局添加器LayoutInflater获取layoutResource布局,        View in = mLayoutInflater.inflate(layoutResource, null);         //将XML资源为layoutResource的布局添加到decor容器里面,至此PhoneWindow 内部类DecorView就添加了子布局        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); //此处很重要,通过findViewById找到 contentParent容器,也是该方法的返回值。        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);        if (contentParent == null) {            throw new RuntimeException("Window couldn't find content container view");        }        if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {            ProgressBar progress = getCircularProgressBar(false);            if (progress != null) {                progress.setIndeterminate(true);            }        }        // Remaining setup -- of background and title -- that only applies        // to top-level windows.        if (getContainer() == null) {            Drawable drawable = mBackgroundDrawable;            if (mBackgroundResource != 0) {                drawable = getContext().getResources().getDrawable(mBackgroundResource);            }            mDecor.setWindowBackground(drawable);            drawable = null;            if (mFrameResource != 0) {                drawable = getContext().getResources().getDrawable(mFrameResource);            }            mDecor.setWindowFrame(drawable);            // System.out.println("Text=" + Integer.toHexString(mTextColor) +            // " Sel=" + Integer.toHexString(mTextSelectedColor) +            // " Title=" + Integer.toHexString(mTitleColor));            if (mTitleColor == 0) {                mTitleColor = mTextColor;            }            if (mTitle != null) {                setTitle(mTitle);            }            setTitleColor(mTitleColor);        }        mDecor.finishChanging();        return contentParent;    }

以上代码代比较复杂,主要做了以下几件事情。
(1)初始化窗口的特征,是否显示标题栏,是否全屏,是否支持ActionBar浮动等等。
(2)通过LayoutInflater 将默认根xml布局转换成view
(3)将找到的View添加到DecorView根布局当中。
(4)从根布局中找到id为R.id.content的 contentParent 容器。也就是当前方法的返回值。
接下来我们看看这个com.android.internal.R.layout.screen_simple布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"      android:orientation="vertical"      android:fitsSystemWindows="true">      <!-- Popout bar for action modes -->      <ViewStub android:id="@+id/action_mode_bar_stub"                android:inflatedId="@+id/action_mode_bar"                android:layout="@layout/action_mode_bar"                android:layout_width="match_parent"                android:layout_height="wrap_content" />      <FrameLayout android:id="@android:id/title_container"           android:layout_width="match_parent"           android:layout_height="?android:attr/windowTitleSize"          style="?android:attr/windowTitleBackgroundStyle">      </FrameLayout>      <FrameLayout android:id="@android:id/content"          android:layout_width="match_parent"           android:layout_height="0dip"          android:layout_weight="1"          android:foregroundGravity="fill_horizontal|top"          android:foreground="?android:attr/windowContentOverlay" />  </LinearLayout>  

我们的DecorView就添加了上面的布局,线性布局LinearLaout里包含两个组件,ViewStub是懒加载,默认不显示,FrameLayout是什么呢?看看id=content,就是我们下面这行找到的contentParent

 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

那么这个父容器 contentParent有什么作用呢?
我们回到下面的两行代码,都是之前出现过的

//获得父容器mContentParent = generateLayout(mDecor); 
 mLayoutInflater.inflate(layoutResID, mContentParent);

这里通过LayoutInflater将 setContentView(layoutResID)传进来的布局id加载到 父容器mContentParent中,至此,setContentView就将布局添加到Activity里面了。

现在我们来梳理一下流程:

Activity setContentView—>Window setContentView—>PhoneWindow setContentView—->PhoneWindow installDecor—–>PhoneWindow generateLayout——>PhoneWindow mLayoutInflater.inflate(layoutResID, mContentParent);

Activity 类中有一个Window抽象类的实现PhoneWindow类,该类中有个内部类DecorView,继承自FrameLayout,在DecorView容器中添加了根布局,根布局中包含了一个id为 contnet的FrameLayout 内容布局,我们的Activity加载的布局xml最后添加到 id为content的FrameLayout布局当中了。用一个图来描述,如下:

这里写图片描述

总结:

1.关于requestWindowFeature(Window.FEATURE_NO_TITLE); 去除标题栏的疑问,如果你自己的xxxActivity是继承自Activity,那么恭喜你使用以上方法可以去除标题栏,如果你自己的xxxActivity是继承自AppCompatActivity或者ActionBarActivity,那么很遗憾告诉你,此次系统默认的标题栏已经在主题中去除,此时显示的标题栏是ActionBar导航栏,如果需要去除导航栏,你可以通过如下代码:getSupportActionBar().hide();来隐藏导航栏。

2.requestWindowFeature(Window.FEATURE_NO_TITLE);方法需要在 setContentView方法之前使用,由上面 Step5分析可得,设置Activity Window 特征是在setContentView方法中设置的,因此,如果需要改变Activity Window窗口特征,需要在setContentView方法之前。其实这里有疑问???为什么设置全屏的方法

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
可以在setContentView之后呢???求解。

0 0
原创粉丝点击