setContentView学习(一)

setContentView流程分两种情况,一种是继承自Activity的情况,另一种是继承自AppCompatActivity的情况,下面分别介绍。

先说继承自Activity的情况,源码为android-30

public class Activity extends ContextThemeWrapper {

    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

其中getWindow()返回的是在Activity中定义的Window对象,而Window是一个抽象类,setContentView又是抽象方法,所以必须找到Window的实现类

/**
 * The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {

通过Window类的介绍可以看到,Window只有一个实现类:PhoneWindow,另外通过模拟机调试android-30的源码也可以定位到Window的实现类是 PhoneWindow,看下具体实现

public class PhoneWindow extends Window implements MenuBuilder.Callback {

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
           ...
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...
        mContentParentExplicitlySet = true;
    }

可以看到我们平时写在Activity中的布局被加载到了mContentParent中,那么mContentParent又是哪里创建的,接下来会提到,核心流程主要是看 installDecor()

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            ......
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ......
        }
}

通过上面的代码可以看到,setContentView的主要核心流程

1. 创建生成顶层View DecorView

2. 以DecorView为父容器,解析并生成xml布局

其中 generateDecor()的流程比较简单,直接new了一个DecorView并返回,下面看geraterLayout()方法的流程,可以看到generateLayout()方法生成的布局就是mContentParent

注意其传参为上面创建的DecorView

protected ViewGroup generateLayout(DecorView decor) {

        TypedArray a = getWindowStyle();
        ......
        int layoutResource;
        int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            ......
        } else if {
            ......
        } 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;
        }
        ......
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ......
        return contentParent;

}

这里会根据不同的feature选择不同的系统xml布局,并且最终会返回id为:ID_ANDROID_CONTENT的父布局,ID_ANDROID_CONTENT的值为:

com.android.internal.R.id.content

我们就以最简单的情况为例,也就是最后一种情况,来看下系统布局 screen_simple.xml

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

会通过DecorView的 onResourcesLoaded()方法加载上面的布局,注意:其中FrameLayout的id

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        ......
        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            ......
        } else {
            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

同样是使用LayoutInflater解析加载布局,最后通过addView()将解析后的布局添加到DecorView

下面通过一个流程图来对以上流程做一个总结

​​​​​​​

附图:继承自Activity的布局层级图


​​​​​​​

接下来看下继承自AppCompatActivity的流程

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

    /**
     * @return The {@link AppCompatDelegate} being used by this Activity.
     */
    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
class AppCompatDelegateImpl extends AppCompatDelegate
        implements MenuBuilder.Callback, LayoutInflater.Factory2 {
    
    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }
    private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            mSubDecor = createSubDecor();
            ......
        }
    }
}

重点看createSubDecor(),看名字也能猜出来,这里应该是创建DecorView

private ViewGroup createSubDecor() {
        ......
        ensureWindow();
        mWindow.getDecorView();
        ......
        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;
        if (!mWindowNoTitle) {
            if (mIsFloating) {
                ......
            } else if (mHasActionBar) {
               ......
                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                        .inflate(R.layout.abc_screen_toolbar, null);
                mDecorContentParent = (DecorContentParent) subDecor
                        .findViewById(R.id.decor_content_parent);
                mDecorContentParent.setWindowCallback(getWindowCallback());
               ......
            }
        } else {
            ......
        }
        ......
        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) {
            ......
            windowContentView.setId(View.NO_ID);
            contentView.setId(android.R.id.content);
            ......
        }
        mWindow.setContentView(subDecor);
        ......
        return subDecor;
    }

说几个需要注意的点:

1.  mWindow.getDecorView();

2. windowContentView.setId(View.NO_ID);

    contentView.setId(android.R.id.content);

3.  mWindow.setContentView(subDecor);

先说mWindow.getDecorView(),这里的mWindow依然是 PhoneWindow,其中getDecorView()方法里会调用 installDecor()方法,这个方法前面已经说的比较详细了。

接下来看下面这句,这里加载了一个系统布局文件:R.layout.abc_screen_toolbar

subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                        .inflate(R.layout.abc_screen_toolbar, null);

来看下这个布局:adc_screen_toolbar.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.ActionBarOverlayLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/decor_content_parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true">

    <include layout="@layout/abc_screen_content_include"/>

    <androidx.appcompat.widget.ActionBarContainer
        android:id="@+id/action_bar_container"
        android:layout_width="match_parentå"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        style="?attr/actionBarStyle"
        android:touchscreenBlocksFocus="true"
        android:keyboardNavigationCluster="true"
        android:gravity="top">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/action_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:navigationContentDescription="@string/abc_action_bar_up_description"
            style="?attr/toolbarStyle"/>

        <androidx.appcompat.widget.ActionBarContextView
            android:id="@+id/action_context_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="gone"
            android:theme="?attr/actionBarTheme"
            style="?attr/actionModeStyle"/>

    </androidx.appcompat.widget.ActionBarContainer>

</androidx.appcompat.widget.ActionBarOverlayLayout>

adb_screen_content_include.xml

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
    <androidx.appcompat.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" />
</merge>
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) {
     windowContentView.setId(View.NO_ID);
     contentView.setId(android.R.id.content);
  }

 其中 windowContentView就是继承自Activity时,id为content的FrameLayout,contentView就是上面布局文件里的ContentFrameLayout,注意看下面的两行:

1. windowContentView.setId(View.NO_ID);-->把原来id为content的FrameLayout的id置为空

2. contentView.setId(android.R.id.content);-->把ContentFrameLayout的id设置为content

这样一来adb_screen_content_include.xml里的ContentFrameLayout就相当于继承自Activity时的id为content的FrameLayout,他们的作用是一样的了。

最后看下 mWindow.setContentView(subDecor);这里就是直接调用PhoneWindow的setContentView()方法,流程和上面继承自Activity时的流程又一样了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值