前言
Activity 启动过程文章系列会按阶段拆开分析。上一篇已经把 Activity 的创建、启动和恢复主链讲完;本文只聚焦 Activity 窗口显示阶段:窗口对象何时准备好、setContentView() 何时把内容装进窗口、以及窗口何时真正挂接到 WindowManager / WMS。
启动流程梳理:
- Activity 启动流程(一)—— Launcher 阶段
- Activity 启动流程(二)—— AMS 处理阶段
- Activity 启动流程(三)—— 应用程序进程启动阶段
- Activity 启动流程(四)—— ActivityThread 初始化阶段
- Activity 启动流程(五)—— Activity 启动阶段
- Activity 启动流程(六)—— Activity 窗口显示
点击阅读:Android 窗口显示系列文章
如果对 Activity、Window、View 三者关系还不熟,可以先看:Android 窗口显示(一)—— Activity、Window 和 View 之间的联系
代码基于 android-14.0.0_r9。
本文只讲 Activity 首次窗口显示链路,并且只停在 ViewRootImpl.setView() 把窗口挂接到 WMS 的入口位置。后面的 performTraversals()、首帧绘制、真正 draw 到屏幕,不在本文范围里。
1. 一句话总览
这一阶段的核心是:Activity 在 attach() 时先拥有 PhoneWindow 和 WindowManager,在 onCreate() / setContentView() 阶段把 DecorView 和内容容器准备好,等到 handleResumeActivity() 里确认窗口需要对用户可见时,才通过 WindowManagerImpl.addView() → WindowManagerGlobal.addView() → ViewRootImpl.setView() 把窗口正式挂接到 WindowManagerService。
主链路如下:
Activity.attach()→PhoneWindow/WindowManagerImpl初始化 →Activity.onCreate()→setContentView()→PhoneWindow.installDecor()→DecorView/ content 准备 →ActivityThread.handleResumeActivity()→WindowManagerImpl.addView()→WindowManagerGlobal.addView()→ViewRootImpl.setView()→addToDisplayAsUser()
flowchart LR
A[Activity.attach]
B[PhoneWindow / WindowManagerImpl]
C[Activity.onCreate]
D[setContentView]
E[installDecor / DecorView]
F[handleResumeActivity]
G[WindowManagerImpl.addView]
H[WindowManagerGlobal.addView]
I[ViewRootImpl.setView]
J[addToDisplayAsUser]
A --> B --> C --> D --> E --> F --> G --> H --> I --> J
这条链路里要特别区分三件事:
- 窗口对象创建,不等于已经显示;
DecorView准备完成,不等于已经挂接到 WMS;setView()进入窗口挂接链,也不等于首帧已经画到屏幕。
2. Activity 窗口显示源码分析
2.1 onCreate() 之前:Activity.attach() 先把窗口骨架准备好
Activity 实例创建出来以后,在 performLaunchActivity() 里会先调用 attach(),再进入 onCreate()。窗口相关的第一批准备也发生在这里:
// 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) {
attachBaseContext(context);
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
mWindow.setWindowManager(
(WindowManager) context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
}
这一阶段的重点不是内容 View,而是窗口骨架:
Activity获得一个PhoneWindow;Window.Callback被绑定到当前Activity;WindowManager被设置给PhoneWindow;- 后续窗口操作都会沿着这条
Activity -> PhoneWindow -> WindowManagerImpl链继续往下走。
所以第一条边界很重要:Window 先于 onCreate() 存在。
这也解释了为什么我们在 onCreate() 里已经可以调用 setContentView():因为此时窗口本身已经准备好了,只是还没有把内容填进去。
2.2 onCreate() 里:setContentView() 真正把内容装进窗口
Activity 进入 onCreate() 之后,通常会主动调用 setContentView()。对于普通 Activity,最终会落到 PhoneWindow.setContentView():
// frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
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;
}
这段代码说明 setContentView() 真正干的事情是两步:
- 如果窗口内部容器还没准备好,就先走
installDecor(); - 再把 Activity 的布局 inflate 到
mContentParent里。
所以 setContentView() 并不是“把窗口加到屏幕上”,而是把业务内容填充到窗口的内容区域里。
2.3 DecorView 何时创建:不是在 PhoneWindow 构造时,而是在首次需要时
旧文里有一个容易让人误解的点:它把 PhoneWindow 初始化和 DecorView 创建说得太贴近,容易让人以为 PhoneWindow 构造时就已经创建好 DecorView 了。
更准确的锚点在 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);
mDecor.makeFrameworkOptionalFitsSystemWindows();
...
}
}
这里可以把窗口内部再拆成两层:
mDecor:顶层DecorView,是整个窗口的根 View;mContentParent:DecorView内部真正承载 Activity 内容布局的容器。
这意味着:
attach()负责准备 Window;installDecor()负责准备 Decor 和内容容器;setContentView()负责把业务布局填进内容容器。
这是本文第二个关键边界。把这三层职责分开看,窗口显示链就不会混乱。
2.4 onResume() 之后:窗口才进入真正的 attach/display 入口
前面的步骤都还停留在客户端本地对象准备阶段。真正开始“把窗口送进 WindowManager 系统”的入口,在 ActivityThread.handleResumeActivity():
// frameworks/base/core/java/android/app/ActivityThread.java
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, boolean shouldSendCompatFakeFocus, String reason) {
if (!performResumeActivity(r, finalStateRequest, reason)) {
return;
}
...
final Activity a = r.activity;
...
boolean willBeVisible = !a.mStartedActivity;
if (!willBeVisible) {
willBeVisible = ActivityClient.getInstance().willActivityBeVisible(
a.getActivityToken());
}
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
}
}
这里有两个必须讲清楚的事实:
- 不是
onCreate()一结束窗口就加到 WMS。 - 只有在
handleResumeActivity()判断窗口需要对用户可见时,才会走到wm.addView(...)。
也就是说,窗口显示链里真正挂接到 WMS 的时机是恢复后、可见性成立时,而不是单纯的 setContentView() 阶段。
2.5 addView() 到 setView():客户端窗口正式挂接到 WMS
wm.addView(decor, l) 往下先经过 WindowManagerImpl:
// frameworks/base/core/java/android/view/WindowManagerImpl.java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyTokens(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
然后进入 WindowManagerGlobal.addView():
// frameworks/base/core/java/android/view/WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
...
if (windowlessSession == null) {
root = new ViewRootImpl(view.getContext(), display);
} else {
root = new ViewRootImpl(view.getContext(), display,
windowlessSession, new WindowlessWindowLayout());
}
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView, userId);
}
这一步做了三件关键事:
- 为当前窗口创建
ViewRootImpl; - 把
DecorView、ViewRootImpl、LayoutParams注册到WindowManagerGlobal的本地列表里; - 调用
ViewRootImpl.setView(),把 View 树和窗口系统真正接起来。
最后的关键入口在 ViewRootImpl.setView():
// frameworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
mView = view;
...
mWindowAttributes.copyFrom(attrs);
...
requestLayout();
InputChannel inputChannel = null;
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
inputChannel = new InputChannel();
}
...
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
mTempControls, attachedFrame, compatScale);
...
}
}
}
这里要把“窗口显示”这件事收得非常克制:
setView()完成的是窗口挂接入口;addToDisplayAsUser()完成的是把窗口注册到 WMS;requestLayout()只是发起后续 traversal;- 真正首帧什么时候画出来,不在本文范围内。
所以旧文里“addView() 完成后 Activity 才会显示到屏幕上”这种说法太宽了。更准确的表述是:到 setView() 为止,窗口已经进入系统显示链路,但首帧绘制还要依赖后续 traversal / draw。
3. 关键设计点
3.1 先有 Window,再有内容,再有显示
这条链路本质上是三段式:
attach()先建窗口骨架 →setContentView()再填内容 →handleResumeActivity()之后才真正挂接显示。
本质上是窗口准备、内容准备、系统挂接三段分离。价值在于:生命周期责任清楚,窗口系统和业务内容不会混在一起。
3.2 PhoneWindow 是本地窗口门面,ViewRootImpl 是显示链桥接点
PhoneWindow 负责在客户端内部组织窗口和内容层级;ViewRootImpl 负责把这棵 View 树真正桥接到 WMS、输入系统、后续 traversal 链路里。前者偏“窗口语义”,后者偏“系统挂接”。
3.3 onResume() 不等于“首帧已经显示”
handleResumeActivity() 里确实会触发 addView(),但这只是窗口进入显示链的入口。setView() 之后还会有后续布局、测量、绘制。所以“生命周期恢复”和“首帧真正上屏”不能直接画等号。
4. 高频误区 / QA
4.1 PhoneWindow 创建时,DecorView 也已经创建好了吗?
不一定。更准确地说,attach() 时先有 PhoneWindow;DecorView 通常是在首次 setContentView() / getDecorView() 触发 installDecor() 时才真正准备出来。
4.2 setContentView() 是不是就把窗口显示到屏幕上了?
不是。setContentView() 做的是内容填充:创建 DecorView / mContentParent,再把布局 inflate 进去。真正的窗口挂接发生在 handleResumeActivity() 之后的 addView() / setView()。
4.3 WindowManagerImpl.addView() 完成,是不是就等于首帧已经显示?
也不是。addView() 和 setView() 只是把窗口送进显示链路,并向 WMS 发起窗口添加请求。真正首帧的 measure / layout / draw 在后续 traversal 里完成。
5. 总结
Activity 窗口显示阶段的本质,不是“某一行代码突然把界面显示出来”,而是 窗口骨架、内容填充、系统挂接 三个阶段逐步接力:attach() 先建 PhoneWindow,setContentView() 再准备 DecorView 和内容区域,handleResumeActivity() 之后才通过 addView() / setView() 把窗口真正送进 WMS。
本文的边界停在 ViewRootImpl.setView() / addToDisplayAsUser()。也就是说,到这里窗口已经进入系统显示链,但首帧绘制和真正 draw 到屏幕的细节,是下一层窗口渲染问题,不属于本文范围。