窗口管理核心类:DisplayContent,WindowToken和WindowState
1. 概念
Android WMS 中 DisplayContent 用来管理一个逻辑屏上的所有窗口,
有几个屏幕就会有几个DisplayContent。使用displayId来区分。
2. 源码
基于 Android 14 源码. (20230911 codesearch main 分支)
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
package com.android.server.wm;
/**
* Utility class for keeping track of the WindowStates and other pertinent contents of a
* particular Display.
*/
class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {
private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayContent" : TAG_WM;
}
用于跟踪特定 Display 的 WindowStates 和其他相关内容的 辅助类。
可以看到它继承了 RootDisplayArea, 以及实现了接口 WindowManagerPolicy.DisplayContentInfo
2.1 构造方法
里面注入两个参数,分别为 Display 和 RootWindowContainer 的对象.
显示的内容由 Display 提供, 包含displayId/displayInfo/displayMetrics 等
/**
* Create new {@link DisplayContent} instance, add itself to the root window container and
* initialize direct children.
* @param display May not be null.
* @param root {@link RootWindowContainer}
*/
DisplayContent(Display display, RootWindowContainer root) {
super(root.mWindowManager, "DisplayContent", FEATURE_ROOT);
if (mWmService.mRoot.getDisplayContent(display.getDisplayId()) != null) {
throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
+ " already exists="
+ mWmService.mRoot.getDisplayContent(display.getDisplayId())
+ " new=" + display);
}
mRootWindowContainer = root;
mAtmService = mWmService.mAtmService;
mDisplay = display;
mDisplayId = display.getDisplayId();
mCurrentUniqueDisplayId = display.getUniqueId();
mOffTokenAcquirer = mRootWindowContainer.mDisplayOffTokenAcquirer;
mWallpaperController = new WallpaperController(mWmService, this);
mWallpaperController.resetLargestDisplay(display);
display.getDisplayInfo(mDisplayInfo);
display.getMetrics(mDisplayMetrics);
mSystemGestureExclusionLimit = mWmService.mConstants.mSystemGestureExclusionLimitDp
* mDisplayMetrics.densityDpi / DENSITY_DEFAULT;
isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
mInsetsStateController = new InsetsStateController(this);
mDisplayFrames = new DisplayFrames(mInsetsStateController.getRawInsetsState(),
mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation),
calculateRoundedCornersForRotation(mDisplayInfo.rotation),
calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation));
initializeDisplayBaseInfo();
mHoldScreenWakeLock = mWmService.mPowerManager.newWakeLock(
PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
TAG_WM + "/displayId:" + mDisplayId, mDisplayId);
mHoldScreenWakeLock.setReferenceCounted(false);
mAppTransition = new AppTransition(mWmService.mContext, mWmService, this);
mAppTransition.registerListenerLocked(mWmService.mActivityManagerAppTransitionNotifier);
mAppTransition.registerListenerLocked(mFixedRotationTransitionListener);
mAppTransitionController = new AppTransitionController(mWmService, this);
mTransitionController.registerLegacyListener(mFixedRotationTransitionListener);
mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
mDisplaySwitchTransitionLauncher = new PhysicalDisplaySwitchTransitionLauncher(this,
mTransitionController);
mRemoteDisplayChangeController = new RemoteDisplayChangeController(mWmService, mDisplayId);
final InputChannel inputChannel = mWmService.mInputManager.monitorInput(
"PointerEventDispatcher" + mDisplayId, mDisplayId);
mPointerEventDispatcher = new PointerEventDispatcher(inputChannel);
// Tap Listeners are supported for:
// 1. All physical displays (multi-display).
// 2. VirtualDisplays on VR, AA (and everything else).
mTapDetector = new TaskTapPointerEventListener(mWmService, this);
registerPointerEventListener(mTapDetector);
registerPointerEventListener(mWmService.mMousePositionTracker);
if (mWmService.mAtmService.getRecentTasks() != null) {
registerPointerEventListener(
mWmService.mAtmService.getRecentTasks().getInputListener());
}
mDeviceStateController = new DeviceStateController(mWmService.mContext, mWmService.mH);
mDisplayPolicy = new DisplayPolicy(mWmService, this);
mDisplayRotation = new DisplayRotation(mWmService, this, mDisplayInfo.address,
mDeviceStateController);
final Consumer<DeviceStateController.DeviceState> deviceStateConsumer =
(@NonNull DeviceStateController.DeviceState newFoldState) -> {
mDisplaySwitchTransitionLauncher.foldStateChanged(newFoldState);
mDisplayRotation.foldStateChanged(newFoldState);
};
mDeviceStateController.registerDeviceStateCallback(deviceStateConsumer);
mCloseToSquareMaxAspectRatio = mWmService.mContext.getResources().getFloat(
R.dimen.config_closeToSquareDisplayMaxAspectRatio);
if (isDefaultDisplay) {
// The policy may be invoked right after here, so it requires the necessary default
// fields of this display content.
mWmService.mPolicy.setDefaultDisplay(this);
}
if (mWmService.mDisplayReady) {
mDisplayPolicy.onConfigurationChanged();
}
if (mWmService.mSystemReady) {
mDisplayPolicy.systemReady();
}
mWindowCornerRadius = mDisplayPolicy.getWindowCornerRadius();
mDividerControllerLocked = new DockedTaskDividerController(this);
mPinnedTaskController = new PinnedTaskController(mWmService, this);
final Transaction pendingTransaction = getPendingTransaction();
configureSurfaces(pendingTransaction);
pendingTransaction.apply();
// Sets the display content for the children.
onDisplayChanged(this);
updateDisplayAreaOrganizers();
mDisplayRotationCompatPolicy =
// Not checking DeviceConfig value here to allow enabling via DeviceConfig
// without the need to restart the device.
mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
/* checkDeviceConfig */ false)
? new DisplayRotationCompatPolicy(this) : null;
mRotationReversionController = new DisplayRotationReversionController(this);
mInputMonitor = new InputMonitor(mWmService, this);
mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display);
setWindowingMode(WINDOWING_MODE_FULLSCREEN);
mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
}
2.2 关键方法
migrateToNewSurfaceControl()
@Override
void migrateToNewSurfaceControl(Transaction t) {
t.remove(mSurfaceControl);
mLastSurfacePosition.set(0, 0);
mLastDeltaRotation = Surface.ROTATION_0;
configureSurfaces(t);
for (int i = 0; i < mChildren.size(); i++) {
SurfaceControl sc = mChildren.get(i).getSurfaceControl();
if (sc != null) {
t.reparent(sc, mSurfaceControl);
}
}
scheduleAnimation();
}
/**
addWindowToken
void addWindowToken(IBinder binder, WindowToken token) {
final DisplayContent dc = mWmService.mRoot.getWindowTokenDisplay(token);
if (dc != null) {
// We currently don't support adding a window token to the display if the display
// already has the binder mapped to another token. If there is a use case for supporting
// this moving forward we will either need to merge the WindowTokens some how or have
// the binder map to a list of window tokens.
throw new IllegalArgumentException("Can't map token=" + token + " to display="
+ getName() + " already mapped to display=" + dc + " tokens=" + dc.mTokenMap);
}
if (binder == null) {
throw new IllegalArgumentException("Can't map token=" + token + " to display="
+ getName() + " binder is null");
}
if (token == null) {
throw new IllegalArgumentException("Can't map null token to display="
+ getName() + " binder=" + binder);
}
mTokenMap.put(binder, token);
if (token.asActivityRecord() == null) {
// Set displayContent for non-app token to prevent same token will add twice after
// onDisplayChanged.
// TODO: Check if it's fine that super.onDisplayChanged of WindowToken
// (WindowsContainer#onDisplayChanged) may skipped when token.mDisplayContent assigned.
token.mDisplayContent = this;
// Add non-app token to container hierarchy on the display. App tokens are added through
// the parent container managing them (e.g. Tasks).
final DisplayArea.Tokens da = findAreaForToken(token).asTokens();
da.addChild(token);
}
}
updateImeParent
void updateImeParent() {
if (mImeWindowsContainer.isOrganized()) {
if (DEBUG_INPUT_METHOD) {
Slog.i(TAG_WM, "ImeContainer is organized. Skip updateImeParent.");
}
// Leave the ImeContainer where the DisplayAreaPolicy placed it.
// FEATURE_IME is organized by vendor so they are responible for placing the surface.
mInputMethodSurfaceParent = null;
return;
}
final SurfaceControl newParent = computeImeParent();
if (newParent != null && newParent != mInputMethodSurfaceParent) {
mInputMethodSurfaceParent = newParent;
getSyncTransaction().reparent(mImeWindowsContainer.mSurfaceControl, newParent);
// When surface parent is removed, the relative layer will also be removed. We need to
// do a force update to make sure there is a layer set for the new parent.
assignRelativeLayerForIme(getSyncTransaction(), true /* forceUpdate */);
scheduleAnimation();
mWmService.mH.post(() -> InputMethodManagerInternal.get().onImeParentChanged());
}
}
999. 参考
https://zhuanlan.zhihu.com/p/588860452 (不错,介绍了DisplayConten/WindowToken/WindowState/Surface... )
其它相关:
Android Surface 学习笔记: https://www.jianshu.com/p/b496c7850d4a
在UI上看到的每一个window(如对话框、全屏的activity、状态栏)都有唯一一个自己的surface,
window将自己的内容(content)绘制到该 surface中。
所以从上层到底层: Activity - Window - Surface