美文网首页
基于9.0的setContentView源码分析

基于9.0的setContentView源码分析

作者: InBinfen | 来源:发表于2018-09-19 17:02 被阅读0次

    分析

    一、AppCompatActivity的setContenView()

      现在我们一般都会继承与AppCompatActivity来创建Activity,so,一如既往,直接点击setContentView来查看。
    类:AppCompatActivity:

       public void setContentView(@LayoutRes int layoutResID) {
            this.getDelegate().setContentView(layoutResID);
        }
    

      直接点击getDelegate()方法看是调用哪个对象:

        @NonNull
        public AppCompatDelegate getDelegate() {
            if (this.mDelegate == null) {
                this.mDelegate = AppCompatDelegate.create(this, this);
            }
    
            return this.mDelegate;
        }
    
        public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
            return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
        }
    
        AppCompatDelegateImpl(Context context, Window window, AppCompatCallback callback) {
            this.mContext = context;
            this.mWindow = window;
            this.mAppCompatCallback = callback;
            this.mOriginalWindowCallback = this.mWindow.getCallback();
            if (this.mOriginalWindowCallback instanceof AppCompatDelegateImpl.AppCompatWindowCallback) {
                throw new IllegalStateException("AppCompat has already installed itself into the Window");
            } else {
                this.mAppCompatWindowCallback = new AppCompatDelegateImpl.AppCompatWindowCallback(this.mOriginalWindowCallback);
                this.mWindow.setCallback(this.mAppCompatWindowCallback);
                TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, (AttributeSet)null, sWindowBackgroundStyleable);
                Drawable winBg = a.getDrawableIfKnown(0);
                if (winBg != null) {
                    this.mWindow.setBackgroundDrawable(winBg);
                }
    
                a.recycle();
            }
        }
    

      一路追踪过来,原来调用的是AppCompatDelegateImpl的setContentView()方法,直接搜索:
    类:AppCompatDelegateImpl:

        public void setContentView(int resId) {
            this.ensureSubDecor();
            ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
            contentParent.removeAllViews();
            LayoutInflater.from(this.mContext).inflate(resId, contentParent);
            this.mOriginalWindowCallback.onContentChanged();
        }
    

      基本上就是通过LayoutInflater执行inflate()方法把resId添加到contentParent之下。你会觉得就把布局inflate()完事了?由于contentParent是经过mSubDecor执行findViewById()来赋值的,我们可以看一下mSubDecor这个值是如何赋值的,可以通过搜索 mSubDecor = 得到如下方法:
    类:AppCompatDelegateImpl:

        private void ensureSubDecor() {
            if (!this.mSubDecorInstalled) {
                this.mSubDecor = this.createSubDecor();   //注释1
                CharSequence title = this.getTitle();
                if (!TextUtils.isEmpty(title)) {
                    if (this.mDecorContentParent != null) {
                        this.mDecorContentParent.setWindowTitle(title);
                    } else if (this.peekSupportActionBar() != null) {
                        this.peekSupportActionBar().setWindowTitle(title);
                    } else if (this.mTitleView != null) {
                        this.mTitleView.setText(title);
                    }
                }
    
                this.applyFixedSizeWindow();
                this.onSubDecorInstalled(this.mSubDecor);
                this.mSubDecorInstalled = true;
                AppCompatDelegateImpl.PanelFeatureState st = this.getPanelState(0, false);
                if (!this.mIsDestroyed && (st == null || st.menu == null)) {
                    this.invalidatePanelMenu(108);
                }
            }
    
        }
    

      从注释1中是调用了createSubDecor()方法来创建mSubDecor的,继续往下看createSubDecor()。
    类:AppCompatDelegateImpl:

      private ViewGroup createSubDecor() {
           //获取设置的主题属性
            TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
          if (!a.hasValue(styleable.AppCompatTheme_windowActionBar)) {    //注释1
                a.recycle();
                throw new IllegalStateException("You need to use a Theme.AppCompat theme (or descendant) with this activity.");
            } else {
                //开始根据属性来设置主题
              if (a.getBoolean(styleable.AppCompatTheme_windowNoTitle, false)) {
                    this.requestWindowFeature(1);
                } else if (a.getBoolean(styleable.AppCompatTheme_windowActionBar, 
                  false)) {
                    this.requestWindowFeature(108);
                }
                省略 ......
                this.mWindow.getDecorView();  //注释2
                LayoutInflater inflater = LayoutInflater.from(this.mContext);
                ViewGroup subDecor = null;
                 
               //根据上面调用的requestWindowFeature()
               //来决定mWindowNoTitle的值,然后对应进入判断条件来生成subDecor。
             if (!this.mWindowNoTitle) {
                    if (this.mIsFloating) {
                        subDecor = (ViewGroup)inflater.inflate(layout.abc_dialog_title_material, (ViewGroup)null);
                        this.mHasActionBar = this.mOverlayActionBar = false;
                    } else if (this.mHasActionBar) {
              
                          省略......
                        subDecor = (ViewGroup)LayoutInflater.from((Context)themedContext).inflate(layout.abc_screen_toolbar, (ViewGroup)null);
                        this.mDecorContentParent = (DecorContentParent)subDecor.findViewById(id.decor_content_parent);
                        this.mDecorContentParent.setWindowCallback(this.getWindowCallback());
    
                          省略......
           
                    }
                } else {
                    if (this.mOverlayActionMode) {
                        subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple_overlay_action_mode, (ViewGroup)null);
                    } else {
                        subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple, (ViewGroup)null);
                    }
                }
    
                         省略......
     
              //把DecorView添加到Window上 并且返回DecorView
              if (subDecor == null) {
                    throw new IllegalArgumentException("AppCompat does not support the current theme features: { windowActionBar: " + this.mHasActionBar + ", windowActionBarOverlay: " + this.mOverlayActionBar + ", android:windowIsFloating: " + this.mIsFloating + ", windowActionModeOverlay: " + this.mOverlayActionMode + ", windowNoTitle: " + this.mWindowNoTitle + " }");
                } else {
                   省略......
                    ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);
           
                     省略......  
                    this.mWindow.setContentView(subDecor);
                    contentView.setAttachListener(new OnAttachListener() {
                        public void onAttachedFromWindow() {
                        }
    
                        public void onDetachedFromWindow() {
                            AppCompatDelegateImpl.this.dismissPopups();
                        }
                    });
                    return subDecor;
                }
         }
    }
    

      经过createSubDecor()的执行,生成了DecorView,在生成DecorView的时候会根据主题属性来加载一个根部局,比如:abc_dialog_title_material之类的,随后把DecorView给添加到Window(PhoneWindow是实现类)之上,最后返回DecorView。
    在setConteView()方法中再通过DecorView对象mSubDecor来findViewById(16908290)来获取到内容根部局(此根部局是内容根部局,是依附在成DecorView下的一个布局节点而不是成DecorView的根部局。即根部局包含内容根部局)。
      最后再通过 LayoutInflater来把我们设置的布局添加到contenParent之下: LayoutInflater.from(this.mContext).inflate(resId, contentParent);

    Activity的setContenView()

      其实AppCompatActivity也是间接继承了Activity的,所以可以直接在Activity中查看setContentView()的方法,我们可以找到更多的细节。
    类:Activity

        public void setContentView(@LayoutRes int layoutResID) {
            getWindow().setContentView(layoutResID);   //注释1
            initWindowDecorActionBar();
        }
    

    这个getWindow()是一个Window类型的:

      public Window getWindow() {
            return mWindow;
        }
    

      可以搜索一下这个mWindow的赋值:

      final void attach(Context context, ActivityThread aThread,
                Instrumentation instr, IBinder token, int ident,
                Application application, Intent intent, ActivityInfo info,
                CharSequence title, Activity parent, String id,
                NonConfigurationInstances lastNonConfigurationInstances,
                Configuration config, String referrer, IVoiceInteractor voiceInteractor,
                Window window, ActivityConfigCallback activityConfigCallback) {
    
             ......
     
            mWindow = new PhoneWindow(this, window, activityConfigCallback);
    
             ......
    
    

      在attach()方法内直接给mWindow new了一个PhoneWindow对象,所以直接找PhoneWindow的setContentView()方法。
    类:PhoneWindow

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

      值得注意的是,这个mContentParent代表的是这个Activity的根部局,是一个VuiewGround类型的,由于我们要打开的Activity还没完全打开显示,布局文件也没有加载,所以此时mContentParent还是为null,, 判断条件成立,执行注释1的代码:

     private void installDecor() {
    
            ......
    
            if (mDecor == null) {
                mDecor = generateDecor(-1);    //注释1
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                mDecor.setIsRootNamespace(true);
                if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                    mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
                }
            } else {
                mDecor.setWindow(this);
            }
    
           ......
    
    }
    

      由于界面布局还未展示mDecor依然为null,判断条件成立,所以注释1的代码会执行起来,来给mDecor赋值:

        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());
                    if (mTheme != -1) {
                        context.setTheme(mTheme);
                    }
                }
            } else {
                context = getContext();
            }
            return new DecorView(context, featureId, this, getAttributes());
        }
    

      可以看到直接new了一个DecorView对象。继续返回installDecor()方法:

     private void installDecor() {
    
            ......
    
            if (mDecor == null) {
                mDecor = generateDecor(-1);    //注释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);  //注释2
                     ......
    
             }
    
           ......
    
    }
    

      接着看注释2的方法代码:

     protected ViewGroup generateLayout(DecorView decor) {
                //获取设置Window的属性
               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);
            }
            //获取屏幕设置的大小属性
            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);
            }
           //判断设置ActionBar、屏幕标题等等的属性,设置相对应的flag
           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);
            }
    
            ......
    
    // Inflate the window decor.
    //根据设置的feature来选择Activity的根部局
            int layoutResource;
            int features = getLocalFeatures();     //注释1
            // 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 {
                // Embedded, so no decoration is needed.
                layoutResource = R.layout.screen_simple;    //注释2
                // System.out.println("Simple!");
            }
    
            mDecor.startChanging();
              //往DecorView加载根部局
            mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    
             //找到内容布局:不管选择哪个根部局,内容布局id都设置为ID_ANDROID_CONTENT这个参数值
            ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);   //注释3 
    
       ...
    
             //设置DecorView下的背景
            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;
    
    }
    

      一开始通过getWindowStyle()获取Window的属性设置,随后根据相关属性设置了相对应的屏幕大小,根部局、背景等。在注释1开始,DecorView在匹配对应的根部局,一直直到注释2确定好了根部局为R.layout.screen_simple,往下执行了 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)语句,把根部局添加到了DecorView中。我们可以先看看根部局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。而FrameLayout的id为content,正好是内容布局的id,这时也确切知道其实我们所添加的布局进去的时候,是添加到FrameLayout的节点之下的。
      经过把根部局添加到decorView上,而DecorView是依附于window之上的(记住PhoneWindow是Window的实现类),然后直接通过findViewById寻找内容根部局,然后再设置背景、frame(主要体现在padding的设置)、Z轴(视觉体现为立体感),是否剪切试图等,最后返回了内容根部局contentParent。
    再回到setContenView()方法中:
    类:PhoneWindow

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

      经过注释1的代码执行,给mContentParent赋值了,然后执行了注释2: mLayoutInflater.inflate(layoutResID, mContentParent); 这语句,把我们的布局添加到了mContentParent之下。

    总结

      分析完AppCompatActivity和Activity的setContentView()的各自源码就会发现其实两个实现的逻辑是一样的,区别不大,都是利用PhoneWindow来完成DecoreView来加载根部局,在根部局下有着一个内容根部局,我们在调用setContentView(layoutId)的时候,把layoutId给添加到这个内容根部局下。
    经过代码逻辑梳理之后,我们可以依次来画出手机屏幕和我们的布局到底是一个怎样的层级关系,如下图所示:


    setContView屏幕层级关系.png

    相关文章

      网友评论

          本文标题:基于9.0的setContentView源码分析

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