likes
comments
collection
share

从setContentView开始阅读源码

作者站长头像
站长
· 阅读数 7

写在前面的话

setContentView作为Activity的入口,极为适合当作源码练习的第一课。阅读源码当然是比较痛苦的,但是在当下的就业环境里,会阅读源码已经是迫在眉睫。

这张图很直观的展示了Activity,Window,DecorView的关系,也能更好的帮助我们理解setContentView。 从setContentView开始阅读源码

图片来源于网络

正文开始

Activity中 setContentView指向 AppCompatDelegate,而AppCompatDelegate是个抽象类,唯一实现类为: AppCompatDelegateImpl

所以,我们来到AppCompatDelegateImplsetContentView,代码如下:

@Override
public void setContentView(int resId) {
    ensureSubDecor();//确保 DecorView已初始化完毕
    ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);//添加布局
    mAppCompatWindowCallback.getWrapped().onContentChanged();//通知window刷新
}

这个方法 里只有5行代码,我们依次来分析一下:

ensureSubDecor() ,从名字上来看,是确保Decor已经添加了,目的是保证 mSubDecor 对象非空,接下来要用。

mSubDecor 是一个ViewGroup对象,是Activity的跟布局。你或许在面试里经常听到它的另一个称呼:DecorView。 所以,第二行代码就是获取content的Viewgroup,

第三行 清除。确保根布局是干净的,无任何子view.

第四行,添加我们的布局,到content上。

第五行,通过windowCallback,刷新内容。

好了,xml已经显示到窗口上了。

ensureSubDecor

private void ensureSubDecor() {
    //先判断是否已安装过 mSubDecor 
    if (!mSubDecorInstalled) {
        //创建 DecorView,我们稍后来看这个方法的实现
        mSubDecor = createSubDecor();
        
        //这里是判断是否有title,暂且按下不表,我们继续看后面的方法
        // If a title was set before we installed the decor, propagate it now
        CharSequence title = getTitle();
        if (!TextUtils.isEmpty(title)) {
            if (mDecorContentParent != null) {
                mDecorContentParent.setWindowTitle(title);
            } else if (peekSupportActionBar() != null) {
                peekSupportActionBar().setWindowTitle(title);
            } else if (mTitleView != null) {
                mTitleView.setText(title);
            }
        }

        applyFixedSizeWindow();

        onSubDecorInstalled(mSubDecor);

        mSubDecorInstalled = true;

        // 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 (!mIsDestroyed && (st == null || st.menu == null)) {
            invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
        }
    }
}

这个方法暴露的有效信息不多,但是逻辑在这里有一个闭环,即先判断onSubDecorInstalled是否为false,随后在执行完createSubDecoronSubDecorInstalled方法之后,将onSubDecorInstalled置为了true。为了阅读源码方便,我们提炼一下有效代码:

if (!mSubDecorInstalled) {
        mSubDecor = createSubDecor();
        applyFixedSizeWindow();
        onSubDecorInstalled(mSubDecor);
        mSubDecorInstalled = true;
}

所以,下一步,我们来看一下createSubDecoronSubDecorInstalled

createSubDecor

private ViewGroup createSubDecor() {
    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

    if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
        a.recycle();
        throw new IllegalStateException(
                "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
    }

    if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
    } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
        // Don't allow an action bar if there is no title.
        requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
    }
    if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
        requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
    }
    if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
        requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
    }
    mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
    a.recycle();

    // Now let's make sure that the Window has installed its decor by retrieving it
    ensureWindow();
    mWindow.getDecorView();

    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;


    if (!mWindowNoTitle) {
        if (mIsFloating) {
            // If we're floating, inflate the dialog title decor
            subDecor = (ViewGroup) inflater.inflate(
                    R.layout.abc_dialog_title_material, null);

            // Floating windows can never have an action bar, reset the flags
            mHasActionBar = mOverlayActionBar = false;
        } else if (mHasActionBar) {
            /**
             * This needs some explanation. As we can not use the android:theme attribute
             * pre-L, we emulate it by manually creating a LayoutInflater using a
             * ContextThemeWrapper pointing to actionBarTheme.
             */
            TypedValue outValue = new TypedValue();
            mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);

            Context themedContext;
            if (outValue.resourceId != 0) {
                themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
            } else {
                themedContext = mContext;
            }

            // Now inflate the view using the themed context and set it as the content view
            subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                    .inflate(R.layout.abc_screen_toolbar, null);

            mDecorContentParent = (DecorContentParent) subDecor
                    .findViewById(R.id.decor_content_parent);
            mDecorContentParent.setWindowCallback(getWindowCallback());

            /**
             * Propagate features to DecorContentParent
             */
            if (mOverlayActionBar) {
                mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
            }
            if (mFeatureProgress) {
                mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
            }
            if (mFeatureIndeterminateProgress) {
                mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
            }
        }
    } else {
        if (mOverlayActionMode) {
            subDecor = (ViewGroup) inflater.inflate(
                    R.layout.abc_screen_simple_overlay_action_mode, null);
        } else {
            subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
        }

        if (Build.VERSION.SDK_INT >= 21) {
            // If we're running on L or above, we can rely on ViewCompat's
            // setOnApplyWindowInsetsListener
            ViewCompat.setOnApplyWindowInsetsListener(subDecor,
                    new OnApplyWindowInsetsListener() {
                        @Override
                        public WindowInsetsCompat onApplyWindowInsets(View v,
                                WindowInsetsCompat insets) {
                            final int top = insets.getSystemWindowInsetTop();
                            final int newTop = updateStatusGuard(top);

                            if (top != newTop) {
                                insets = insets.replaceSystemWindowInsets(
                                        insets.getSystemWindowInsetLeft(),
                                        newTop,
                                        insets.getSystemWindowInsetRight(),
                                        insets.getSystemWindowInsetBottom());
                            }

                            // Now apply the insets on our view
                            return ViewCompat.onApplyWindowInsets(v, insets);
                        }
                    });
        } else {
            // Else, we need to use our own FitWindowsViewGroup handling
            ((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
                    new FitWindowsViewGroup.OnFitSystemWindowsListener() {
                        @Override
                        public void onFitSystemWindows(Rect insets) {
                            insets.top = updateStatusGuard(insets.top);
                        }
                    });
        }
    }

    if (subDecor == null) {
        throw new IllegalArgumentException(
                "AppCompat does not support the current theme features: { "
                        + "windowActionBar: " + mHasActionBar
                        + ", windowActionBarOverlay: "+ mOverlayActionBar
                        + ", android:windowIsFloating: " + mIsFloating
                        + ", windowActionModeOverlay: " + mOverlayActionMode
                        + ", windowNoTitle: " + mWindowNoTitle
                        + " }");
    }

    if (mDecorContentParent == null) {
        mTitleView = (TextView) subDecor.findViewById(R.id.title);
    }

    // Make the decor optionally fit system windows, like the window's decor
    ViewUtils.makeOptionalFitsSystemWindows(subDecor);

    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
            R.id.action_bar_activity_content);

    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    if (windowContentView != null) {
        // There might be Views already added to the Window's content view so we need to
        // migrate them to our content view
        while (windowContentView.getChildCount() > 0) {
            final View child = windowContentView.getChildAt(0);
            windowContentView.removeViewAt(0);
            contentView.addView(child);
        }

        // Change our content FrameLayout to use the android.R.id.content id.
        // Useful for fragments.
        windowContentView.setId(View.NO_ID);
        contentView.setId(android.R.id.content);

        // The decorContent may have a foreground drawable set (windowContentOverlay).
        // Remove this as we handle it ourselves
        if (windowContentView instanceof FrameLayout) {
            ((FrameLayout) windowContentView).setForeground(null);
        }
    }

    // Now set the Window's content view with the decor
    mWindow.setContentView(subDecor);

    contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
        @Override
        public void onAttachedFromWindow() {}

        @Override
        public void onDetachedFromWindow() {
            dismissPopups();
        }
    });

    return subDecor;
}

从第一行到第23行,这一段看着是不是很熟悉?我们在自定义View里获取自定义属性的时候经常见到,与我们的主线无关,略过。 第26,27这2行有专门的注释:确保window已经安装好了decor

而33行开始一直到119行,这么大的if~else代码块,只干了一件事情,对subDecor 进行赋值。在121行对 subDecor进行了判空,如果为空,抛出异常,我们的逻辑戛然而止。

在137行,有一句注释:确保windowdecor相匹配。

而132行,139行,通过subDecor分别获取了2个view,一个TextView(R.id.title),一个ContentFrameLayout(R.id.action_bar_activity_content)

142行,从window中获取windowContentView (R.id.content)

重点来了!从143行开始,先判断windowContentView 有无子view,如果有,把所有的子view剪贴到subDecor的content上。然后把content的布局id改为R.id.content。这一步执行之后,window的content上没有了子view。

165行,把subdecor添加到window上。

最后,返回subdecor。

把这个方法精简一下,有效代码如下:

private ViewGroup createSubDecor() {
    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
    //省略次要代码
    a.recycle();
    //确保 window 已经安装了 decor
    ensureWindow();
    mWindow.getDecorView();
    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;
    //省略 subDecor 的赋值以及 判空
    if (mDecorContentParent == null) {
        mTitleView = (TextView) subDecor.findViewById(R.id.title);
    }
    //确保 subDecor 与window 相统一
    ViewUtils.makeOptionalFitsSystemWindows(subDecor);
    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
            R.id.action_bar_activity_content);
    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    if (windowContentView != null) {
        while (windowContentView.getChildCount() > 0) {
            final View child = windowContentView.getChildAt(0);
            windowContentView.removeViewAt(0);
            contentView.addView(child);
        }
        windowContentView.setId(View.NO_ID);
        contentView.setId(android.R.id.content);
    }
    mWindow.setContentView(subDecor);
    return subDecor;
    }

applyFixedSizeWindow()

方法相对比较简单,获取到R.id.content,随后设置了内边距,然后重新布局 requestLayout,这一部分与我们的主线关联不大,按下不表。

private void applyFixedSizeWindow() {
    ContentFrameLayout cfl = (ContentFrameLayout) mSubDecor.findViewById(android.R.id.content);

    // This is a bit weird. In the framework, the window sizing attributes control
    // the decor view's size, meaning that any padding is inset for the min/max widths below.
    // We don't control measurement at that level, so we need to workaround it by making sure
    // that the decor view's padding is taken into account.
    final View windowDecor = mWindow.getDecorView();
    cfl.setDecorPadding(windowDecor.getPaddingLeft(),
            windowDecor.getPaddingTop(), windowDecor.getPaddingRight(),
            windowDecor.getPaddingBottom());

    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
    a.getValue(R.styleable.AppCompatTheme_windowMinWidthMajor, cfl.getMinWidthMajor());
    a.getValue(R.styleable.AppCompatTheme_windowMinWidthMinor, cfl.getMinWidthMinor());

    if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMajor)) {
        a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMajor,
                cfl.getFixedWidthMajor());
    }
    if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMinor)) {
        a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMinor,
                cfl.getFixedWidthMinor());
    }
    if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMajor)) {
        a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMajor,
                cfl.getFixedHeightMajor());
    }
    if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMinor)) {
        a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMinor,
                cfl.getFixedHeightMinor());
    }
    a.recycle();

    cfl.requestLayout();
}

onSubDecorInstalled()

void onSubDecorInstalled(ViewGroup subDecor) {}

一个空方法,原来这就是惊喜啊!

写在后面的话

setContentView 作为Activity的入口,其地位不可谓不重要。其中涉及的Actvity,window,DecorView 三者的关系更是在面试中常被问及。理解了setContentView,对于Activity的理解也就更近了一步。

留一个小尾巴:LayoutInflater.from(mContext).inflate(resId, contentParent) 我们下篇文章再见!

转载自:https://juejin.cn/post/7000916643136143391
评论
请登录