系统服务-WindowManagerService

本文深入分析WindowManagerService,探讨窗口创建、布局与动画系统的工作原理。解析WMS的窗口管理结构,理解WindowToken、WindowState与DisplayContent的角色。揭示窗口显示次序与布局策略,以及动画渲染流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分析WindowManagerService

介绍最原始、最简单的窗口创建方法
研究WMS的窗口管理结构
探讨WMS布局系统的工作原理
研究WMS动画系统工作原理

4.1 初识WMS

窗口是什么?
屏幕上用于绘制各种UI和响应用户输入的巨型区域,原理上,窗口是独自占有一个surface实例的显示区域。例如 dialog activity 壁纸 状态栏等。

卷1第8章介绍了一个activity通过surface来显示自己的过程。

surface是一块画布,应用可以通过canvas或openGL在上面作画。
然后通过surfaceFlinger将多块surface的内容按照特定顺序z-order进行混合输出到FrameBuffer,再显示出来。
每个窗口都有一个surface,需要一个角色对所有窗口的surface进行协调管理,WMS为所有窗口分配surface,掌握surface显示顺序以及位置尺寸,控制窗口动画,并且是输入系统中转站。

窗口拥有显示和响应用户输入两个含义。本章侧重分析窗口显示,下章分析响应用户输入

深入分析WMS两个子系统的工作原理:
布局系统:计算与管理窗口的位置、层次
动画系统:根据布局系统计算的窗口位置和层次渲染窗口动画

4.1.1 一个从命令行启动的动画窗口
1 SampleWindow的实现
仅使用WMS接口创建并渲染一个动画窗口

2 初识窗口的创建、绘制与销毁
SampleWindow代码简单,却提炼了一个窗口的创建、绘制以及销毁的过程。没有使用WMS以外的系统服务,也没使用四大组件的框架。纯WMS实现UI和应用程序框架。

客户端创建一个窗口的步骤:
1 获取IWindowSession和WMS实例。客户端通过IWindowSession向WMS发送请求。
2 创建并初始化WindowManager.LayoutParams. 它继承自ViewGroup.LayoutParams类,并扩展了窗口相关的属性。其最重要的是type属性。这个属性描述了窗口的类型,而窗口类型正是WMS对多个窗口进行ZOrder排序的依据。
3 向WMS添加一个窗口令牌(WindowToken)后续讲解,窗口令牌描述了一个显示行为,并且WMS要求每一个窗口必须隶属于某一个显示令牌。
4 向WMS添加一个窗口。必须在LayoutParams中指明此窗口所隶属于的窗口令牌,否则在某些情况下添加操作会失败。在SampleWindow中,不设置令牌也可以完成添加操作,因为窗口的类型被设为TYPE_SYSTEM_ALERT,它是系统窗口的一种。而对于系统窗口,WMS会自动为其创建显示令牌,故无需客户端操心。
5 向WMS申请对窗口进行重新布局。重新布局就是根据窗口新的属性去调整其surface相关属性,或者重新创建一个surface(窗口尺寸变化导致之前surface不满足需求)。向WMS添加一个窗口之后,其仅仅是将它在WMS中进行注册而已。只有经过重新布局之后,窗口才拥有WMS为其分配的画布。有了画布,窗口之后可随时进行绘制。

窗口绘制的过程:
1 通过Surface.lock()函数获取可以在其上作画的Canvas实例
2 使用Canvas实例进行作画
3 通过Surface.unlockCanvasAndPost() 函数提交绘制结果

关于Surface的原理与使用方法,参考卷1第8章

在客户端也可通过OpenGl进行作画。
例中使用Choreographer类进行动画帧安排。Choreographer与Handler的post()函数相似。后会在后续某个时机回调传入Runnable对象。不同在于他们回调时机有差异。Handler回调取决于消息队列的处理情况,而Choreographer回调时机则为下一次VSYNS(垂直刷新同步)。在WMS内部一样使用Choreographer类进行窗口平移、旋转、缩放等动画的渲染。

销毁窗口的操作通过IWindowSession.remove() 方法窗口从WMS中删除。

3 窗口的概念
例中mWindow(类型IWindow)的变量。IWindow继承自Binder,实现类MyWindow继承自IWindow.Stub
窗口的本质就是画布surface,当一个surface显示在屏幕上时,就是用户所看到的窗口。客户端向WMS添加一个窗口的过程,就是WMS为其分配一个surface的过程,一个个surface在WMS管理下有序的排布在屏幕上,android呈现多彩界面。

显示系统的三个层次

1 第一个层次是UI框架层,其工作为surface上绘制UI元素以及相应输入事件
2第二个层次是WMS,工作是管理surface的分配、层级顺序
3 第三个层次为surfaceFlinger,负责将多个surface混合并输出

4.1.2 WMS的构成
1 WMS的诞生
SystemServer.java
import com.android.server.wm.WindowManagerService;
1 创建WMS实例,通过WindowManagerService的静态函数main()
wm = WindowManagerService.main(context, inputManager,
mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
!mFirstBoot, mOnlyCore, new PhoneWindowManager());
2 添加到ServiceManager
ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
…3 初始化显示信息
wm.displayReady();
…4 通知WMS,系统的初始化完成
wm.systemReady();

WMS创建分三阶段:
1 创建WMS的实例
2 初始化显示信息
3 处理systemReady通知

WMS的main()函数
public static WindowManagerService main(final Context context, final InputManagerService im,
final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore,
WindowManagerPolicy policy) {
DisplayThread.getHandler().runWithScissors(() ->
sInstance = new WindowManagerService(context, im, haveInputMethods, showBootMsgs,
onlyCore, policy), 0);
return sInstance;
}

private WindowManagerService(Context context, InputManagerService inputManager,
boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
WindowManagerPolicy policy) {

mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);

}

2 WMS 的重要成员
总结下在WMS的启动过程中创建的重要成员

mInputManager:管理每个窗口的输入事件通道InputChannel以及向通道上派发事件。
mAnimator:所有窗口动画的总管(窗口动画是WindowStateAnimator对象),在Choreographer的驱动下,逐个渲染所有动画
mPolicy:WindowManagerPolicy的一个实现。目前只有PhoneWindowManager一个实现类。定义了很多窗口相关的策略,为WMS的顾问。监听屏幕旋转的状态,预处理一些系统按键HOME BACK
displayContent:多屏幕
mSessions:一个arraySet,元素类型Session。保存了当前所有想向WMS寻求窗口管理服务的客户端。

4.1.3 初始WMS的小结
WMS客户端如何使用窗口,和一些成员变量

4.2 WMS的窗口管理结构
例中添加窗口的函数是IWindowSession.add(). IWindowSession是WMS与客户端交互的一个代理,add直接调用WMS的addWindow()函数。

public int addWindow(Session session, IWindow client, int seq,
LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
int[] appOp = new int[1];
首先检查权限,没有权限的客户端不能添加窗口
int res = mPolicy.checkAddPermission(attrs, appOp);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
boolean reportNewConfig = false;
WindowState parentWindow = null; 当为某个窗口添加子窗口时,parentWindow 将用来保存父窗口的实例
long origId;
final int callingUid = Binder.getCallingUid();
final int type = attrs.type;
synchronized(mWindowMap) {
if (!mDisplayReady) {
throw new IllegalStateException(“Display has not been initialialized”);
}
获取窗口要添加的DisplayContent,在添加窗口时,必须通过displayId参数指定添加到哪个DisplayContent,例中没有指定session会为其选择DEFAULT_DISPLAY
final DisplayContent displayContent = getDisplayContentOrCreate(displayId);

如果要添加的窗口是另一个子窗口,就要求父窗口必须已经存在
Attrs.type表示窗口的类型,attrs.token表示窗口所隶属的对象,对于子窗口来说,则表示其父窗口
if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
parentWindow = windowForClientLocked(null, attrs.token, false);
if (parentWindow == null) {
Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
+ attrs.token + “. Aborting.”);
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
WMS要求窗口的层级关系最多为两层
if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
&& parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
+ attrs.token + “. Aborting.”);
return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
}
}
…根据客户端的attrs.token取出已注册的WindowToken
WindowToken token = displayContent.getWindowToken(
hasParent ? parentWindow.mAttrs.token : attrs.token);
final int rootType = hasParent ? parentWindow.mAttrs.type : type;
boolean addToastWindowRequiresToken = false;
if (token == null) {
if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
Slog.w(TAG_WM, "Attempted to add application window with unknown token "
+ attrs.token + “. Aborting.”);
return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
}
…其他类型的窗口则不需要事先向WMS添加WindowToken,因为WMS会在 这里隐式的创建一个。False表示WindowToken由WMS隐式创建
final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
final boolean isRoundedCornerOverlay =
(attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
token = new WindowToken(this, binder, type, false, displayContent,
session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
}
对于APPLICATION类型的窗口,要求对应的WindowToken类型也为APPLICATION,并且是WindowToken的子类:asAppWindowToken
else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
atoken = token.asAppWindowToken();
if (atoken == null) {
Slog.w(TAG_WM, "Attempted to add window with non-application token "
+ token + “. Aborting.”);
return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
} else if (atoken.removed) {
Slog.w(TAG_WM, “Attempted to add window with exiting application token "
+ token + “. Aborting.”);
return WindowManagerGlobal.ADD_APP_EXITING;
} else if (type == TYPE_APPLICATION_STARTING && atoken.startingWindow != null) {
Slog.w(TAG_WM, “Attempted to add starting window to token with already existing”
+ " starting window”);
return WindowManagerGlobal.ADD_DUPLICATE_ADD;
}
}
…WMS为要添加的窗口创建一个WindowState 对象,这个对象维护了一个窗口的所有状态信息
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid,
session.mCanAddInternalSystemWindow);
…这个函数会调整LayoutParams成员的取值
mPolicy.adjustWindowParamsLw(win, win.mAttrs, hasStatusBarServicePermission);
win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));

res = mPolicy.prepareAddWindowLw(win, attrs);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;
}
…将WindowState对象加入mWindowMap中
win.attach();
mWindowMap.put(client.asBinder(), win);

AddWindow()函数展示了三个概念,WindowToken、WindowState、DisplayContent。除子窗口外,其他任何一个窗口都必须指明所属的WindowToken,窗口在WMS中通过一个WindowState实例进行管理和保管。同时必须在窗口中指明所属的DisplayContent,以便确定窗口显示在哪个屏幕上。

4.2.1 理解WindowToken
1 WindowToken的意义

1将属于同一应用组件的窗口组织起来。应用组件可以是Activity、InputMethod、Wallpaper。在WMS对窗口的管理中,用WindowToken指代一个应用组件。
2 是对应用组件的行为进行规范管理的手段。WindowToken由应用组件或管理者负责向WMS声明并持有。应用组件在需要新的窗口时,必须提供WindowToken表明自己身份,并且窗口的类型必须和所持有的WindowToken类型一致。创建系统类型的窗口时不需要声明WindowToken,wms会隐式声明一个。开始时就检查了权限,并非所有都可添加系统窗口。

2 向WMS声明WindowToken
在例中使用wms.addWindowToken()函数声明mToken作为令牌。所以在添加窗口时,通过设置lp.token为mToken向WMS出示,从而获得WMS添加窗口的许可。

@Override
public void addWindowToken(IBinder binder, int type, int displayId) {
声明Token的调用者拥有MANAGE_APP_TOKENS的权限
if (!checkCallingPermission(MANAGE_APP_TOKENS, “addWindowToken()”)) {
throw new SecurityException(“Requires MANAGE_APP_TOKENS permission”);
}

synchronized(mWindowMap) {
    final DisplayContent dc = mRoot.getDisplayContent(displayId);
    WindowToken token = dc.getWindowToken(binder);
    if (token != null) {
        Slog.w(TAG_WM, "addWindowToken: Attempted to add binder token: " + binder
                + " for already created window token: " + token
                + " displayId=" + displayId);
        return;
    }
    if (type == TYPE_WALLPAPER) {
        new WallpaperWindowToken(this, binder, true, dc,
                true /* ownerCanManageAppTokens */);
    } else {
   构造函数和addWindow不同最后一个参数为true
        new WindowToken(this, binder, type, true, dc, true /* ownerCanManageAppTokens */);
    }
}

}

显式创建的token,必须调用removeWindowToken()才能移除
addWindowToken()函数告诉我们,WindowToken有两层含义:
1 对显示组件(客户端)而言的Token,是任意一个Binder的实例,对显示组件来说仅是创建窗口的令牌。
2 对WMS而言的WindowToken,保存了对应于客户端的Token,存储于mTokenMap中。

显示组件是如何声明WindowToken
1 Wallpaper和InputMethod的Token
Wallpaper的Token声明在WallpaperManagerService中
boolean bindWallpaperComponentLocked(ComponentName componentName, boolean force,
boolean fromUser, WallpaperData wallpaper, IRemoteCallback reply) {
…一个简单的binder对象,这个token将在即将显示的wallpaper被连接时传递给它
WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper);

try {
if (wallpaper.userId == mCurrentUserId) {
if (DEBUG)
Slog.v(TAG, "Adding window token: " + newConn.mToken);
mIWindowManager.addWindowToken(newConn.mToken, TYPE_WALLPAPER, DEFAULT_DISPLAY);
mLastWallpaper = wallpaper;
}
}
…WallpaperManagerService向WMS声明的Token类型为TPYE_WALLPAPER,所以wallpaper仅能本分地创建TPYE_WALLPAPER类型的窗口。
void detachWallpaperLocked(WallpaperData wallpaper) {

mIWindowManager.removeWindowToken(wallpaper.connection.mToken, DEFAULT_DISPLAY);

}

InputMethod的Token来源与WallPaper类似。声明在InputMethodManagerService.java

InputBindResult startInputInnerLocked() {

mIWindowManager.addWindowToken(mCurToken, TYPE_INPUT_METHOD, DEFAULT_DISPLAY);

}

取消声明的位置
void unbindCurrentMethodLocked(boolean savePosition) {

mIWindowManager.removeWindowToken(mCurToken, DEFAULT_DISPLAY);

}

2 Activity的Token
位置位于ActivityStack.java
移除token removeActivityFromHistoryLocked
Activity的Token定义IApplicationToken.Stub appToken 其中定义了一系列和窗口相关的通知回调
windowsDrawn() 当窗口完成初次绘制后通知AMS
windowsVisible() 当窗口可见时通知AMS
windowsGone() 窗口不可见时通知AMS
keyDispatchingTimeout() 窗口没能按时完成输入事件的处理,回调将导致anr
getKeyDispatchingTimeout() 从AMS处获取界定anr的时间

4.2.2 理解WindowState
从WMS.addWindow(),当WMS添加一个窗口时,WMS会为其创建一个WindowState。WindowState表示一个窗口的所有属性,所以它是WMS中事实上的窗口。
WS在显示组件一侧也有对应的类型:IWindow.Stub提供了很多和窗口管理相关通知的回调,例如尺寸、焦点变化。

4.2.3 理解DisplayContent
隶属于同一个DC的窗口将被显示在同一个屏幕

4.3 理解窗口的显示次序
在addWindow()函数,WMS为窗口创建了用于描述窗口状态的WindowState,接下来便为新建的窗口确定显示次序。左上角为原点,向右为x,向下为y,向上为z

4.3.1 主序、子序和窗口类型
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow,
PowerManagerWrapper powerManagerWrapper) {
…为普通窗口分配ZOrder
else {
// The multiplier here is to reserve space for multiple
// windows in the same type layer.
主序 详见深入3-113
mBaseLayer = mPolicy.getWindowLayerLw(this)
* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
子序
mSubLayer = 0;
mIsChildWindow = false;
mLayoutAttached = false;
mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD
|| mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
mIsWallpaper = mAttrs.type == TYPE_WALLPAPER;
}

}

4.3.2 通过主序与子序确定窗口的次序
详见深入3-115 与现代码不一致

4.3.3 更新显示次序到Surface

4.3.4 关于显示次序的小结
WMS动画系统将最终的显示次序通过Surface.setLayout()设置进SurfaceFlinger。

4.4 窗口的布局
WMS如何对窗口进行布局。例中将窗口添加到WMS后,需要执行IWindowSession.relayout()函数后才能获得一块作画的Surface,并放置在指定位置。IWindowSession.relayout()作用就在于根据客户端提供的布局参数(LayoutParameters)为窗口的重建Surface,将其放置在屏幕的指定位置。IWindowSession.relayoutWindow()对应WMS的relayoutWindow()

详见深入3-120 与现代码不一致
WindowManagerService.relayoutWindow() 根据传入的参数执行更新
创建window的可见性更新或创建Surface及启动动画效果
public int relayoutWindow(Session session, IWindow client, int seq, LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility, int flags,
long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,
DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration,
Surface outSurface) {

synchronized(mWindowMap) {
从mWindowMap中获取需要进行relayout的WindowState,relayoutWindow()根据传入的参数更新WindowState的属性。
WindowState win = windowForClientLocked(session, client, false);

win.mSystemUiVisibility = systemUiVisibility;

}

4.4.2 布局操作的外围代码分析
4.5 WMS的动画系统

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值