美文网首页
FrameWork层源码分析之setContentView的加载

FrameWork层源码分析之setContentView的加载

作者: 暴走的小青春 | 来源:发表于2019-08-04 22:51 被阅读0次

    用activity就必然就会写setContentView,那最终的流程是如何呢?继承于AppCompatActivity和activity其加载的流程也有所区别,具体情况来说下:

    继承于Activity:
      public void setContentView(@LayoutRes int layoutResID) {
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
        }
    

    可以看到是调用了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);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    

    这里的mContentParent第一次是为null的,自然走进了installDecor()方法

     private void installDecor() {
            mForceDecorInstall = false;
            if (mDecor == null) {
                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 = generateLayout(mDecor);
    ...
    

    当mDecor也为空的时候调用了generateDecor方法,decorview就被创建了,重点是后面当mContentParent为null时会走到generateLayout方法,这个方法其实返回的就是

     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;
                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();
            mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
            if (contentParent == null) {
                throw new RuntimeException("Window couldn't find content container view");
            }
    

    这个上面各种features下所对应的布局,这里拿screen_simple来说好了,当然此时这个布局已经被添加到decorview里了
    也就是 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);这句代码,再来看下这个布局
    就是这个布局

    <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>
    

    可以看到返回了contentParent也就是frameLayout,最后看下setonContentview

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

    所以我们的view是被加在framtlayout上的

    也就是decorview ->LinearLayout->frameLayout->自己写的view

    接下来说下appCompatActivity中setContentView的加载流程。

    继承于AppCompatActivity

    //最终走进了AppCompatDelegateImpl的setContentView方法

    
      @Override
        public void setContentView(int resId) {
            ensureSubDecor();
            ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
            contentParent.removeAllViews();
            //将我们的布局id映射成View并且放到contentParent下
            LayoutInflater.from(mContext).inflate(resId, contentParent);
            mOriginalWindowCallback.onContentChanged();
        }
    
    

    这里有一个调用ensureSubDecor的方法

    private ViewGroup createSubDecor() {
            TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
    
            if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
                a.recycle();
                //这里抛出没有用AppCompatTheme主题的错误
                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();
    
            //这里是重点
            mWindow.getDecorView();
    
            final LayoutInflater inflater = LayoutInflater.from(mContext);
            //可以看到其实就是个ViewGroup,我们接着往下看,跟DecorView到底有啥关系
            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);
    
                    ...
                } else if (mHasActionBar) {
                    
                    TypedValue outValue = new TypedValue();
                    mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
    
                    ...
    
                    // 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);
    
                    /**
                     * 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) {
            requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY)会走进来
                    subDecor = (ViewGroup) inflater.inflate(
                            R.layout.abc_screen_simple_overlay_action_mode, null);
                } else {
                    //ok,所以如果这些我们都没设置,默认就走到这里来了,在这里映射出了subDecor,稍后我们来看下这个布局是啥
                    subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
                }
    
                ...
            }
    
            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);
            //这个contentView很重要,是我们布局的父容器,你可以把它直接当成FrameLayout
            final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                    R.id.action_bar_activity_content);
            //看过相关知识的同学应该知道android.R.id.content这个Id在以前是我们布局的父容器的Id
            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);
                }
    
              
                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);
    
            ....
    
            return subDecor;
        }
    
    

    核心代码就在这个里面了,相对于activity的,AppCompatActivity就显得略微复杂一点了。首先对于主题不是mIsFloating来说,也就是正常的,没有actionbar和ttitle的会加载如下布局
    subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
    看下abc_screen_simple的样式

    <android.support.v7.widget.FitWindowsLinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/action_bar_root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:fitsSystemWindows="true">
    
        <android.support.v7.widget.ViewStubCompat
            android:id="@+id/action_mode_bar_stub"
            android:inflatedId="@+id/action_mode_bar"
            android:layout="@layout/abc_action_mode_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
    
          <android.support.v7.widget.ContentFrameLayout
                android:id="@id/action_bar_activity_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:foregroundGravity="fill_horizontal|top"
                android:foreground="?android:attr/windowContentOverlay" />
    
    </android.support.v7.widget.FitWindowsLinearLayout>
    

    可以说是一个FitWindowsLinearLayout嵌套了一个ContentFrameLayout
    然后通过

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

    找到contentView后,由于在一开始执行了 mWindow.getDecorView();
    所以

    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    

    这个content就是原来screen_simple里面的content
    然后执行while循环把我们写的setContentView的布局从原来的父view移除添加到ContentFrameLayout这个布局下面,然后清除原来的布局id
    可以说是个很巧妙的办法
    最后在调用 mWindow.setContentView(subDecor);

    @Override
        public void setContentView(View view, ViewGroup.LayoutParams params) {
            // 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)) {
                view.setLayoutParams(params);
                final Scene newScene = new Scene(mContentParent, view);
                transitionTo(newScene);
            } else {
                mContentParent.addView(view, params);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    

    由于此时mContentParent不为空(前面getgetDecorView执行过了,mContentParent就是screen_simple所解析成的view)
    所以走到了 mContentParent.addView(view, params);

    故AppCompatActivity的层级是

    decorview ->LinearLayout->frameLayout-> FitWindowsLinearLayout-> ContentFrameLayout->自己写的view

    相关文章

      网友评论

          本文标题:FrameWork层源码分析之setContentView的加载

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