您的位置:首页 > 移动开发 > Android开发

Android源码解析Activity#setContentView()方法

2016-07-27 11:13 721 查看
在Activity初始化的过程中,会调用Activity的attach方法,在该方法中会创建一个PhoneWindow的实例,将其作为Activity的mWindow成员变量。

在执行完了Activity#attach()方法之后,会执行Activity#onCreate()方法。

我们在Activity#onCreate()方法中会就调用setContentView()方法,我们将一个Layout的资源ID传入该方法,调用了该方法之后就将layout资源转换成ViewGroup了,之后就可以调用findViewById()查找ViewGroup中的各种View。

一图胜千言

为了让大家更清晰地理顺代码的调用过程,我做了一张图,如下所示:



我在上图中每一步都设置了一个超链接,如下图所示:



但是Markdown中内嵌的SVG不支持超链接,如果想看一下每一步代码在Android源码中执行的位置,可以用浏览器打开链接 https://ispring.github.io/svg/setContentView_zh.svg,然后单击每一步的超链接就可以了。

源码解析

Activity#setContentView()源码如下所示:

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


首先通过
getWindow()
方法得到了mWindow,其实它是一个PhoneWindow类型的对象,我们之前提到PhoneWindow是在Activity#attach()方法中被初始化的。然后调用了PhoneWindow#setContentView()方法。

PhoneWindow#setContentView()源码如下所示:

public void setContentView(int layoutResID) {
if (mContentParent == null) {
//installDecor()方法会调用generateDecor()和generateLayout()方法
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();
}
}


该方法传入了一个Activity的layout资源layoutResID,该资源就代表了Activity中的content内容,理解了此处content所代表的意义之后,要说一下PhoneWindow中有两个比较重要的成员变量mContentParent和mContentRoot,这两个字段都是ViewGroup类型。mContentParent从字面上看就是content的parent,即Activity的layout是要放到mContentParent中去的。mContentRoot从字面上看就是content的root,即content的根结点,一般情况下,mContentParent是放置在mContentRoot中的。即mContentRoot > mContentParent > content。关于如何实例化mContentRoot 、mContentParent 和 content,后面会详细说明。

PhoneWindow#setContentView()方法中会调用installDecor()方法。

PhoneWindow#installDecor()方法的源码如下所示:

private void installDecor() {
if (mDecor == null) {
//创建DecorView
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
//根据features生成Activity的layout资源的父节点
mContentParent = generateLayout(mDecor);
...
}
}


installDecor()中会调用generateDecor()和generateLayout()方法。

PhoneWindow#generateDecor()会创建DecorView,其源码如下所示:

protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}


执行完了generateDecor()之后,就会执行generateLayout(),其源码如下所示:

protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.

TypedArray a = getWindowStyle();

...
//根据Theme和Style计算features

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);
}

if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}

if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
requestFeature(FEATURE_ACTION_MODE_OVERLAY);
}

if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
requestFeature(FEATURE_SWIPE_TO_DISMISS);
}

...

// Inflate the window decor.

//根据features计算layoutResource,此处的layoutResource表示要插入到DecorView中的子节点
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;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
...
layoutResource = R.layout.screen_title_icons;
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_progress;
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
...
layoutResource = R.layout.screen_custom_title;
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
...
layoutResource = R.layout.screen_action_bar 或 R.layout.screen_title;
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
...
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// 不需要装饰,直接用最简单的资源文件即可
layoutResource = R.layout.screen_simple;
}

mDecor.startChanging();

//将计算到的layoutResource转换为实际的View,并将其插入到DecorView中,将其作为成员变量mContentRoot
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;

//计算出的layoutResource对应着mContentRoot,它其中肯定有一个ID叫做ID_ANDROID_CONTENT的ViewGroup
//从中找到该Group,赋值给contentParent,contentParent就表示我们Actiivy的layout资源文件的父节点
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...

mDecor.finishChanging();

//contentParent会赋值给成员变量mContentParent
return contentParent;
}


generateDecor()这个方法从字面上看就知道是要产生layout,那么要产生哪些layout呢?其实这个方法就是为了创建我们之前说的mContentRoot和mContentParent。generateDecor()方法的返回值就是mContentParent,我们具体分析一下代码的执行过程。

generateDecor()方法会首先根据Theme和Style,会多次调用requestFeature()方法,计算特性features,点此查看对应源码。

然后会根据计算出的特性features,要计算一个layout资源layoutResource,layoutResource就是对应着mContentRoot。features具备的特性不同,layoutResource的值也就不同,layoutResource的可能取值有:

com.android.internal.R.layout.screen_swipe_dismiss.xml

com.android.internal.R.layout.screen_title_icons

com.android.internal.R.layout.screen_progress

com.android.internal.R.layout.screen_custom_title

com.android.internal.R.layout.screen_action_bar

com.android.internal.R.layout.screen_title

com.android.internal.R.layout.screen_simple_overlay_action_mode

com.android.internal.R.layout.screen_simple等。

根据features计算layoutResource的具体逻辑可参见源码

在计算出layoutResource之后,会将计算到的layoutResource转换为实际的View,将其作为成员变量mContentRoot,并将其插入到DecorView中,也就是说mDecor是mContentRoot的父节点,此时PhoneWindow中的View树:mDecor -> mContentRoot

之后会调用findViewById()方法查找ID为ID_ANDROID_CONTENT的View。PhoneWindow是继承自Window的,PhoneWindow的findViewById()是在Window中定义,其源码如下所示:

public View findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}


由此我们可以看出PhoneWindow的findViewById()方法其实就是从mDecor中查找View。ID_ANDROID_CONTENT也是在Window类中定义的,如下所示:

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;


其实无论上面计算的layoutResource是哪个layout,该layout中都有一个id为
content
的ViewGroup。

举例来说,我们有一个MainActivity,其直接继承自Activity,Application和MainActivity都没有设置任何Theme和Style,当App运行在Android 6.0系统上的时候,得到的layoutResource是com.android.internal.R.layout.screen_action_bar,具体如下所示:

<?xml version="1.0" encoding="utf-8"?>
<com.android.internal.widget.ActionBarOverlayLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/decor_content_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:splitMotionEvents="false"
android:theme="?attr/actionBarTheme">
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.android.internal.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:transitionName="android:action_bar"
android:touchscreenBlocksFocus="true"
android:gravity="top">
<com.android.internal.widget.ActionBarView
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/actionBarStyle" />
<com.android.internal.widget.ActionBarContextView
android:id="@+id/action_context_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
style="?attr/actionModeStyle" />
</com.android.internal.widget.ActionBarContainer>
<com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/actionBarSplitStyle"
android:visibility="gone"
android:touchscreenBlocksFocus="true"
android:gravity="center"/>
</com.android.internal.widget.ActionBarOverlayLayout>


我们可以看到com.android.internal.R.layout.screen_action_bar中有一个
android:id="@android:id/content"
的FrameLayout,该FrameLayout就是
mContentParent


当generateLayout()执行完毕后,installDecor()方法也就执行完了。

这样PhoneWindow#setContentView(layoutResID)中会执行下面的代码:

mLayoutInflater.inflate(layoutResID, mContentParent);


此处的layoutResID是我们Activity的资源文件,比如R.layout.activity_main,此处将该文件inflate成具体的View,并将其放入到mContentParent中。

之后还会通过代码
cb.onContentChanged()
触发内容变化回调的执行。

这样Activity#setContentView()也就执行完了,假设我们的R.layout.activity_main中只有一个RelativeLayout,那么通过hierarchyviewer查看到的View树如下所示:



View树的根结点是
PhoneWindow$DecorView
类型的,此处的$表示DecorView是PhoneWindow的一个内部类,该DecorView也就是PhoneWindow中的字段mDecor。screen_action_bar定义的ActionBarOverlayLayout就是PhoneWindow的mContentRoot,其是mDecor的子节点。screen_action_bar中内部id为
content
的FrameLayout就是PhoneWindow中的mContentParent,其是我们Activity的layout的父节点。

我们回过头来再思考一下DecorView这个类,英文decor的意思其实就是装饰,也就是说这是一个起到装饰的类,除了装饰,DecorView还要作为我们自己layout的容器。那到底装饰了什么东西呢?我个人认为,上图中除了绿色文本标识的其他的View都可以看做装饰,因为这些View是根据features特性的不同而创建的,如果需要有Action Bar的特性,那么就装饰上一个View作为Action Bar的容器;如果不需要Action Bar,但需要显示title,那么就装饰上一个View作为title的容器,等等。

最后我们再用一张图理顺Activity、PhoneWindow与View树之间的关系。



希望本文对大家理解setContentView()方法有所帮助!

相关阅读:

[GitHub开源]Android自定义View实现微信打飞机游戏

Android中View的量算、布局及绘图机制
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: