本文是基于 Android 14 的源码解析。
当我们写 Activity 时会调用 setContentView() 方法来加载布局。现在来看看 setContentView() 方法是怎么实现的,源码如下所示:
路径:/frameworks/base/core/java/android/app/Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
这里调用了 getWindow().setContentView(layoutResID),getWindow() 指的是什么呢?接着往下看,getWindow() 返回 mWindow,源码如下所示:
路径:/frameworks/base/core/java/android/app/Activity.java
public Window getWindow() {
return mWindow;
}
那这个 mWindow 又是什么呢?我们继续查看代码,最终在 Activity 的 attach() 方法中发现了 mWindow,源码如下所示:
路径:/frameworks/base/core/java/android/app/Activity.java
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
IBinder shareableActivityToken) {
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
...
}
而 getWindow() 又指的是 PhoneWindow。所以来看看 PhoneWindow 的 setContentView() 方法,源码如下所示:
路径:/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
...
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
// 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();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
...
}
原来 mWindow 指的就是 PhoneWindow,PhoneWindow 是继承抽象类 Window 的,这样就知道了getWindow() 得到的是一个 PhoneWindow,因为 Activity 中 setContentView() 方法调用的是 getWindow().setContentView(layoutResID)。
挑关键的接着看,看看上面代码 installDecor() 方法里面做了什么,源码如下所示:
路径:/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
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);
...
}
}
在前面的代码中没发现什么,紧接着查看上面代码 generateDecor() 方法里做了什么,源码如下所示:
路径:/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
这里创建了一个 DecorView,这个 DecorView 就是 Activity 中的根 View。接着查看 DecorView 的源码,发现 DecorView 是 PhoneWindow 类的内部类,并且继承了 FrameLayout。我们再回到 installDecor() 方法中,查看 generateLayout(mDecor) 做了什么,源码如下所示:
路径:/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) {
...
int layoutResource;
int features = getLocalFeatures();
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
removeFeature(FEATURE_ACTION_BAR);
} 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) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = 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;
}
...
return contentParent;
}
PhoneWindow 的 generateLayout() 方法比较长,这里只截取了一小部分关键的代码,其主要内容就是根据不同的情况加载不同的布局给 layoutResource。现在查看上面代码 R.layout.screen_title,源码如下所示:
路径:/frameworks/base/core/res/res/layout/screen_title.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
<!-- 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" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
<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>
上面的 ViewStub 是用来显示 Actionbar 的。下面的两个 FrameLayout:一个是 title,用来显示标题;另一个是 content,用来显示内容。看到上面的源码,大家就知道了一个 Activity 包含一个 Window 对象,这个对象是由 PhoneWindow 来实现的。PhoneWindow 将 DecorView 作为整个应用窗口的根 View,而这个 DecorView 又将屏幕划分为两个区域:一个是 TitleView,另一个是 ContentView,而我们平常做应用所写的布局正是展示在 ContentView 中的,如图所示: