在android开发过程中,看源码是最好的学习方法,也是最痛苦的(学习是快乐的,过程是痛苦的)。真可谓是“成也源码败也源码“。
作为一个android开发的老鸟都知道DecorView这个最顶层view,那么这个DecorView是如何创建的,我们在activity里面创建的用户布局又是如何加载,如何添加到DecorView上 的,小菜鸟就来过一下这个过程。。
在这里我们还需要知道DecorView,Window,PhoneWindow这几个鸟是啥东西,我相信大神的你肯定都是见过的;Window看系统源码是抽象基类,提供了很多设置方法,比如 public View findViewById(@IdRes int id) {},这个方法你肯定见过,就是Window提供的,没见过打我好吧;PhoneWindow是唯一一个继承自Window的类,也就是所有工作都是这哥们完成的,以后所提到的Window也都是PhoneWindow。DecorView又是啥呢,看他的继承关系是一个FrameLayout,现在就豁然开朗了DecorView就是页面的最顶层view,我们所谓的标题栏,内容栏等都在上面。好回到我们的主题
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
getWindow()肯定就是是 activity里面的 Window,根据上面说的肯定就是PhoneWindow的实例,接着进去PhoneWindow:
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
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();
}
mContentParentExplicitlySet = true;
}
第一次进去的时候mContentParent(id未content的FrameLayout,后面会看到)为空,就是没有初始化DecorView,需要进入installDecor, mLayoutInflater.inflate(layoutResID, mContentParent);加载布局到mContentParent。
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
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) {
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
。。。。。
}
}
这里就看到generateDecor里面new 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, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
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);
}
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
generateLayout 里面代码很多,这里就贴一部分代码,FLAG_FULLSCREEN这个是不是很熟悉,全屏模式。这里面的获取的参数都是用户在style和在activity设置的参数,最后来设置对应的属性,和还在相对应的布局文件,如下面布局xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:fitsSystemWindows="true"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- Popout bar for action modes -->
<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"/>
<RelativeLayout android:id="@android:id/title_container"
style="?android:attr/windowTitleBackgroundStyle"
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize">
<!-- The title background has 9px left padding. -->
<ImageView android:id="@android:id/left_icon"
android:visibility="gone"
android:layout_marginEnd="9dip"
android:layout_width="16dip"
android:layout_height="16dip"
android:scaleType="fitCenter"
android:layout_alignParentStart="true"
android:layout_centerVertical="true" />
<ProgressBar android:id="@+id/progress_circular"
style="?android:attr/progressBarStyleSmallTitle"
android:visibility="gone"
android:max="10000"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"
android:layout_marginStart="6dip"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!-- There are 6dip between this and the circular progress on the right, we
also make 6dip (with the -3dip margin_left) to the icon on the left or
the screen left edge if no icon. This also places our left edge 3dip to
the left of the title text left edge. -->
<ProgressBar android:id="@+id/progress_horizontal"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="-3dip"
android:layout_toStartOf="@android:id/progress_circular"
android:layout_toEndOf="@android:id/left_icon"
android:layout_centerVertical="true"
android:visibility="gone"
android:max="10000" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_toStartOf="@id/progress_circular"
android:layout_toEndOf="@android:id/left_icon"
>
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@null"
android:fadingEdge="horizontal"
android:scrollHorizontally="true"
android:gravity="center_vertical"
android:layout_marginEnd="2dip"
/>
<!-- 2dip between the icon and the title text, if icon is present. -->
<ImageView android:id="@android:id/right_icon"
android:visibility="gone"
android:layout_width="16dip"
android:layout_height="16dip"
android:layout_weight="0"
android:layout_gravity="center_vertical"
android:scaleType="fitCenter"
/>
</LinearLayout>
</RelativeLayout>
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
或者
<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>
等等。。。。
`
android:id=”@android:id/content” 这句你肯定很熟悉,我们的内容布局view,activity里面的布局文件最后都加载在这里面,也就是activity.setContentView里面的内容。注意ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)这句,我们generateLayout返回的就是contentParent,始终要记得contentParent是在DecorView内部,DecorView是根view。
setContentView源码解析就告一段落了。。
回顾下
Window是一个抽象类,提供了各种窗口操作的方法,比如设置背景标题ContentView等等
PhoneWindow则是Window的唯一实现类,它里面实现了各种添加背景主题ContentView的方法,内部通过DecorView来添加顶级视图
每一个Activity上面都有一个Window,可以通过getWindow获取
DecorView,顶级视图,继承与FramentLayout,setContentView则是添加在它里面的@id/content里
setContentView里面创建了DecorView,根据Theme,Feature添加了对应的布局文件,最终setContentView会添加在content里面
当setContentView设置显示后会回调Activity的onContentChanged方法
图是借的哈