当前位置: 代码迷 >> Android >> 从setContentView步骤分析Android加载布局流程
  详细解决方案

从setContentView步骤分析Android加载布局流程

热度:16   发布时间:2016-04-28 00:13:32.0
从setContentView方法分析Android加载布局流程

PS一句:当初你所逃避的问题终会在未来的某一天重新出现在你面前,因此,当你第一次遇到它时,请不要逃避。

相信很多初学者对XML布局怎么加载到Activity上并且显示在手机屏幕上很好奇吧?今天我们就从经常使用的方法

setContentView来从源码分析一下XML布局是怎么加载到当前Activity上的。

Activity#setContentView

我们知道,Activity是在onCreate方法中使用setContentView方法来加载布局的,那么它内部的源码是怎么实现的呢?

Setp 1

处于好奇,我们进入了Activity的源码,找到setContentView方法如下:

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

setContentView方法实现很简单,里面调用两个方法,我们这里分析第一个方法:

getWindow().setContentView(layoutResID);

getWindow()得到一个Window对象 mWindow ,Window是一个抽象类,用来描述Activity视图最顶端的窗口显示和行为操作。Window是

一个抽象类,那么里面的setContentView(layoutResID)是一个抽象方法,并没有具体的实现,既然Window是一个抽象类,那么

在Activity里面就有一个Window抽象类的实现,我们查找代码发现 mWindow对象赋值方法如下:

mWindow = PolicyManager.makeNewWindow(this);

Step 2

Look the fuck resoure code,继续看看 PolicyManager类

public final class More PolicyManager {30    private static final String POLICY_IMPL_CLASS_NAME =31        "com.android.internal.policy.impl.Policy";3233    private static final IPolicy sPolicy;3435    static {36        // Pull in the actual implementation of the policy at run-time37        try {38            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);39            sPolicy = (IPolicy)policyClass.newInstance();40        } catch (ClassNotFoundException ex) {41            throw new RuntimeException(42                    POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);43        } catch (InstantiationException ex) {44            throw new RuntimeException(45                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);46        } catch (IllegalAccessException ex) {47            throw new RuntimeException(48                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);49        }50    }5152    // Cannot instantiate this class53    private More PolicyManager() {}5455    // The static methods to spawn new policy-specific objects56    public static Window More makeNewWindow(Context context) {57        return sPolicy.makeNewWindow(context);58    }

地 57 行 sPolicy对象是有第 38,39行通过类路径”com.android.internal.policy.impl.Policy“生成的,那么我们在源码中找到 Policy类,在此类中找到了如下方法:

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

由此可见,我们终于找到Activity类中的 mWindow对象的实现类了,就是PhoneWindow类。

Setp 3

从源码发现,PhoneWindow类就是抽象类Window的实现类。那么 setContentView方法的实现也在这个类里面了,

到PhoneWindow类发现,setContentView方法实现如下:

 @Override    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,mContentParent是什么呢?接下来会分析,一开始条件成立,进入installDecor()方法。

Setp 4

到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);            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.            mDecor.makeOptionalFitsSystemWindows();            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(                    R.id.decor_content_parent);            if (decorContentParent != null) {                mDecorContentParent = decorContentParent;                mDecorContentParent.setWindowCallback(getCallback());                if (mDecorContentParent.getTitle() == null) {                    mDecorContentParent.setWindowTitle(mTitle);                }                final int localFeatures = getLocalFeatures();                for (int i = 0; i < FEATURE_MAX; i++) {                    if ((localFeatures & (1 << i)) != 0) {                        mDecorContentParent.initFeature(i);                    }                }                mDecorContentParent.setUiOptions(mUiOptions);                if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) != 0 ||                        (mIconRes != 0 && !mDecorContentParent.hasIcon())) {                    mDecorContentParent.setIcon(mIconRes);                } else if ((mResourcesSetFlags & FLAG_RESOURCE_SET_ICON) == 0 &&                        mIconRes == 0 && !mDecorContentParent.hasIcon()) {                    mDecorContentParent.setIcon(                            getContext().getPackageManager().getDefaultActivityIcon());                    mResourcesSetFlags |= FLAG_RESOURCE_SET_ICON_FALLBACK;                }                if ((mResourcesSetFlags & FLAG_RESOURCE_SET_LOGO) != 0 ||                        (mLogoRes != 0 && !mDecorContentParent.hasLogo())) {                    mDecorContentParent.setLogo(mLogoRes);                }                // Invalidate if the panel menu hasn't been created before this.                // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu                // being called in the middle of onCreate or similar.                // A pending invalidation will typically be resolved before the posted message                // would run normally in order to satisfy instance state restoration.                PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);                if (!isDestroyed() && (st == null || st.menu == null)) {                    invalidatePanelMenu(FEATURE_ACTION_BAR);                }            } else {                mTitleView = (TextView)findViewById(R.id.title);                if (mTitleView != null) {                    mTitleView.setLayoutDirection(mDecor.getLayoutDirection());                    if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {                        View titleContainer = findViewById(                                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);                    }                }            }            if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {                mDecor.setBackgroundFallback(mBackgroundFallbackResource);            }            // Only inflate or create a new TransitionManager if the caller hasn't            // already set a custom one.            if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {                if (mTransitionManager == null) {                    final int transitionRes = getWindowStyle().getResourceId(                            R.styleable.Window_windowContentTransitionManager,                            0);                    if (transitionRes != 0) {                        final TransitionInflater inflater = TransitionInflater.from(getContext());                        mTransitionManager = inflater.inflateTransitionManager(transitionRes,                                mContentParent);                    } else {                        mTransitionManager = new TransitionManager();                    }                }                mEnterTransition = getTransition(mEnterTransition, null,                        R.styleable.Window_windowEnterTransition);                mReturnTransition = getTransition(mReturnTransition, USE_DEFAULT_TRANSITION,                        R.styleable.Window_windowReturnTransition);                mExitTransition = getTransition(mExitTransition, null,                        R.styleable.Window_windowExitTransition);                mReenterTransition = getTransition(mReenterTransition, USE_DEFAULT_TRANSITION,                        R.styleable.Window_windowReenterTransition);                mSharedElementEnterTransition = getTransition(mSharedElementEnterTransition, null,                        R.styleable.Window_windowSharedElementEnterTransition);                mSharedElementReturnTransition = getTransition(mSharedElementReturnTransition,                        USE_DEFAULT_TRANSITION,                        R.styleable.Window_windowSharedElementReturnTransition);                mSharedElementExitTransition = getTransition(mSharedElementExitTransition, null,                        R.styleable.Window_windowSharedElementExitTransition);                mSharedElementReenterTransition = getTransition(mSharedElementReenterTransition,                        USE_DEFAULT_TRANSITION,                        R.styleable.Window_windowSharedElementReenterTransition);                if (mAllowEnterTransitionOverlap == null) {                    mAllowEnterTransitionOverlap = getWindowStyle().getBoolean(                            R.styleable.Window_windowAllowEnterTransitionOverlap, true);                }                if (mAllowReturnTransitionOverlap == null) {                    mAllowReturnTransitionOverlap = getWindowStyle().getBoolean(                            R.styleable.Window_windowAllowReturnTransitionOverlap, true);                }                if (mBackgroundFadeDurationMillis < 0) {                    mBackgroundFadeDurationMillis = getWindowStyle().getInteger(                            R.styleable.Window_windowTransitionBackgroundFadeDuration,                            DEFAULT_BACKGROUND_FADE_DURATION_MS);                }                if (mSharedElementsUseOverlay == null) {                    mSharedElementsUseOverlay = getWindowStyle().getBoolean(                            R.styleable.Window_windowSharedElementsUseOverlay, true);                }            }        }    }

在代码的第 3 行我们看到 mDecor = generateDecor();方法调用,继续跳进 generateDecor()方法:

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

这里生成一个DecorView对象,DecorView是PhoneWindow类的内部类,继承自FrameLayout。到目前为止,setContentView

方法里生成一个FrameLayout类型的DecorView组件。

Step 5

继续分析代码,看第 11 行:

 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(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);        }        //ActionBar导航栏是否不占布局空间叠加显示在当前窗口之上。        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);        }        if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {            requestFeature(FEATURE_SWIPE_TO_DISMISS);        }        //当前Activity是否支持全屏        if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));        }       ................       //设置状态栏的颜色        if (!mForcedStatusBarColor) {            mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);        }        //设置导航栏的颜色        if (!mForcedNavigationBarColor) {            mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);        }        if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion                >= android.os.Build.VERSION_CODES.HONEYCOMB) {            if (a.getBoolean(                    R.styleable.Window_windowCloseOnTouchOutside,                    false)) {                setCloseOnTouchOutsideIfNotSet(true);            }        }        WindowManager.LayoutParams params = getAttributes();        //设置输入法的状态        if (!hasSoftInputMode()) {            params.softInputMode = a.getInt(                    R.styleable.Window_windowSoftInputMode,                    params.softInputMode);        }        if (a.getBoolean(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);            }        }        //设置当前Activity的出现动画效果        if (params.windowAnimations == 0) {            params.windowAnimations = a.getResourceId(                    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(                            R.styleable.Window_windowBackground, 0);                }                if (mFrameResource == 0) {                    mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);                }                mBackgroundFallbackResource = a.getResourceId(                        R.styleable.Window_windowBackgroundFallback, 0);                if (false) {                    System.out.println("Background: "                            + Integer.toHexString(mBackgroundResource) + " Frame: "                            + Integer.toHexString(mFrameResource));                }            }            mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);            mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);            mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);        }        //以下代码为当前Activity窗口添加 decor根布局。        // 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();        //通过布局添加器LayoutInflater获取layoutResource布局,        View in = mLayoutInflater.inflate(layoutResource, null);        //将XML资源为layoutResource的布局添加到decor容器里面,至此PhoneWindow 内部类DecorView就添加了之布局        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));        mContentRoot = (ViewGroup) in;        //此处很重要,通过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);            }        }        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {            registerSwipeCallbacks();        }        // Remaining setup -- of background and title -- that only applies        // to top-level windows.        //以下代码设置Activity窗口的背景,标题等        if (getContainer() == null) {            final Drawable background;            if (mBackgroundResource != 0) {                background = getContext().getDrawable(mBackgroundResource);            } else {                background = mBackgroundDrawable;            }            mDecor.setWindowBackground(background);            final Drawable frame;            if (mFrameResource != 0) {                frame = getContext().getDrawable(mFrameResource);            } else {                frame = null;            }            mDecor.setWindowFrame(frame);            mDecor.setElevation(mElevation);            mDecor.setClipToOutline(mClipToOutline);            if (mTitle != null) {                setTitle(mTitle);            }            if (mTitleColor == 0) {                mTitleColor = mTextColor;            }            setTitleColor(mTitleColor);        }        mDecor.finishChanging();        return contentParent;    }

以上代码代比较复杂,主要做了以下几件事情。

  1. 从第 8 行到第110行,主要是初始化窗口的特征,是否显示标题栏,是否全屏,是否支持ActionBar浮动等等。
  2. 第112-187行,主要是给容器DecorView添加id为layoutResource布局的根布局。待会分析layoutResource布局。
  3. 第178行,通过LayoutInflater 将xml布局转换成VIEW.
  4. 第180行将找到的View添加到DecorView根布局当中。
  5. 第184行,从根布局中找到id为R.id.content的 contentParent 容器。也就是当前方法的返回值。
    接下来,看看 id为layoutResource的布局到底实现了什么?我们来看看171行的R.layout.screen_simple;资源
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:fitsSystemWindows="true"    android:orientation="vertical">    <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"              android:theme="?attr/actionBarTheme" />    <FrameLayout         android:id="@android:id/content"         android:layout_width="match_parent"         android:layout_height="match_parent"         android:foregroundInsidePadding="false"         android:foregroundGravity="fill_horizontal|top"         android:foreground="?android:attr/windowContentOverlay" /></LinearLayout>

原来我们的DecorView根布局里面添加了类似上面的布局,线性布局LinearLaout里包含两个组件,ViewStub是懒加载,默认不显示,FrameLayout是什么呢?看看id=content,就是我们184行找到的父容器 contentParent。那么这个父容器 contentParent有什么作用呢?

我们回到 Step4 的第 11行,mContentParent = generateLayout(mDecor); 获得 父容器 mContentParent。我们再次回到 Step3步的第17行, 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之后呢???求解。

版权声明:本文为博主原创文章,未经博主允许不得转载。

  相关解决方案