四大组件流程分析之Activity的setContentView函数流程分析

507 阅读4分钟

「这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战」。

在Activity的初始化过程中,经常会用到一个函数setContentView,来设置Activity所需要绘制的layout,今天我们就来分析一下,这个函数的具体是做了什么 首先查看这个函数的代码

Activity.java
public void setContentView(@LayoutRes int layoutResID) {
    // 调用getWindow函数的setContentView函数
    // getWindow()会返回Activity的全局参数mWindow,从该类的attach函数中可知,这边是一个PhoneWindow对象
    getWindow().setContentView(layoutResID);
    // 根据Activity的相应条件,确认是否需要初始化一个ActionBar对象
    // 若需要,则直接初始化WindowDecorActionBar对象
    initWindowDecorActionBar();
}

public Window getWindow() {
    return mWindow;
}

可以看到,在这个setContentView函数(在Activity类中有好几个重载函数,此处我们只需要分析其中的一个即可,剩余的函数均是一样的分析)中,

  1. 首先会调用getWindow函数的setContentView函数,而getWindow函数主要是返回一个mWindow全局参数,mWindow的初始化在Activity.attach函数中,为一个PhoneWindow对象,而这个函数的调用是当系统初始化Activity的时候,会调用ActivityThread对象的attach函数,从而会调用Activity.attach函数
  2. 调用initWindowDecorActionBar函数,根据Activity的相应条件,确认是否需要初始化一个ActionBar对象,若需要,则直接初始化WindowDecorActionBar对象 先附上PhoneWindow的类图
classDiagram
Window <|-- PhoneWindow
MenuBuilder *-- Callback
Callback <|-- PhoneWindow
<<interface>> Callback
<<abstract>> Window
class Window {
    +setContentView(@LayoutRes int)
    +setContentView(View)
    +setContentView(View, ViewGroup.LayoutParams)
    +addContentView(View, ViewGroup.LayoutParams)
    +clearContentView()
    +getLayoutInflater()
    +getDecorView()
    +setCallback(Callback callback)
}
class PhoneWindow {
    +setContentView(int layoutResID)
    +setContentView(View, ViewGroup.LayoutParams)
    +addContentView(View, ViewGroup.LayoutParams)
}

值得注意的是,Window是一个抽象类,PhoneWindow是其唯一的子类,它的实例一般被添加到WindowManager中进行管理。

PhoneWindow的setContentView函数

查看其实现

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函数初始化一个Decor View
        installDecor();
    }

    // ...... 本篇不关注代码省略
    {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    // ......
    mContentParentExplicitlySet = true;
}

可以看到,在这个函数中,调用installDecor函数来初始化Decor View

installDecor函数初始化DecorView

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        // 1. 生成和配置DecorView
        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) {
        // 2. 初始化mContentParent对象
        mContentParent = generateLayout(mDecor);
        // ...... 与此篇分析无关代码暂时省略
    }
}

可以看到,在这个函数中,首先通过generateDecor函数初始化一个DecorView,此后调用generateLayout来初始化mContentParent

generateDecor函数初始化一个DecorView

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

DecorView(Context context, int featureId, PhoneWindow window,
            WindowManager.LayoutParams params) {
    super(context);
    mFeatureId = featureId;

    mShowInterpolator = AnimationUtils.loadInterpolator(context,
            android.R.interpolator.linear_out_slow_in);
    mHideInterpolator = AnimationUtils.loadInterpolator(context,
            android.R.interpolator.fast_out_linear_in);

    mBarEnterExitDuration = context.getResources().getInteger(
            R.integer.dock_enter_exit_duration);
    mForceWindowDrawsBarBackgrounds = context.getResources().getBoolean(
            R.bool.config_forceWindowDrawsStatusBarBackground)
            && context.getApplicationInfo().targetSdkVersion >= N;
    mSemiTransparentBarColor = context.getResources().getColor(
            R.color.system_bar_background_semi_transparent, null /* theme */);

    updateAvailableWidth();

    setWindow(window);

    updateLogTag(params);

    mResizeShadowSize = context.getResources().getDimensionPixelSize(
            R.dimen.resize_shadow_size);
    initResizingPaints();

    mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK);
}

此处会初始化一个DecorView对象,从这个对象的类图来看

classDiagram
FrameLayout <|-- DecorView
RootViewSurfaceTaker <|-- DecorView
WindowCallbacks <|-- DecorView
<<interface>> RootViewSurfaceTaker
<<interface>> WindowCallbacks

DecorView继承自FrameLayout

generateLayout来初始化mContentParent

此后调用generateLayout函数来初始化mContentParent参数

protected ViewGroup generateLayout(DecorView decor) {
    // ...... 获取和设置Window属性
   
    WindowManager.LayoutParams params = getAttributes();

    // Inflate the window decor.
    int layoutResource;
    int features = getLocalFeatures();
    // 根据feature来获取当前Activity的对应的DecorView的layout
    // ...... 此处我们不做过多的分析,仅获取默认的layout分析,其他情景下的layout可以自行去分析下
    {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
    }

    mDecor.startChanging();
    // 将上述layout资源对应的View存放到mDecor中
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    // public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
    // 此处获取对应的layout中的id为content对应的ViewGroup对象
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }
    // ......
    return contentParent;
}
screen_sample.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>

从上述代码分析,无论是feature对应的是哪一种layout,其必定需要包含一个id为content的ViewGroup,否则此处会直接抛出异常,最终generateLayout函数返回的是一个id为content对应的ViewGroup对象,而从上述的xml文件中可以看到,此处是一个FrameLayout对象。

总结

同时,在上述的代码中也可以明确地看出,在这个过程中,

  1. 首先mDecor是一个DecorView对象,其继承自FrameLayout,它是通过generateDecor函数来生成的
  2. 在generateLayout函数中,通过Activity对象的属性值选取对应的,来选取对应类型的layout,然后将这整个的Layout添加到上述生成的DecorView中,当然这个过程是直接添加还是间接添加由系统来决定
  3. 初始化mContentParent参数为上述获取的layout中的id为content对应的ViewGroup对象,由此也可知,无论Activity的属性如何,在其对应的layout中必有一个id为content对应的ViewGroup对象

设置用户layout

从上述的代码中可以看到,在调用installDecor函数初始化DecorView和mContentParent参数后,就会通过调用LayoutInflater对象的inflate函数来将用户layout添加到mContentParent中

mLayoutInflater.inflate(layoutResID, mContentParent);

此处的不再做详细分析,待后续有机会仔细分析下该流程的具体实现

时序图

按照惯例,附上上述流程的时序图 图片.png