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,然后单击每一步的超链接就可以了。
首先通过
PhoneWindow#setContentView()源码如下所示:
该方法传入了一个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()方法的源码如下所示:
installDecor()中会调用generateDecor()和generateLayout()方法。
PhoneWindow#generateDecor()会创建DecorView,其源码如下所示:
执行完了generateDecor()之后,就会执行generateLayout(),其源码如下所示:
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中定义,其源码如下所示:
由此我们可以看出PhoneWindow的findViewById()方法其实就是从mDecor中查找View。ID_ANDROID_CONTENT也是在Window类中定义的,如下所示:
其实无论上面计算的layoutResource是哪个layout,该layout中都有一个id为
举例来说,我们有一个MainActivity,其直接继承自Activity,Application和MainActivity都没有设置任何Theme和Style,当App运行在Android 6.0系统上的时候,得到的layoutResource是com.android.internal.R.layout.screen_action_bar,具体如下所示:
我们可以看到com.android.internal.R.layout.screen_action_bar中有一个
当generateLayout()执行完毕后,installDecor()方法也就执行完了。
这样PhoneWindow#setContentView(layoutResID)中会执行下面的代码:
此处的layoutResID是我们Activity的资源文件,比如R.layout.activity_main,此处将该文件inflate成具体的View,并将其放入到mContentParent中。
之后还会通过代码
这样Activity#setContentView()也就执行完了,假设我们的R.layout.activity_main中只有一个RelativeLayout,那么通过hierarchyviewer查看到的View树如下所示:
View树的根结点是
我们回过头来再思考一下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的量算、布局及绘图机制
在执行完了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的量算、布局及绘图机制
相关文章推荐
- 新手Android学习笔记 05——IllegalStateException非法异常之一
- 新手Android学习笔记 05——IllegalStateException非法异常之一
- android studio radioGroup radiobutton使用listactivity演示
- 动态修改actionBar返回键颜色
- Activity的启动模式
- 关于Android下各个布局方式里面LayoutParams的用法
- 关于Android下各个布局方式里面LayoutParams的用法
- Android 自己实现 NavigationView [Design Support Library(1)]
- android developer tiny share-20160726
- android developer tiny share-20160726
- android studio 快捷键
- View的工作原理
- Android中Tab设置的一个神奇属性“clipChildren”
- Android ContentProvider和getContentResolver
- Android Studio常用快捷键
- android开发 简单的数据库操作 adb命令
- android
- android媒体--stagefright概述【一】
- 你所了解的BaseAdapter是这样的吗?
- android的几个tips