书接上回,已经到 Activity resume 了,但是resume被调用,只代表activity进入到了resumed生命周期状态,并不代表它的窗口已经完成添加。更不代表屏幕上已经出现了第一帧画面。
Activity中的onCreate()、onStart()、onResume()描述的是生命周期状态的推进,开发者也会在这些回调中初始化页面、恢复数据和准备交互。
那真正的窗口可以显示在界面上,需要几个阶段。view 树 -----> window 树 ------> SurfaceFlinger 图层
setContentView()
→ App进程中创建View树
WindowManager.addView()
→ ViewRootImpl连接View树与WMS
→ WMS创建WindowState并加入WindowContainer树
performTraversals()
→ measure/layout/draw
→ 绘制结果写入Surface对应的Buffer
SurfaceFlinger
→ 合成各窗口图层并输出到屏幕
一、setContentView
首先,上面 app 进程在按照 system_server 发过来的 ClientTransaction 执行应用的生命周期时,刚执行到 onCreate调用阶段。
在多数普通activity中,开发者会在onCreate中调用setcontentview,为当前Activity设置页面内容。
frameworks/base/core/java/android/app/Activity.java
java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);//返回的是 Activity 创建时生成的 PhoneWindow。
//因此真正设置页面内容的工作由PhoneWindow.setContentView()完成。
}
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
java
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
}
PhoneWindow.setContentView()首先通过installDecor()创建或准备窗口最外层的DecorView,并找到用于承载开发者页面的mContentParent。随后,LayoutInflater将开发者提供的XML布局解析成View对象,并加入mContentParent,最终形成以DecorView为根节点的View树。
这里可以通过dumpsys activity top 查看顶层activity的信息,如下

此时页面内容仍然只是App进程中的一组View对象。WMS虽然已经知道对应Activity的存在,但尚未为其创建
WindowState,SurfaceFlinger中也还没有可供合成的应用窗口内容
二、addWindow
app进程在按照system_server 发过来的ClientTransaction执行应用的生命周期时,刚执行到Activity.onResume()之后
回到r.activity.performResume(r.startsNotResumed, reason); 会继续执行下面的 wm.addView(decor, l);
frameworks/base/core/java/android/app/ActivityThread.java
java
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
......
wm.addView(decor, l);
//decor是view树的根节点
//WindowManagerGlobal将为它创建ViewRootImpl
//ViewRootImpl负责连接App端View树和WMS端窗口
}
View树关心measure、layout、draw;WMS关心Display、窗口层级、焦点、Insets、输入通道及Surface。ViewRootImpl就是两个世界之间的桥梁。
frameworks/base/core/java/android/view/WindowManagerImpl.java
java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
frameworks/base/core/java/android/view/WindowManagerGlobal.java
java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView, userId);
}
addToDisplayAsUser()中的mWindow不是PhoneWindow,而是ViewRootImpl内部实现了IWindow接口的Binder对象。WMS以这个IWindow作为客户端窗口标识,并创建对应的WindowState。
frameworks/base/core/java/android/view/ViewRootImpl.java
java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls);
}
frameworks/base/services/core/java/com/android/server/wm/Session.java
java
public int addToDisplayAsUser(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, Rect outFrame,
Rect outContentInsets, Rect outStableInsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
outInsetsState, outActiveControls, userId);
}
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
java
public int addWindow(Session session, IWindow client, int seq,
LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
Rect outContentInsets, Rect outStableInsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState, InsetsSourceControl[] outActiveControls,
int requestUserId) {
int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
appOp); // 检查权限 检查调用方是否有权添加这种类型的窗口
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid, userId,
session.mCanAddInternalSystemWindow);//在WMS中为客户端窗口创建WindowState记录
displayPolicy.getLayoutHint(win.mAttrs, token, outFrame, outContentInsets,
outStableInsets, outDisplayCutout); // 这里得到的是初步布局,后续relayout()仍可能继续调整
win.attach();
mWindowMap.put(client.asBinder(), win);
win.mToken.addWindow(win);
}
WindowState创建后,WMS将它添加到对应WindowToken的子节点中。由于WindowToken自身已经处在WindowContainer层级中,这个窗口也由此正式进入WMS管理的窗口树。
三、首次Traversal:View树如何产生第一帧内容
WMS.addWindow()成功之后,App端的ViewRootImpl已经与WMS端的WindowState建立了联系。但此时只能证明窗口已经登记,View树还没有按照WMS提供的窗口尺寸完成测量,布局和绘制,因此仍然没有可供SurfaceFlinger合成的有效画面
这里从frameworks/base/core/java/android/view/ViewRootImpl.java 的
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) 开始 在执行addToDisplayAsUser之前 还需要执行requestLayout();
这里会发一个异步的任务
frameworks/base/core/java/android/view/ViewRootImpl.java
java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
requestLayout();
}
public void requestLayout() {
scheduleTraversals();
}
void scheduleTraversals() {
// 避免重复预约Traversal,将短时间内的多个布局请求合并到后续一帧处理
// 标记需要重新布局,并通过Choreographer预约一次Traversal
//此处只是预约,不会立即执行performTraversals()
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
requestLayout()虽然写在addToDisplayAsUser()前面,但它只负责预约Traversal。当前主线程会继续同步执行WMS.addWindow();等setView()流程结束、Choreographer调度到对应回调时,才真正执行doTraversal()
java
void doTraversal() {
performTraversals();
├── 与WMS协商窗口Frame、Insets和Surface
├── performMeasure():确定各View的尺寸
├── performLayout():确定各View的位置
└── performDraw():将View树绘制到Surface对应的Buffer
}
//源码会根据窗口尺寸、Insets和可见性等状态决定是否重新测量,并不代表每次Traversal都严格按固定顺序各执行一次。
View树不是直接交给SurfaceFlinger。App先把View树绘制成Buffer,SurfaceFlinger看到的是Layer和Buffer,而不是Button、TextView等View对象。