【Android 13源码分析】WMS-添加窗口(addWi

在了解完Activity启动流程后,现在目标应用的进程已经启动了,但是离用户在屏幕上看到Activity下的UI内容还有一段距离。
一个窗口想要显示在屏幕上,还需要经过3大步骤:

窗口显示三部曲.png

    1. addWindow流程这一步是创建 WindowState,并且挂载到窗口树上。
    1. relayout流程addWindow执行后,WindowState就创建和挂载好了,但是WindowState毕竟也是一个容器,没有真正的UI内容。
      执行relayout流程时会触发真正持有UI数据的Surface的创建,然后会将这个Surface返回到应用进程,应用进程在进行View的绘制三部曲。
      除了创建Surface的逻辑,relayoutWindow流程还会触发窗口位置的摆放逻辑。
    1. finishDrawing流程真正显示到屏幕上的内容不是Activity也不是Window,而是View树绘制的内容。
      经过上面2步,应用进程已经有Surface了, 并且执行绘制三部曲,也就是说View树的UI数据也就都绘制到Surface下的buff中了。绘制完成就需要通知SurfaceFlinger进行合成了,
      只有经过SurfaceFlinger处理后,才能真正显示到屏幕上。

绝大部分情况下,一个窗口的显示,这三步是是必经流程。(开机动画是直接通过SurfaceFlinger绘制的)

本篇分析第一步:addWindow流程

当前为Activity短暂的一生系列的第三块内容, 建议先看完 WindowContainer窗口层级Activity启动流程

  1. 概述

先对比一下一个应用启动后窗口的区别:

启动Activity后层级结构树对比.png

红色部分就是启动应用后多出来的部分,在 DefaultTaskDisplayArea 节点下多出来这么一个层级:

1
2
3
arduino复制代码Task
ActivityRecord
WindowState (就是那个 9c20028)

其中 Task 和 ActivityRecord 是如何挂载上去的在Activity启动流程已经介绍了,本篇要分析的 addWindow 的流程最重要的就是搞明白窗口对应的WindowState是如何创建并且挂载到窗口树中的。

整个流程框图如下:

addWindow一级框图.png

    1. 应用进程首先会创建出一个的Window
    1. 执行WindowManagerGlobal::addView方法,最终触发ViewRootImpl::setView方法来触发夸进程通信,通知WMS执行addWindow逻辑
    1. 应用和WMS通信是通过Session这个类,具体是调用了 Session::addToDisplayAsUser这个方法
    1. system_server进程的WMS执行addWindow方法时,会根据参数创建出一个WindowState,并且将其挂载到对应的WindowToken下(也就是挂载到窗口树中)

后面的内容也是围绕着这4点做详细解释首先介绍App进程的处理,然后介绍system_server进程的处理。

  1. APP进程流程

先看应用层是需要做哪些事:应用进程启动后,会执行LaunchActivityItem和ResumeActivityItem 这2个事务,对应执行到Activity的onCreate和onResume生命周期,这其中肯定也涉及到了Window相关的操作。

调用链如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
arduino复制代码LaunchActivityItem::execute
ActivityThread::handleLaunchActivity
ActivityThread::performLaunchActivity
Instrumentation::newActivity --- 创建Activity
Activity::attach --- 创建Window
Window::init
Window::setWindowManager
Instrumentation::callActivityOnCreate
Activity::performCreate
Activity::onCreate

ResumeActivityItem::execute
ActivityThread::handleResumeActivity
ActivityThread::performResumeActivity
Activity::performResume
Instrumentation::callActivityOnResume
Activity::onResume
WindowManagerImpl::addView --- 创建ViewRootImpl
WindowManagerGlobal::addView
ViewRootImpl::setView --- 与WMS通信 addView

根据这个调用链可知:先执行onResume,再执行addView。所以执行了onResume只是Activity可见,不代表View都显示了,可能都还没触发WMS的绘制,如果后续的任何一个地方出了问题,我们写在XML里的布局都不会显示出来。(以前写App的时候以为执行了onResume屏幕上就显示UI了)

2.1 创建Window逻辑

这块的执行是在 LaunchActivityItem 事务的执行流程中,到Activity的onCreate的生命周期之间, 具体的位置可以看上面的调用链,开始撸代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
ini复制代码# ActivityThread
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
// 定义window
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
// 正常不执行这里
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
......
// 注意token传递的是ActivityRecord的token
// 这里的window正常逻辑为null
activity.attach(...,r.token,, window, ...);
......
}

# Activity
final void attach(......) {
......
// 创建PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// 一些设置
mWindow.setWindowControllerCallback(mWindowControllerCallback);
// 留意这边将Activity设置为setCallback
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
......
// 设置window的token为 ActivityRecord
mToken = token;
......
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());
}
mWindowManager = mWindow.getWindowManager();
......
}

我们在Activity通过getWindow方法返回的就是这个mWindow,首先得确认这个,别分析了半天分析错了Window对象,毕竟framework的代码这么多。

这里有个面试点,在Activity::attach中看到mWindow 原來是一个PhoneWindow的对象,PhoneWindow是Window的唯一子类,Window是个抽象类,所以也确实没有办法直接new。

然后是一堆设置,这里需要注意 setCallback 方法,是将Activity设置给了Window,这里有什么用呢? 像configruation的改变和input事件的传递流程都是先走到Window的,因为在WMS模块没有Activity的概念,只有Window,那么最后是怎么走到Activity呢?就是这里设置的setCallback。当然这个在当前分析的addWindow流程没有关系,但是需要有点印象。

再下面的setWindowManager和getWindowManager两个方法也很有意思,因为咋一看有点矛盾,在一个地方set又get感觉很多余,因为这里set和get返回的对象,其实不是同一个对象。

2.1.1 setWindowManager,getWindowManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ini复制代码# Window
// 应用Token
private IBinder mAppToken;

// wm :WindowManager对象,注意下传进来的值
// appToken :这个就是AMS中与当前Activity对应的ActivityRecord
// appName :Activity全类名
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
// 将ActivityRecord设置给mAppToken
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated;
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
// 根据强制也能看出 mWindowManager 是WindowManagerImpl的类型
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
public WindowManager getWindowManager() {
return mWindowManager;
}

这里将传递进来的 wm, 强转成WindowManagerImpl 后调用其 createLocalWindowManager方法。

该函数重新创建返回了一个 WindowManagerImpl 对象。 所以说setWindowManager 和 setWindowManager 的不是同一个对象, WindowManagerImpl::createLocalWindowManager方法如下:

1
2
3
4
5
6
7
8
9
10
ini复制代码# WindowManagerImpl
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow, mWindowContextToken);
}
private WindowManagerImpl(Context context, Window parentWindow,
@Nullable IBinder windowContextToken) {
mContext = context;
mParentWindow = parentWindow;
mWindowContextToken = windowContextToken;
}

这边注意的是将 Window 设置给了 mParentWindow。 相当于通过新创建的PhonWindow创建了一个WindowManagerImpl,作为其mWindowManager的对象。

到这里创建Window相关的就分析完了,创建的这个Window其实是 PhoneWindow 接下来看APP层的addWindow是如何触发的。

建议要理清楚 Window, PhoneWindow ,WindowManagerImpl,WindowManager这几个类的区别和联系,不要搞混了。

2.2 addWindow相关

执行到onCreate之前, 已经创建好了Window,所以我们在Activity::onCreate可以把我们的XML布局设置过去,也能通过 getWindow来做一些操作。

Window创建好后就要执行 addWindow逻辑了,根据调用链,是 ResumeActivityItem事务触发的,这个事务最终会执行到 Activity::onResume生命周期。

接下来看一遍代码的执行流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
ini复制代码# ActivityThread
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
......
// 触发onResume
if (!performResumeActivity(r, finalStateRequest, reason)) {
return;
}
......
// 拿到activity
final Activity a = r.activity;
......
if (r.window == null && !a.mFinished && willBeVisible) {
// 将本地的window设置到activityRecord中
r.window = r.activity.getWindow();
// 获取DecorView
View decor = r.window.getDecorView();
// 设置不可见 在后面调用Activity::makeVisible会设为可见
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
// 获取参数
WindowManager.LayoutParams l = r.window.getAttributes();
// DecorView设置给Activity
a.mDecor = decor;
// 设置Activity的windowType,注意这个type,才是应用的窗口类型
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
......
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
// 重点:执行addView,并设置mWindowAdded=true
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}

} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
}

主要是一些属性的设置然后执行addView, 这里比较需要注意的就是Activity的windowType为TYPE_BASE_APPLICATION = 1, 还有个TYPE_APPLICATION=2,目前已知是在创建ActivityRecord时使用。
然后我们已经知道wm就是WindowManagerImpl了,但是不应该是addWindow么,怎么成addView了呢?接着去看下面流程。

1
2
3
4
5
6
7
8
9
10
less复制代码# WindowManagerImpl
// 单例
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance()

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyTokens(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}

这个方法并没有啥复杂的,直接调到了WindowManagerGlobal,不过这里也有2个需要注意的点:

    1. WindowManagerGlobal是个单例,那就是说一个进程仅此一个
    1. 这里将mParentWindow传递了过去,上面分析的时候知道这个mParentWindow其实就是我们创建的PhoneWindow
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
csharp复制代码# WindowManagerGlobal

public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
......
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
// 调整window参数,设置token,比如title,和硬件加速的标志位
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
......
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
......
// 这一段的意思是如果执行过addView的话,再执行就报错
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
......
IWindowSession windowlessSession = null;
......
// 对应应用来说windowlessSession是为null的
if (windowlessSession == null) {
// 重点* 创建ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
} else {
root = new ViewRootImpl(view.getContext(), display,
windowlessSession);
}
// 设置参数到 decorView
view.setLayoutParams(wparams);
// 添加到对应集合,看得出来在WindowManagerGlobal中这3个对象应该是要一一对应的
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
// 重点 * 调用ViewRootImpl::setView
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}

这段代码最重要的就是做了2件事:

    1. ViewRootImpl的创建 (ViewRootImpl在整个WMS系统中是非常重要的一个类)
    1. 执行ViewRootImplL::setView方法, 这里也是应用进程处理的终点,剩下的就是跨进程交给WMS处理了

2.3 ViewRootImpl::setView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
scss复制代码# ViewRootImpl

final IWindowSession mWindowSession;

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
// 当前第一次执行肯定为null
if (mView == null) {
mView = view;
......
mAdded = true; // 表示已经add
int res; // 定义稍后跨进程add返回的结果
requestLayout(); // 非常重要的方法--请求布局更新
InputChannel inputChannel = null; // input事件相关
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
inputChannel = new InputChannel();
}
......
try {
......
// 重点* 这里通过binder通信,调用WMS的 addWindow方法
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
mTempControls);
......
}
// 后续流程与addWindow主流程无关,但是也非常重要
......
// 计算window的尺寸
final Rect displayCutoutSafe = mTempRect;
state.getDisplayCutoutSafe(displayCutoutSafe);
final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
mWindowLayout.computeFrames(mWindowAttributes, state,
displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
mInsetsController.getRequestedVisibilities(),
getAttachedWindowFrame(), 1f /* compactScale */, mTmpFrames);
setFrame(mTmpFrames.frame);
......
if (res < WindowManagerGlobal.ADD_OKAY) {
......// 对WMS调用后的结果判断是什么错误
}
......
view.assignParent(this); //这就是为什么decorView调用getParent返回的是ViewRootImpl的原因
......
}
}
}

这个方法是核心方法,处理了很多事,都加载备注上了。为了有一个宏观的印象,这里将其触发的各个调用链整理出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
arduino复制代码ViewRootImpl::setView
ViewRootImpl::requestLayout
ViewRootImpl::scheduleTraversals
ViewRootImpl.TraversalRunnable::run --- Vsync相关--scheduleTraversals
ViewRootImpl::doTraversal
ViewRootImpl::performTraversals
ViewRootImpl::relayoutWindow --- relayoutWindow
ViewRootImpl::performMeasure --- View绘制三部曲
ViewRootImpl::performLayout
ViewRootImpl::performDraw
ViewRootImpl::createSyncIfNeeded --- 绘制完成finishDrawing
WindowSession.addToDisplayAsUser --- addWindow
WindowLayout::computeFrames --- 窗口大小计算

这里要注意:虽然看顺序好像 addWindow流程是在relayoutWindow执行前,但是因为 doTraversal是异步的,所以还是先执行addWindow流程的。

回到当前主题,继续跟踪addWindow流程,也就是addToDisplayAsUser,看来上面的setView方法还有2个点不清楚:

  1. mWindowSession是什么?
  2. 参数里的mWindow是什么?

2.3.1 ViewRootImpl的mWindowSession是什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
arduino复制代码# ViewRootImpl

final W mWindow;
final IWindowSession mWindowSession;

public ViewRootImpl(Context context, Display display) {
this(context, display, WindowManagerGlobal.getWindowSession(),
false /* useSfChoreographer */);
}
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
boolean useSfChoreographer) {
mContext = context;
mWindowSession = session;
......
mWindow = new W(this);
......
}

WindowManagerGlobal::addView下构造ViewRootImpl的通过2个参数的构造方法,所以他的session就是WindowManagerGlobal.getWindowSession()

这里提一嘴,既然会这么设计,那么说明在Framework中肯定不是这一种方式获取session,比如画中画就是另一种,以后会提到,当前留个印象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
java复制代码# WindowManagerGlobal

@UnsupportedAppUsage
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
// Emulate the legacy behavior. The global instance of InputMethodManager
// was instantiated here.
// TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
scala复制代码# WindowManagerService
@Override
public IWindowSession openSession(IWindowSessionCallback callback) {
return new Session(this, callback);
}
# Session

class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
final WindowManagerService mService;
......
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
requestedVisibilities, outInputChannel, outInsetsState, outActiveControls);
}
}

所以这里的WindowManagerGlobal::getWindowSession返回的就是一个Session对象。Session继承IWindowSession.Stub,并且内部持有WMS引用。

2.3.2 ViewRootImpl的mWindow是什么

调用addToDisplayAsUser这个方法传递的mWindow,他并不是前面分析的Activity的那个Window,上面mWindow也是在ViewRootImpl的构造方法里赋值的。那这个W是什么呢?

1
2
3
4
5
6
7
8
9
10
11
scala复制代码# ViewRootImpl

static class W extends IWindow.Stub {
private final WeakReference<ViewRootImpl> mViewAncestor;
private final IWindowSession mWindowSession;

W(ViewRootImpl viewAncestor) {
mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
mWindowSession = viewAncestor.mWindowSession;
}
}

所以这里的mWindow只是一个内部类W的对象,这个W继承了IWindow.Stub,那也是用例binder通信的,W内部有一个ViewRootImpl弱引用。

2.4 APP进程小结

addWindow的流程,在APP进程到此就结束了,后面的逻辑由WMS执行。

2.4.1 二级框图

addWindow二级框图.png

应用进程启动后,会执行2个事务,分别触发到Activity的 onCreate和onResume2个常见的生命周期,所以这里分为了2个分支。

onCreate 分支

    1. 首先肯定是要创建Activity的
    1. 然后创建出Window,Window是抽象类,PhoneWindow是Window的唯一实现类。
    1. 执行到 onCreate 生命周期

onResume 分支

    1. 先触发了 onResume 的执行流程
    1. 执行WindowManagerImpl::addView
    1. 创建核心类 ViewRootImpl
    1. 执行关键函数 ViewRootImpl::setView ,跨进程通信后续流程就由WMS执行了

2.4.2 知识点小结:

一个进程内有个Window的总管家:WindowManagerGlobal。当Activity创建并初始化PhoneWindow后,WindowManagerImpl会调用WindowManagerGlobal的addView方法,将后续流程交给其处理。

WindowManagerGlobal会创建一个ViewRootImpl,WindowManagerGlobal内部还有3个集合,将Window的DecorView,参数LayoutParams和新创建的ViewRootImpl一一添加到对应的集合中。

后续流程由ViewRootImpl进行,ViewRootImpl的addView方式会通过Session最终调用到WMS的addWindow方法。

    1. Activity里的window其实是PhoneWindow,因为Window是抽象类,而PhoneWindow是其唯一子类
    1. Window的windowManager是WindowManagerImpl,内部有2个重要成员,DevordViw和WindowManagerImpl,Window本身并没有内容所以DevordViw才是UI的实际载体
    1. WindowManagerGlobal是单例类,一个进程只有一个,内部维护了3个集合

还有3个重要的方法:

Activity::attach

    1. 创建了PhoneWindow
    1. 设置了WindowManagerImpl作为WindowManager

WindowManagerGlobal::addView

    1. 创建ViewRootImpl
    1. 执行ViewRootImpl::setView

ViewRootImpl::setView

    1. IWindowSession::addToDisplayAsUser : 代表着addWindow流程在App进程结束,后面由WMS进行
    1. requestLayout:请求更新布局,触发relayoutWindow流程
    1. computeFrames : 计算窗口大小

ViewRootImpl::setView 这个方法比较长,这也是我目前比较熟悉的几个事情,也许还有重要分支被我忽略了。

其实有一个疑问,明明addWindo流程,但是到了WindowManagerImpl就变成了addView,传递的也是DecoreView,再到和WMS同信的时候,参数里连DecoreView都不剩了,这怎么能叫addWindow流程呢?
带着这个疑问下一篇将介绍WMS到底是怎么做的。

【Android 13源码分析】WMS-添加窗口(addWindow)流程-1-应用进程处理

【Android 13源码分析】WMS-添加窗口(addWindow)流程-2-SystemService进程处理

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%