美文网首页Android技术知识Android开发Android开发经验谈
【重拾View(一)】——setContentView()源码解

【重拾View(一)】——setContentView()源码解

作者: 被代码淹没的小伙子 | 来源:发表于2018-09-01 18:24 被阅读18次

    前言

    【重拾View】这个系列主要是想对View的各方面进行细化和回顾,随着Android研究的深入,对View的理解只停留在刚开始接触自定义View三部曲,简单知道onMeasure,onLayout,onDraw等仅仅和View相关联的使用层级的理解,感觉是远远不够的。本篇博客就分析一下我们最常见的一个方法setContentView,但是又没有真正关注过底层的实现。

    层级关系

    分析完本篇博客,会对下图有了更深对理解。


    层级关系

    主要流程源码

    public void setContentView(@LayoutRes int layoutResID) {
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
        }
    
    
        public Window getWindow() {
            return mWindow;
        }
    
    final void attach(){
        ...
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ...
    }
    

    首先既然是setContentView,当然是在Activity下的方法,如上面的源码所示,可以看到,当我们调用setContentView的时候,实际上调用的是WinddowsetContentView方法,而Window是一个抽象类,真正的实现类,可以看到在Activityattach方法里,对Window进行里初始化,对应的实现类是PhoneWindow。所以接下来我们来看一下PhoneWindowsetContentView方法。

    @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) {
                    //初始化DecorView
                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);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    
    

    这里可以看到,在PhoneWindowsetContentView方法里,首先判读了mContentParent是否为空,这里暂时先不考虑mContentParent的作用,但是默认为空,所以这里可以看到,当mContentParent为空的时候,会走installDecor()方法,这个方法会初始化一个Android中耳熟能详的一个东西DecorView

    private void installDecor() {
            mForceDecorInstall = false;
            if (mDecor == null) {
                //生成了一个FrameLayout,作为DecorView
                mDecor = generateDecor(-1);
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                mDecor.setIsRootNamespace(true);
                if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                    mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
                }
            } else {
                mDecor.setWindow(this);
            }
            if (mContentParent == null) {
                    //初始化mContentParent
                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) && !mIsStartingWindow) {
                        invalidatePanelMenu(FEATURE_ACTION_BAR);
                    }
                } else {
                    mTitleView = findViewById(R.id.title);
                    if (mTitleView != null) {
                        if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                            final View titleContainer = findViewById(R.id.title_container);
                            if (titleContainer != null) {
                                titleContainer.setVisibility(View.GONE);
                            } else {
                                mTitleView.setVisibility(View.GONE);
                            }
                            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);
                    }
                }
            }
        }
    

    这个方法里我们需要注意两个方法:

    generateDecor
    generateLayout

    if (mDecor == null) {
       mDecor = generateDecor(-1);
       ...
       } else {
       ...
       }
    

    可以看到这里首先对mDecor进行了判空,而这个mDecor就是DecorView对象。

    protected DecorView generateDecor(int featureId) {
            // System process doesn't have application context and in that case we need to directly use
            // the context we have. Otherwise we want the application context, so we don't cling to the
            // activity.
            Context context;
            if (mUseDecorContext) {
                Context applicationContext = getContext().getApplicationContext();
                if (applicationContext == null) {
                    context = getContext();
                } else {
                    context = new DecorContext(applicationContext, getContext().getResources());
                    if (mTheme != -1) {
                        context.setTheme(mTheme);
                    }
                }
            } else {
                context = getContext();
            }
            return new DecorView(context, featureId, this, getAttributes());
        }
    

    可以看到,这里最终就是new了一个DecorView对象。而我们来看一下DecorView对源码。

    public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    

    可以发现其实DecorView实际上就是一个FrameLayout。

    if (mContentParent == null) {
                    //初始化mContentParent
                mContentParent = generateLayout(mDecor);
    }
    

    可以看到这里初始化完DecorView后便开始创建mContentParent

    protected ViewGroup generateLayout(DecorView decor) {
            // Apply data from current theme.
    
            TypedArray a = getWindowStyle();
    
            if (false) {
                System.out.println("From style:");
                String s = "Attrs:";
                for (int i = 0; i < R.styleable.Window.length; i++) {
                    s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
                            + a.getString(i);
                }
                System.out.println(s);
            }
            //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);
            }
            //activity是否没有标题
            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);
            }
    
            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 (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
                    false)) {
                setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
                        & (~getForcedWindowFlags()));
            }
    
            if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
                    false)) {
                setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
                        & (~getForcedWindowFlags()));
            }
    
            if (a.getBoolean(R.styleable.Window_windowOverscan, false)) {
                setFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags()));
            }
    
            if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
                setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
            }
    
            if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch,
                    getContext().getApplicationInfo().targetSdkVersion
                            >= android.os.Build.VERSION_CODES.HONEYCOMB)) {
                setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
            }
    
            a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
            a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
            if (DEBUG) Log.d(TAG, "Min width minor: " + mMinWidthMinor.coerceToString()
                    + ", major: " + mMinWidthMajor.coerceToString());
            if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
                if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
                a.getValue(R.styleable.Window_windowFixedWidthMajor,
                        mFixedWidthMajor);
            }
            if (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) {
                if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
                a.getValue(R.styleable.Window_windowFixedWidthMinor,
                        mFixedWidthMinor);
            }
            if (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) {
                if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
                a.getValue(R.styleable.Window_windowFixedHeightMajor,
                        mFixedHeightMajor);
            }
            if (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) {
                if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
                a.getValue(R.styleable.Window_windowFixedHeightMinor,
                        mFixedHeightMinor);
            }
            if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) {
                requestFeature(FEATURE_CONTENT_TRANSITIONS);
            }
            if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) {
                requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
            }
    
            mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
    
            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 targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
            final boolean targetHcNeedsOptions = context.getResources().getBoolean(
                    R.bool.target_honeycomb_needs_options_menu);
            final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);
    
            if (targetPreHoneycomb || (targetPreIcs && targetHcNeedsOptions && noActionBar)) {
                setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_TRUE);
            } else {
                setNeedsMenuKey(WindowManager.LayoutParams.NEEDS_MENU_SET_FALSE);
            }
    
            if (!mForcedStatusBarColor) {
                mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
            }
            if (!mForcedNavigationBarColor) {
                mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
            }
    
            WindowManager.LayoutParams params = getAttributes();
    
            // Non-floating windows on high end devices must put up decor beneath the system bars and
            // therefore must know about visibility changes of those.
            if (!mIsFloating && ActivityManager.isHighEndGfx()) {
                if (!targetPreL && a.getBoolean(
                        R.styleable.Window_windowDrawsSystemBarBackgrounds,
                        false)) {
                    setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                            FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());
                }
                if (mDecor.mForceWindowDrawsStatusBarBackground) {
                    params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
                }
            }
            if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
                decor.setSystemUiVisibility(
                        decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
            }
    
            if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
                    >= android.os.Build.VERSION_CODES.HONEYCOMB) {
                if (a.getBoolean(
                        R.styleable.Window_windowCloseOnTouchOutside,
                        false)) {
                    setCloseOnTouchOutsideIfNotSet(true);
                }
            }
            //设置输入法的状态
            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));
                    }
                }
                if (mLoadElevation) {
                    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);
            }
    
            // Inflate the window decor.
    
            int layoutResource;
            int features = getLocalFeatures();
            // System.out.println("Features: 0x" + Integer.toHexString(features));
            //根据不同的features选择不同布局进行加载
            if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
                layoutResource = R.layout.screen_swipe_dismiss;
                setCloseOnSwipeEnabled(true);
            } 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();
            //加载布局,并加入DecorView
            mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
            //通过findViewById,找到内容区域,是一个FrameLayout
            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(contentParent);
            }
    
            // Remaining setup -- of background and title -- that only applies
            // to top-level windows.
            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;
        }
    

    这个方法比较长,我们会发现和我们自定义View的步骤很相近,这里首先获取了自定义属性,然后分别设置了一些属性值。

    //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);
            }
            
            //activity是否没有标题
            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);
            }
            
            //activity是否全屏
            if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
                setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
            }
            
            //设置输入法的状态
            if (!hasSoftInputMode()) {
                params.softInputMode = a.getInt(
                        R.styleable.Window_windowSoftInputMode,
                        params.softInputMode);
            }
            
            //Activity的进入动画
            if (params.windowAnimations == 0) {
                params.windowAnimations = a.getResourceId(
                        R.styleable.Window_windowAnimationStyle, 0);
            }
    

    可以看到我们在xml里面设置的属性值在这里就生效了,具体还有其他的属性值这里就不具体分析了。

    //根据不同的features选择不同布局进行加载
            if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
                layoutResource = R.layout.screen_swipe_dismiss;
                setCloseOnSwipeEnabled(true);
            } 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!");
            }
    

    接下来这段代码的作用就是确定最终的布局文件,可以看到这里用到了很多的ifelse,这里通过位运算符和feature进行比较,可能这里说feature比较陌生,我们平常设置我们的Activity全屏的时候,常用的一段代码就是:

    requestWindowFeature(Window.FEATURE_NO_TITLE);
    

    这里就是改变feature的属性,所以这里当我们没有特殊设置的的时候,最终最选择使用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>
    

    可以看到,这就是一个再平常不过的布局文件了,一个LinearLayout,第一个ViewStub不会默认加载,还剩一个FrameLayout,注意这里的id@android:id/content。我们继续后面的代码

    mDecor.startChanging();
            //加载布局,并加入DecorView
            mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
            //通过findViewById,找到内容区域,是一个FrameLayout
            ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
            if (contentParent == null) {
                throw new RuntimeException("Window couldn't find content container view");
            }
    

    可以看到这里其实有两部操作

    1.将刚才的布局加入到DecorView中
    2.返回DecorView中的FrameLayout,也就是刚才布局。

    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
            mStackId = getStackId();
    
            if (mBackdropFrameRenderer != null) {
                loadBackgroundDrawablesIfNeeded();
                mBackdropFrameRenderer.onResourcesLoaded(
                        this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                        mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                        getCurrentColor(mNavigationColorViewState));
            }
    
            mDecorCaptionView = createDecorCaptionView(inflater);
            //用LayoutInflater加载布局文件
            final View root = inflater.inflate(layoutResource, null);
            if (mDecorCaptionView != null) {
                if (mDecorCaptionView.getParent() == null) {
                    addView(mDecorCaptionView,
                            new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
                }
                mDecorCaptionView.addView(root,
                        new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
            } else {
            //加入到DecorView中
                // Put it below the color views.
                addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mContentRoot = (ViewGroup) root;
            initializeElevation();
        }
    

    这里可以看一下onResourcesLoaded方法,我们将刚才R.layout.screen_simple;的布局文件传入,可以看到这里先用LayoutInflater加载这个布局文件,注意这里并没有直接通过LayoutInflater将布局文件加入DecorView中,而是先通过一个判断,这里应该是一个可以优先级较高的布局,但是不影响流程分析,这里就不纠结了,可以看到,在后面,通过addView方法,将这个布局文件,加入到DecorView中。

    //通过findViewById,找到内容区域,是一个FrameLayout
            ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
            if (contentParent == null) {
                throw new RuntimeException("Window couldn't find content container view");
            }
    //前面布局文件中看到的FrameLayout的Id        
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
    

    加入到DecorView中,通过findViewById方法,注意这里寻找到Id就是我们前面在布局R.layout.screen_simple;看到的那个FrameLayout的ID。

     protected ViewGroup generateLayout(DecorView decor) {
                ....
            return contentParent;
        }
    

    所以这里可以发现,generateLayout方法回将布局文件中的FrameLayout加载并返回。而这时候我们在往回看。

    private void installDecor() {
            mForceDecorInstall = false;
            if (mDecor == null) {
                mDecor = generateDecor(-1);
                ...
               } else {
                mDecor.setWindow(this);
            }
            if (mContentParent == null) {
            //获取到布局文件中的FrameLayout
                mContentParent = generateLayout(mDecor);
    }
    

    这里回将这个FrameLayout赋值给mContentParent,这时候我们再回过头来看我们最开始PhoneWindow中的setContentView方法。

    @Override
        public void setContentView(int layoutResID) {
            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 {
            //通过LayoutInflater将我们Activity的布局文件,加载到FrameLayout中
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    

    当我们加载完系统的FrameLayout,并赋值到mContentParent中,后面还是通过LayoutInflater将我们Activity传入的布局文件加载到FrameLayout中。这时,我们自己定义的布局文件就完成了加入到手机的页面中。

    细节分析

    1.requestWindowFeature(Window.FEATURE_NO_TITLE)需要在setContentView之前调用

    我们平常设置我们的Activity全屏的时候,常用的一段代码就是:

    requestWindowFeature(Window.FEATURE_NO_TITLE);
    

    而且这段代码需要在setContentView之前调用,这里就可以连起来,我们在setContentView之前调用requestWindowFeature(Window.FEATURE_NO_TITLE);改变了feature属性,然后在setContentView的时候,再读取feature属性,发现需要全屏,则使用的是全屏的布局文件,不包含标题栏。

    //Activity中最后设置了mLocalFeatures
    public boolean requestFeature(int featureId) {
            final int flag = 1<<featureId;
            mFeatures |= flag;
            mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;
            return (mFeatures&flag) != 0;
        }
        
        //PhoneWindow中,feature读取的就是mLocalFeatures
        protected final int getLocalFeatures()
        {
            return mLocalFeatures;
        }
    

    从上面的代码也验证了我们的猜想。

    2.setContentView在不同Activity的差异

    我们来看一下android.app.Activity和android.support.v7.app.AppCompatActivity的差异

    android.app.Activity.class
    public void setContentView(@LayoutRes int layoutResID) {
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
        }
      android.support.v7.app.AppCompatActivity.class  
        @Override
        public void setContentView(@LayoutRes int layoutResID) {
            getDelegate().setContentView(layoutResID);
        }
    

    可以看到,v7包下并不是直接使用PhoneWindowsetContentView方法。而是使用了一个代理。

    @NonNull
        public AppCompatDelegate getDelegate() {
            if (mDelegate == null) {
                mDelegate = AppCompatDelegate.create(this, this);
            }
            return mDelegate;
        }
        
        private static AppCompatDelegate create(Context context, Window window,
                AppCompatCallback callback) {
            if (Build.VERSION.SDK_INT >= 24) {
                return new AppCompatDelegateImplN(context, window, callback);
            } else if (Build.VERSION.SDK_INT >= 23) {
                return new AppCompatDelegateImplV23(context, window, callback);
            } else if (Build.VERSION.SDK_INT >= 14) {
                return new AppCompatDelegateImplV14(context, window, callback);
            } else if (Build.VERSION.SDK_INT >= 11) {
                return new AppCompatDelegateImplV11(context, window, callback);
            } else {
                return new AppCompatDelegateImplV9(context, window, callback);
            }
        }
    

    果然,Goolge爸爸为了适配费尽心机~,最后我们会发现,上面的都是子类父类的关系,AppCompatDelegateImplN-> AppCompatDelegateImplV23-> AppCompatDelegateImplV14-> AppCompatDelegateImplV11-> AppCompatDelegateImplV9,所以这里调用的setContentView都是使用的AppCompatDelegateImplV9中的方法。

    @Override
        public void setContentView(int resId) {
            ensureSubDecor();
            ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
            contentParent.removeAllViews();
            LayoutInflater.from(mContext).inflate(resId, contentParent);
            mOriginalWindowCallback.onContentChanged();
        }
    

    可以看到,原理上大同小异,只不过,这里不是由Window发起的,而是换了一种方式。

    总结

    本篇博客主要分析的是setContentView的源码,这里回顾一下前面分析得到的过程。

    1.实质调用的是window的setContentView方法,window是在Activity的attach方法里创建的,实现类是PhoneWindow
    2.首先创建DecorView,实质就是一个FrameLayout
    3.根据Feature(例如是否有标题栏)选择一个系统布局文件,然后加入到DecorView中。
    4.默认是一个常规的LinearLayout嵌套FrameLayout,而FrameLayout就是我们所熟知的id为content的副布局。
    5.最后通过LayoutInflater将我们自己创建的Activity布局文件加入到刚才那个名为content的FrameLayout中。

    通过这次分析我们对下图,也就是我们常见的一个Android页面层级关系有了一个很深的认识,当然我们也会发现LayoutInflater这个我们常用类的重要性,下一篇博客会对该类进行分析。

    层级关系

    推荐&学习&参考博客

    从setContentView方法分析Android加载布局流程
    自定义View(七)-View的工作原理- Activity的布局加载
    Android setContentView 源码解析

    相关文章

      网友评论

        本文标题:【重拾View(一)】——setContentView()源码解

        本文链接:https://www.haomeiwen.com/subject/rpdsiftx.html