Window的操作

基于Android U

Window的操作包括对Window的添加、更新和删除。对于Window的操作,最终都是交由WMS来进行处理的。Window的操作分为两大部分,一部分是WindowManager处理部分,另一部分是WMS处理部分。我们知道Window分为三大类,分别是Application Window(应用程序窗口)、Sub Window(子窗口)和System Window(系统窗口),对于不同类型的Window添加过程会有所不同,但是对于WMS处理部分,添加的过程基本上是一样的,WMS对于这三大类的Window基本上是“一视同仁”的,如下图。

系统窗口的添加过程

这里以系统窗口StatusBar为例,StatusBar是SystemUI的重要组成部分,具体就是指系统状态栏,用于显示时间、电量和信号等信息。

1
2
3
4
5
6
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
makeStatusBarView(result); // 1
mNotificationShadeWindowController.attach();
mStatusBarWindowController.attach(); // 2
}

注释1处构建StatusBar的视图。

注释2处添加StatusBar的视图。

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
59
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
public void attach() {
...
mLp = getBarLayoutParams(mContext.getDisplay().getRotation()); // 1
...

mWindowManager.addView(mStatusBarWindowView, mLp); // 2
mLpChanged.copyFrom(mLp);

mContentInsetsProvider.addCallback(this::calculateStatusBarLocationsForAllRotations);
calculateStatusBarLocationsForAllRotations();
mIsAttached = true;
apply(mCurrentState);
}

private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation);
lp.paramsForRotation = new WindowManager.LayoutParams[4];
for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
lp.paramsForRotation[rot] = getBarLayoutParamsForRotation(rot);
}
return lp;
}

private WindowManager.LayoutParams getBarLayoutParamsForRotation(int rotation) {
int height = SystemBarUtils.getStatusBarHeightForRotation(mContext, rotation);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
height,
WindowManager.LayoutParams.TYPE_STATUS_BAR, // 3
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
| WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
PixelFormat.TRANSLUCENT);
lp.privateFlags |= PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
lp.token = new Binder();
lp.gravity = Gravity.TOP;
lp.setFitInsetsTypes(0 /* types */);
lp.setTitle("StatusBar");
lp.packageName = mContext.getPackageName();
lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
final InsetsFrameProvider gestureInsetsProvider =
new InsetsFrameProvider(mInsetsSourceOwner, 0, mandatorySystemGestures());
final int safeTouchRegionHeight = mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.display_cutout_touchable_region_size);
if (safeTouchRegionHeight > 0) {
gestureInsetsProvider.setMinimalInsetsSizeInDisplayCutoutSafe(
Insets.of(0, safeTouchRegionHeight, 0, 0));
}
lp.providedInsets = new InsetsFrameProvider[] {
new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars())
.setInsetsSize(getInsets(height)),
new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement())
.setInsetsSize(getInsets(height)),
gestureInsetsProvider
};
return lp;

}

注释1处通过getBarLayoutParams()创建LayoutParams来配置StatusBar视图的属性,包括Width、Height、Type、Flag、Gravity等。注释3处还设置了TYPE_STATUS_BAR,表示StatusBar视图的窗口类型是状态栏。

注释2处调用了WindowManager的addView()方法,addView()方法定义在WindowManager的父类接口ViewManager中,而addView()方法则是在WindowManagerImpl中实现的。

1
2
3
4
5
6
7
frameworks/base/core/java/android/view/WindowManagerImpl.java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
android.util.SeempLog.record_vg_layout(383,params);
applyTokens(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}

addView()方法的第一个参数是View,说明窗口也是以View的形式存在的。addView()方法中会调用WindowManagerGlobal的addView()方法。

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
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 (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams); // 1
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}

ViewRootImpl root;
View panelParentView = null;

synchronized (mLock) {
...
// 2
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); // 3
mRoots.add(root); // 4
mParams.add(wparams); // 5

// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView, userId); // 6
} catch (RuntimeException e) {
...
}
}
}

WindowManagerGlobal中维护了三个和Window操作相关的列表,分别是:View列表(ArrayList<View> mViews)、布局参数列表 (ArrayList<WindowManager.LayoutParams> mParams)和ViewRootImpl列表(ArrayList<ViewRootImpl> mRoots)。

addView()首先会对参数view、params和display进行检查。

注释1处,如果当前窗口要作为子窗口,就会根据父窗口对子窗口的WindowManager.LayoutParams类型的wparams对象进行相应调整。注释3处将添加的View保存到View列表中。注释5处将窗口的参数保存到布局参数列表中。

在注释2处创建了ViewRootImpl并赋值给root,紧接着在注释4处将root存入到ViewRootImpl列表中。

注释6处将窗口和窗口的参数通过setView()方法设置到ViewRootImpl中,可见我们添加窗口这一操作是通过ViewRootImpl来进行的。ViewRootImpl身负了很多职责,主要有以下几点:

  • View树的根并管理View树;
  • 触发View的测量、布局和绘制;
  • 输入事件的中转站;
  • 管理Surface;
  • 负责与WMS进行进程间通信。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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) {
...
try {
...
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
mTempControls, attachedFrame, compatScale);
...
} catch (RemoteException | RuntimeException e) {
...
} finally {
...
}
...
}
}
}

调用mWindowSession的addToDisplayAsUser()方法,mWindowSession是IWindowSession类型的,它是一个Binder对象,用于进行进程间通信,IWindowSession是Client端的代理,它的Server端的实现为Session,此前的代码逻辑都是运行在本地进程的,而Session的addToDisplayAsUser()方法则运行在WMS所在的进程(SystemServer进程)中。

从图中可以看出,本地进程的ViewRootImpl要想和WMS进行通信需要经过Session,那么Session为何包含在WMS中呢?我们接着往下看Session的addToDisplayAsUser()方法。

1
2
3
4
5
6
7
8
9
10
frameworks/base/services/core/java/com/android/server/wm/Session.java
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, @InsetsType int requestedVisibleTypes,
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
requestedVisibleTypes, outInputChannel, outInsetsState, outActiveControls,
outAttachedFrame, outSizeCompatScale);
}

在addToDisplayAsUser()方法中调用了WMS的addWindow()方法,并将自身也就是Session作为参数传了进去,每个应用程序进程都会对应一个Session,WMS会用ArrayList来保存这些Session,这就是为什么WMS包含Session的原因。这样剩下的工作就交给WMS来处理,在WMS中会为这个添加的窗口分配Surface,并确定窗口显示次序,可见负责显示界面的是画布Surface,而不是窗口本身。WMS会将它所管理的Surface交由SurfaceFlinger处理,SurfaceFlinger会将这些Surface混合并绘制到屏幕上。

Activity的添加过程

无论是哪种窗口,它的添加过程在WMS处理部分中基本是类似的,只不过会在权限和窗口显示次序等方面会有些不同。但是在WindowManager处理部分会有所不同,这里以最典型的应用程序窗口Activity为例,Activity在启动过程中,如果Activity所在的进程不存在则会创建新的进程,创建新的进程之后就会运行代表主线程的实例ActivityThread,ActivityThread管理着当前应用程序进程的线程,这在Activity的启动过程中运用得很明显,当界面要与用户进行交互时,会调用ActivithThread的handleResumeActivity()方法。

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
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)) { // 1
return;
}
...

final Activity a = r.activity;

...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
...
ViewManager wm = a.getWindowManager(); // 2
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l); // 3
} else {
...
}
}
} else if (!willBeVisible) {
...
}
...
}

注释1处的performResumeActivity()方法最终会调用Activity的onResume()方法。

注释2处得到ViewManager类型的wm对象,注释3处调用了ViewManager的addView()方法,而addView()方法则是在WindowManagerImpl中实现的,此后的过程在上面的系统窗口StatusBar的添加过程中已经讲过,唯一需要注意的是ViewManager的addView()方法的第一个参数为DecorView,这说明Activity窗口中会包含DecorView。

Window的更新过程

Window的更新过程和Window的添加过程是类似的。需要调用ViewManager的updateViewLayout()方法,updateViewLayout()方法在WindowManagerImpl中实现。

1
2
3
4
5
6
frameworks/base/core/java/android/view/WindowManagerImpl.java
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
android.util.SeempLog.record_vg_layout(384,params);
applyTokens(params);
mGlobal.updateViewLayout(view, params);
}

调用WindowManagerGlobal的updateViewLayout()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
frameworks/base/core/java/android/view/WindowManagerGlobal.java
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
...
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

view.setLayoutParams(wparams); // 1

synchronized (mLock) {
int index = findViewLocked(view, true); // 2
ViewRootImpl root = mRoots.get(index); // 3
mParams.remove(index); // 4
mParams.add(index, wparams); // 5
root.setLayoutParams(wparams, false); // 6
}
}

注释1处将更新的参数设置到View中。

注释2处得到要更新的窗口在View列表中的索引,注释3处在ViewRootImpl列表中根据索引得到窗口的ViewRootImpl。

注释4和5处用于更新布局参数列表,注释6处调用ViewRootImpl的setLayoutParams()方法将更新的参数设置到ViewRootImpl中。

1
2
3
4
5
6
7
8
frameworks/base/core/java/android/view/ViewRootImpl.java
public void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
synchronized (this) {
...
scheduleTraversals();
...
}
}

ViewRootImpl的setLayoutParams()方法在最后会调用ViewRootImpl的scheduleTraversals()方法。

1
2
3
4
5
6
7
8
9
10
11
frameworks/base/core/java/android/view/ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); // 1
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

注释1处的Choreographer译为“舞蹈指导”,用于接收显示系统的VSync信号,在下一个帧渲染时控制执行一些操作。Choreographer的postCallback()方法用于发起添加回调,这个添加的回调将在下一帧被渲染时执行。这个添加的回调指的是注释1处的TraversalRunnable类型的mTraversalRunnable。

1
2
3
4
5
6
7
frameworks/base/core/java/android/view/ViewRootImpl.java
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}

在TraversalRunnable的run()方法中调用了doTraversal()方法。

1
2
3
4
5
6
7
8
frameworks/base/core/java/android/view/ViewRootImpl.java
void doTraversal() {
if (mTraversalScheduled) {
...
performTraversals();
...
}
}

doTraversal()方法中又调用了performTraversals()方法,performTraversals()方法使得ViewTree开始View的工作流程。

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
frameworks/base/core/java/android/view/ViewRootImpl.java
private void performTraversals() {
...
if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
|| mForceNextWindowRelayout) {
...
try {
...
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); // 1
...
} catch (RemoteException e) {
} finally {
...
}
...
if (!mStopped || mReportNextDraw) {
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()
|| dispatchApplyInsets || updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width,
lp.privateFlags);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height,
lp.privateFlags);
...
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // 2
...
}
}
} else {
...
}
...
if (didLayout) {
performLayout(lp, mWidth, mHeight); // 3
...
}
...

if (!isViewVisible) {
...
} else if (cancelAndRedraw) {
...
} else {
...
if (!performDraw() && mActiveSurfaceSyncGroup != null) { // 4
mActiveSurfaceSyncGroup.markSyncReady();
}
}

...
}

注释1处的relayoutWindow()方法内部会调用IWindowSession的relayout()方法来更新Window视图,最终会调用WMS的relayoutWindow()方法。

注释2、3、4处分别调用performMeasure()、performLayout()、performDraw()方法,它们的内部又会调用View的measure()、layout()、draw()方法,这样就完成了View的工作流程。在performTraversals()方法中更新了Window视图,又执行Window中的View的工作流程,这样就完成了Window的更新。


Window的操作
https://citrus-maxima.github.io/2024/03/10/Window的操作/
作者
柚子树
发布于
2024年3月10日
许可协议