基于9.0的setContentView源码分析

分析

一、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
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容