前言java
在Activity中通常第一句就是调用setContentView(R.layout.XXX),但这其中系统作了那些工做?android
咱们知道,在ClassLoader装载了MainActivity以后,首先建立了Application,以后依次调用Application对象的onAttach和onCreate()方法。而后顺序调用第一个Activity的onAttach和onCreate()方法。大概有个印象便可,后文会涉及到。具体参考:Launcher启动应用程序流程源码解析。app
新建测试工程TestHierarchyide
新建工程后,activity_main.xml中默认只有一个RelativeLayout,其中包含一个TextView。工具
设置Android:id="@+id/myRelativeLayout",android:id="@+id/myTextView"。布局
设置MainActivity继承自Activity。测试
保持默认MainActivity extends AppCompatActivitythis
保持默认<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">.net
至此初始工做完毕。xml
使用hierarchy查看布局结构
hierarchy是随着SDK发布的一款可视化布局分析工具。这里只须要基础的查看布局层次。因为Hierarchy Viewer只能链接Android开发版手机或是模拟器,因此咱们先在虚拟机上运行程序,而后进入..\sdk\tools,找到hierarchyviewer.bat,双击。接着选中咱们的程序,以后点击Load View Hierarchy,以后会获得一个黑不溜秋的视图。而这里,就是重点要看的地方。可是为了方便理解,省去一部分没必要要的Tiltle等元素,这里以继承Activity为例进行解析。能够先点击各个节点试试每一个View对应的位置。
MainActivity继承Activity
这里注意下右上角的两个节点,这明显就是activity_main.xml。不信看id!
源码解析
源码位置:frameworks/base/core/Java/android/app/Activity.java
Activity#setContentView()
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
因为继承的是Activity,首先咱们就省略了initWindowDecorActionBar()这一步。Activity大法好~。接下来要关注的就一行代码。首先看下这个getWindow()返回的是个什么鬼。
public Window getWindow() {
return mWindow;
}
前言中说Activity的建立的时候第一个执行的方法就是attach()。这里的mWindow就是在attach()方法中被实例化的。
final void attach(...){
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
}
mWindow是个Window对象,可是PhoneWindow继承于Window。经过PhoneWindow获取到mWindow以后设置了一个回调。Activity实现了Window.Callback接口,并且Activity中持有一个Window的引用,这就意味着在调用Callback接口方法的时候,Activity能够获得相应的回调。而且Activity能够经过Window属性去操做View。跟进getWindow().setContentView(layoutResID)。
源码位置:frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
PhoneWindow#setContentView()
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
// 返回false,执行else分支
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();
}
}
先大概分析这段代码流程,首先判断mContentParent是否是为空,第一次进来什么也没干呐,铁定为null。FEATURE_CONTENT_TRANSITIONS属性用于设置Activity的切换效果,默认false。上面首先调用了installDecor(),从上下文名称来看,这个方法应该和mContentParent变量有关系。接着调用mLayoutInflater.inflate(layoutResID, mContentParent)将咱们设置的R.layout.XXX填充到mContentParent。结合前面hierarchyviewer图来看,mContentParent就是包含activity_main.xml的FrameLayout的一个实例。最后回调Callback#onContentChanged(),这里的cb其实就是Activity对象。这个方法在Activity中的实现为空方法,因此咱们能够在本身的Activity中复写这个方法,实现本身的逻辑。在Activity的布局文件发生改动,即调用setContentView()或者addContentView()以后会调用onContentChanged()方法。跟进installDecor(),下面重点解析mDecor和mContentParent。
PhoneWindow#installDecor()
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
...
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
...
}
首先调用generateDecor()方法获取mDecor实例,接着依据mDecor实例获取到mContentParent。跟进。
PhoneWindow#generateDecor()
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
private final class DecorView extends FrameLayout
在PhoneWindow#generateDecor()中直接new了一个DecorView 对象,能够看到:DecorView也只是个继承FrameLayout的ViewGroup。下面跟进generateLayout()。
PhoneWindow#generateDecor()
protected ViewGroup generateLayout(DecorView decor) {
// 获取自定义属性window
TypedArray a = getWindowStyle();
...
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
}
...
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
}
....
else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
mDecor.finishChanging();
return contentParent;
}
前面省略的一大段的做用是获取自定义属性window以后所作的各类初始化工做,这里以requestFeature(FEATURE_NO_TITLE)为例。由于在这以后才执行View in = mLayoutInflater.inflate(layoutResource, null),将系统依据style采用的布局文件转换为View in,这里继承的是Activity,style=Theme.AppCompat.Light.DarkActionBar,因此加载的布局为screen_title.xml。以后将in加入到mDecor中,接着将in赋值给mContentRoot。这里的public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content。布局文件screen_title.xml以下所示:
<?xml version="1.0" encoding="utf-8"?>
<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>
id为content的Framelayout是contentParent,最外层的LinearLayout为mContentView。id为action_mode_bar_stub的android:visibility="gone"。最后放出一张本身标注的图~