从这篇文章开始,我们来看下WindowManagerService相关的内容。WMS作为android非常重要的一个模块,我们在平时的开发中其实一直在接触着。从名字上看,翻译成中文叫做窗口管理,但是如果我们去看他的源码,他涉及到的模块远远不止"窗口管理“这么简单,如果我们看过AMS源码的话,从AMS源码中我们就可以看到许多和WMS相关的部分,所以如果我们要分析WMS源码,起码首先要对AMS执行流程有一定的了解,这样才能更好的知道WMS最基本的对一个Activity应用窗口是怎么管理的。另外在窗户启动后,还涉及到动画,view的绘制,从view中又会引出surfaceFlinger,这个是最终控制把view绘制到屏幕上的模块,另外还有对输入事件控制的模块InputManagerService等等。可以说WMS涉及到的模块太多了,如果把这些模块都理解透了,基本framework一半以上就都理解了。我们本文主要还是针对WMS本身自己的核心功能来描述,其他模块会在后续陆续地在来分析,一方面WMS本身概念其实也挺多的,光是讲解WMS已经内容不少了。其次,framework内容博大精深,虽然每个模块的功能尽量独立,遵守高内聚低耦合的原则,但是就像前面说的WMS会涉及到很多其他模块,所以其实很多流程如果仅仅看每个模块自己代码,有时候是比较难理解的,所以每次看源码常常会有理解又进了一步的感觉,所以先把WMS给大致理解了,也就为理解其他模块打下一个基础。好了,话不多说,我们开始从系统注册WMS说起,这里是android 8的版本,和之前我们分析的系统模块版本保持一致。
# 启动WMS
之前我们有分析过android的开机启动流程,见这篇文章[android启动之SystemServer进程](https://liqi.site/archives/a-n-d-r-o-i-d-qi-dong-zhi-z-y-g-o-t-e-jin-cheng--er-#RaKztarJ)。在SystemServer进程中会启动各种系统Service,其中WMS也是在这里启动的,我们看下方法
```java
private void startOtherServices() {
...........
wm = WindowManagerService.main(context, inputManager,
mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
!mFirstBoot, mOnlyCore, new PhoneWindowManager());
ServiceManager.addService(Context.WINDOW_SERVICE, wm);
...........
}
```
在startOtherServices方法里启动的方法很多,我们只看WMS的,就是上面截取的这段代码,我们看到会通过WindowManagerService.main方法创建一个WindowManagerService的实例,然后注册到ServiceManager。我们看下WindowManagerService的main方法:
```java
public static WindowManagerService main(final Context context, final InputManagerService im,
final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore,
WindowManagerPolicy policy) {
// 调用DisplayThread的Handler来创建WindowManagerService
// 这个方法调用者线程比如是SystemServer,会把new WindowManagerService的任务post给DisplayThread线程去做
// 然后SystemServer自己阻塞,等待DisplayThread线程new好了WindowManagerService后,唤醒SystemServer
// 一句话,先new DisplayThread,再new WindowManagerService,最后唤醒sytemServer
// 这里new WindowManagerService是在DisplayThread线程中创建的
DisplayThread.getHandler()
.runWithScissors(() -> sInstance = new WindowManagerService(context, im, haveInputMethods, showBootMsgs,
onlyCore, policy), 0);
return sInstance;
}
```
这里就是main方法了,这里我们可以看到会通过DisplayThread现场的Handler的runWithScissors方法来创建WindowManagerService。关于这里使用Handler的runWithScissors方法,这个方法我们平时一般用的不多,不过使用的场景遇到的应该还是不少的。这个方法的作用是,如果当前线程继续往后执行前,需要在另一个线程中完成一个前序任务,那么可以通过runWithScissors这个方法在另一个线程完成任务前阻塞本线程,这样就可以达到目的。我们稍稍看下这个方法:
```java
public final boolean runWithScissors(final Runnable r, long timeout) {
if (r == null) {
throw new IllegalArgumentException("runnable must not be null");
}
if (timeout < 0) {
throw new IllegalArgumentException("timeout must be non-negative");
}
// mLooper不是调用者的Looper
if (Looper.myLooper() == mLooper) {// 如果当前线程和Handler是同一个线程,runnable会立刻执行
r.run();
return true;
}
// 把这个runnable封装成BlockingRunnable对象
BlockingRunnable br = new BlockingRunnable(r);
// 调用BlockingRunnable的postAndWait方法,把这个runnable加入到这个Handler中
// 走到这里了,这个handler所在线程肯定不是调用者的线程。timeou是超时时间,即
// 距离当前时间后多少时间,是个相对值
return br.postAndWait(this, timeout); // 这里this不是调用者的Handler,和上面mLooper是一个
}
```
这个方法开始先做一些校验,之后会通过Looper来判断,当前线程的Looper和这个Handler的Looper是否是同一个,如果是同一个的话,那么也不存在2个线程首先执行的问题了,直接执行任务就可以了。如果不是同一个的话,会先把要执行的Runnable封装为BlockingRunnable对象,然后调用这个对象的postAndWait方法,我们看下BlockingRunnable这个类:
```java
private static final class BlockingRunnable implements Runnable {
private final Runnable mTask;
private boolean mDone;
public BlockingRunnable(Runnable task) {
mTask = task;
}
@Override
public void run() {
try {
mTask.run();
} finally {
synchronized (this) {
mDone = true;
notifyAll(); // 执行完成后,唤醒调用postAndWait方法的线程
}
}
}
// 一般来说,调用这个方法的线程和这里handler参数的线程不是同一个,handler不是调用者线程
public boolean postAndWait(Handler handler, long timeout) {
if (!handler.post(this)) { // 把当前这个BlockingRunnable加入到handler线程的队列中
return false; // 加入Handler的消息队列失败
}
// 这里阻塞的是调用者
synchronized (this) {
if (timeout > 0) {
// 超时时间,expirationTime是个绝对时间值
final long expirationTime = SystemClock.uptimeMillis() + timeout;
while (!mDone) { // 循环测试是否这个消息已经执行了
// 计算还有多久要超时
long delay = expirationTime - SystemClock.uptimeMillis();
if (delay <= 0) { // 走到这里说明超时了,并且还没执行
return false; // timeout
}
try {
// 阻塞delay时间,这里阻塞的是调用线程,非handler线程
// 这里只有在delay时间到才会被唤醒,或者已经执行了mDone为true
// 的时候才会唤醒。这里应该不会有死锁问题,但是时间可能会长一点,实际
// 产品中体验可以不佳。
wait(delay);
} catch (InterruptedException ex) {
}
}
} else { // 走到这里只有timeout是0的时候,如果没有执行,会永久阻塞,直到主动被唤醒
while (!mDone) {
try {
// 这里由于是永久被阻塞,如果handler没有执行这个消息,这个方法就会一直阻塞
// 如果这个调用线程还持有其他锁,,可能会产生死锁问题
// 如果使用这个方法,需要执行这个消息的线程不能退出,比如主线程,或者使用安全退出
// 才能保证不会有死锁。MessageQueue的quit和enqueueMessage方法都是线程安全的,
// 所以只要能安全退出,就可以保证不死锁。
// 另外就是调用postAndWait这个方法的超时尽量不要传0,减少风险。
wait();
} catch (InterruptedException ex) {
}
}
}
}
return true;
}
}
```
BlockingRunnable这个类是Runnable的子类,除了重写run这个方法外,他还有一个就是前面runWithScissors方法最后调用的postAndWait方法了。我们先看重写的run方法,在这个方法里面除了执行传入的Runnable任务外,最后会把变量mDone置为true,表示任务完成,然后唤醒本线程。这2个的处理我们要结合postAndWait方法一起看。
postAndWait方法有两个参数,第一个参数就是要执行这个Runnable的Handler,第二个参数表示要在多少时间内执行,还没执行的话就超时了。所以这里我们看到如果超时时间大于0,并且mDone为false,表示还没执行,那么就会计算从现在开始多少时间后超时,如果超时了还没执行就返回false了,否则会调用wait方法注册当前调用的线程,这时候当前线程也就阻塞了,也就达到了在这个任务完成前,当前线程不往下执行的目的。最后当前这个Runnable完成后,前面我们看到会把mDone置为true,并且唤醒当前线程,这样当前线程就继续往下执行了。
使用这个方法有一个地方需要注意,上面postAndWait方法,第二个参数超时时间如果为0的话,如果这个Runnable没有执行的话,最后会调用wait方法永久被阻塞,如果这个Runnable一直都没执行,那么当前线程可能就一直被阻塞了,如果当前线程持有一些公共资源那么就可能会导致死锁。所以这里如果超时时间小于等于0的话,需要保证执行的Handler所在线程不能突然退出,或者退出的时候能安全退出,保存能执行完所有的任务。
好了,稍微偏离主题了一下,我们回到WMS中,之所以使用Handler的runWithScissors方法是因为创建WindowManagerService后,后面很多地方需要用到WMS,所以需要等待WMS创建完后才能继续往下执行。
# 创建WMS
接着我们看下WindowManagerService的构造方法:
```java
// WMS的构造方法,这里new的是在display线程中创建的
private WindowManagerService(Context context, InputManagerService inputManager,
boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
WindowManagerPolicy policy) {
// 初始化一些提供给固定类型service用的锁,以本servce为值
installLock(this, INDEX_WINDOW);
// 初始化RootWindow
mRoot = new RootWindowContainer(this);
mContext = context;
mHaveInputMethods = haveInputMethods;
mAllowBootMessages = showBootMsgs;
mOnlyCore = onlyCore;
// 一些和窗口显示相关的参数
mLimitedAlphaCompositing = context.getResources().getBoolean(
com.android.internal.R.bool.config_sf_limitedAlpha);
mHasPermanentDpad = context.getResources().getBoolean(
com.android.internal.R.bool.config_hasPermanentDpad);
mInTouchMode = context.getResources().getBoolean(
com.android.internal.R.bool.config_defaultInTouchMode);
mDrawLockTimeoutMillis = context.getResources().getInteger(
com.android.internal.R.integer.config_drawLockTimeoutMillis);
mAllowAnimationsInLowPowerMode = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowAnimationsInLowPowerMode);
// 获取屏幕允许最大宽度
mMaxUiWidth = context.getResources().getInteger(
com.android.internal.R.integer.config_maxUiWidth);
// 获取IMS
mInputManager = inputManager; // Must be before createDisplayContentLocked.
// 获取DisplayManagerInternal
mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
mDisplaySettings = new DisplaySettings();
mDisplaySettings.readSettingsLocked();
// 窗口大小的测量类
mWindowPlacerLocked = new WindowSurfacePlacer(this);
// 获取WindowManagerPolicy
mPolicy = policy;
mTaskSnapshotController = new TaskSnapshotController(this);
// 添加WindowManagerPolicy到Service
LocalServices.addService(WindowManagerPolicy.class, mPolicy);
// IMS相关
if (mInputManager != null) {
final InputChannel inputChannel = mInputManager.monitorInput(TAG_WM);
mPointerEventDispatcher = inputChannel != null
? new PointerEventDispatcher(inputChannel)
: null;
} else {
mPointerEventDispatcher = null;
}
mFxSession = new SurfaceSession();
// 获取DisplayManager
mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
// 获取所有屏幕
mDisplays = mDisplayManager.getDisplays();
// 遍历每块屏幕,把Display封装为DisplayContent
for (Display display : mDisplays) {
createDisplayContentLocked(display);
}
mKeyguardDisableHandler = new KeyguardDisableHandler(mContext, mPolicy);
// 获取PowerManager
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
// 低电量监听
if (mPowerManagerInternal != null) {
mPowerManagerInternal.registerLowPowerModeObserver(
new PowerManagerInternal.LowPowerModeListener() {
@Override
public int getServiceType() {
return ServiceType.ANIMATION;
}
@Override
public void onLowPowerModeChanged(PowerSaveState result) {
synchronized (mWindowMap) {
final boolean enabled = result.batterySaverEnabled;
if (mAnimationsDisabled != enabled && !mAllowAnimationsInLowPowerMode) {
mAnimationsDisabled = enabled;
dispatchNewAnimatorScaleLocked(null);
}
}
}
});
mAnimationsDisabled = mPowerManagerInternal
.getLowPowerState(ServiceType.ANIMATION).batterySaverEnabled;
}
mScreenFrozenLock = mPowerManager.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
mScreenFrozenLock.setReferenceCounted(false);
mAppTransition = new AppTransition(context, this);
mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier);
// 动画相关
final AnimationHandler animationHandler = new AnimationHandler();
animationHandler.setProvider(new SfVsyncFrameCallbackProvider());
mBoundsAnimationController = new BoundsAnimationController(context, mAppTransition,
AnimationThread.getHandler(), animationHandler);
// 获取AMS
mActivityManager = ActivityManager.getService();
mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
AppOpsManager.OnOpChangedInternalListener opListener = new AppOpsManager.OnOpChangedInternalListener() {
@Override
public void onOpChanged(int op, String packageName) {
updateAppOpsState();
}
};
mAppOps.startWatchingMode(OP_SYSTEM_ALERT_WINDOW, null, opListener);
mAppOps.startWatchingMode(AppOpsManager.OP_TOAST_WINDOW, null, opListener);
// 一些和窗口动画相关的参数
// Get persisted window scale setting
mWindowAnimationScaleSetting = Settings.Global.getFloat(context.getContentResolver(),
Settings.Global.WINDOW_ANIMATION_SCALE, mWindowAnimationScaleSetting);
mTransitionAnimationScaleSetting = Settings.Global.getFloat(context.getContentResolver(),
Settings.Global.TRANSITION_ANIMATION_SCALE,
context.getResources().getFloat(
R.dimen.config_appTransitionAnimationDurationScaleDefault));
setAnimatorDurationScale(Settings.Global.getFloat(context.getContentResolver(),
Settings.Global.ANIMATOR_DURATION_SCALE, mAnimatorDurationScaleSetting));
// 注册一些设置管理策略变化等的广播
IntentFilter filter = new IntentFilter();
// Track changes to DevicePolicyManager state so we can enable/disable keyguard.
filter.addAction(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
// Listen to user removal broadcasts so that we can remove the user-specific
// data.
filter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiver(mBroadcastReceiver, filter);
// 设置监听
mSettingsObserver = new SettingsObserver();
mHoldingScreenWakeLock = mPowerManager.newWakeLock(
PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM);
mHoldingScreenWakeLock.setReferenceCounted(false);
// 窗口动画
mAnimator = new WindowAnimator(this);
mAllowTheaterModeWakeFromLayout = context.getResources().getBoolean(
com.android.internal.R.bool.config_allowTheaterModeWakeFromWindowLayout);
LocalServices.addService(WindowManagerInternal.class, new LocalService());
// 初始化窗口管理策略
initPolicy();
// 注册监听WMS是否有发生错误的回调
// Add ourself to the Watchdog monitors.
Watchdog.getInstance().addMonitor(this);
openSurfaceTransaction();
try {
createWatermarkInTransaction();
} finally {
closeSurfaceTransaction();
}
showEmulatorDisplayOverlayIfNeeded();
}
```
这里创建WMS的代码也是非常长,我们也没必要全部了解,上面大部分主要的代码,我这里都简单注释了一下,我们可以看到这里有几个类后面我们分析WMS时候会重点用到,这里先简单提一下。首先这里new了一个RootWindowContainer,从这个名字可以看出他是一个窗口容器,我们后面会遇到好多个窗口容器,他们都代表着某类窗口,这里这个RootWindowContainer里面装着的元素代表一个逻辑上的屏幕,比如我们手机上的屏幕会抽象为一个DisplayContent类,这里RootWindowContainer装着的就是DisplayContent。
除了RootWindowContainer外,还有InputManager,这个是处理屏幕的输入事件的,这个模块我们这里不说,后面有机会会专门在分析InputManager。
这里还会new一个WindowManagerPolicy类,这个类是设置当前默认显示屏幕相关的一些操作,比如设置窗口层级,指定类型,布局等等,后面我们会遇到这个类的使用。
此外还有WindowSurfacePlacer类,这个是测量窗口大小时候用的,主要是对View的测量。
还有获得PowerManager,这个是管理电量的。
AppOpsManager是管理权限的。
WindowAnimator这个类,是和窗口动画相关的。
DisplayManager是管理显示屏幕有关的。
还有一些监听设置的监听类,这里就不一一说了,基本上WMS依赖着非常多的其他模块,我们在后面分析具体流程的时候遇到了在说,这里我们就看下这显示屏幕相关的一段代码,因为这个和窗口的概念最接近,以便我们对下一步介绍窗口相关概念前有个大概的感觉。
```java
mDisplays = mDisplayManager.getDisplays();
// 遍历每块屏幕,把Display封装为DisplayContent
for (Display display : mDisplays) {
createDisplayContentLocked(display);
}
```
上面这段代码是前面创建WMS类的时候中的一小段,这里主要的含义是通过DisplayManager获取代表所有屏幕类Display,然后为每个屏幕创建一个DisplayContent,把这些DisplayContent加入到前面说过的RootWindowContainer中去。
这里的DisplayContent代表着和一个屏幕相关的内容,我们的手机除了主屏外,其实还有其他的屏幕,比如现在折叠机有外屏,还有比如手机外接显示器之类的也是一个屏幕,这些屏幕会用Display类来描述,Display是从DisplayManager中获取的,具体获取的过程就不跟过去了,后面有机会分析DisplayManager的时候再说,我们这里专门分析WMS,上面的mDisplays就是获取所有屏幕的Display的数组,然后遍历这个数组来创建DisplayContent,我们看下创建的方法:
```java
private void createDisplayContentLocked(final Display display) {
if (display == null) {
throw new IllegalArgumentException("getDisplayContent: display must not be null");
}
mRoot.getDisplayContentOrCreate(display.getDisplayId());
}
```
这个方法最后会调用RootWindowContainer的getDisplayContentOrCreate,参数是每个Display的id。
```java
DisplayContent getDisplayContentOrCreate(int displayId) {
// 获取屏幕的getDisplayContent
DisplayContent dc = getDisplayContent(displayId);
if (dc == null) { // 如果是空,从WMS中获取Display
final Display display = mService.mDisplayManager.getDisplay(displayId);
if (display != null) {
final long callingIdentity = Binder.clearCallingIdentity();
try {
// 创建DisplayContent
dc = createDisplayContent(display);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
}
}
return dc;
}
```
这个方法看上去不长,涉及到的其他方法其实挺多的。首先看下这个方法的含义,首先根据displayId调用getDisplayContent方法,看是不是已经有这个displayId对应的DisplayContent了如果有就返回,没有的话,会根据displayId从DisplayManager中获取Display,然后根据这个Display创建DisplayContent返回。
这里和DisplayManager相关的方法getDisplay获取Display,我们也不跟进去看了,主要看两个方法,一个是getDisplayContent,通过displayId获取DisplayContent。另一个是createDisplayContent来创建DisplayContent。
```java
protected final WindowList<E> mChildren = new WindowList<E>();
DisplayContent getDisplayContent(int displayId) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final DisplayContent current = mChildren.get(i);
if (current.getDisplayId() == displayId) {
return current;
}
}
return null;
}
```
这个方法很好理解,遍历mChildren集合,寻找集合中displayId相等的元素,如果找到了就返回,没有的话,返回null。前面我们有提到过RootWindowContainer是一个保存所有DisplayContent的容器,他的超类是WindowContainer,这个类我们稍后会说,上面mChildren就是WindowContainer类中的元素,他的类型是WindowList,其实WindowList就是一个集合:
```java
class WindowList<E> extends ArrayList<E> {
void addFirst(E e) {
add(0, e);
}
E peekLast() {
return size() > 0 ? get(size() - 1) : null;
}
E peekFirst() {
return size() > 0 ? get(0) : null;
}
}
```
可以看到他的父类是一个ArrayList,只不过封装了一下获取首位元素的方法,这个我们看一眼就行。我们回到上面getDisplayContent方法介绍完了,我们再看一下createDisplayContent这个方法:
```java
private DisplayContent createDisplayContent(final Display display) {
// new一个DisplayContent,这个构造方法中会把创建的DisplayContent加入到RootWindowContainer中
final DisplayContent dc = new DisplayContent(display, mService, mLayersController,
mWallpaperController);
final int displayId = display.getDisplayId();
if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display);
// 获取DisplayInfo
final DisplayInfo displayInfo = dc.getDisplayInfo();
final Rect rect = new Rect();
// 获取overscan,即过扫描区域,赋值给rect
mService.mDisplaySettings.getOverscanLocked(displayInfo.name, displayInfo.uniqueId, rect);
// 把区域赋值给displayInfo
displayInfo.overscanLeft = rect.left;
displayInfo.overscanTop = rect.top;
displayInfo.overscanRight = rect.right;
displayInfo.overscanBottom = rect.bottom;
```
这个方法我们分两部分来看,这里是第一部分。首先会new一个DisplayConten。 接着会获取DisplayContent中的DisplayInfo对象,DisplayInfo描述的是这个屏幕的一些基础信息,比如宽度,高度,像素密度,旋转角度等等,上面方法中会通过DisplaySettings获取这个屏幕的过扫描区域,所谓过扫描区域就是屏幕的四种都是有一些黑边的,一般来说这些黑边的宽度不同屏幕不一样,所以会影响到实际屏幕可用的区域,这里getOverscanLocked方法会从系统配置文件中获取这些黑边额宽度,然后赋值给rect,最后把过扫描区域复制给displayInfo的四个变量。我们这里先看下DisplayContent的构造方法:
```java
DisplayContent(Display display, WindowManagerService service,
WindowLayersController layersController, WallpaperController wallpaperController) {
// 如果已经有这个DisplayContent了,那么退出
if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
+ " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
+ " new=" + display);
}
mDisplay = display;
mDisplayId = display.getDisplayId();
mLayersController = layersController;
mWallpaperController = wallpaperController;
// 从传入的参数中赋值mDisplayInfo
// 填充mDisplayInfo数据
display.getDisplayInfo(mDisplayInfo);
// 填充mDisplayMetrics
display.getMetrics(mDisplayMetrics);
// 是否是默认显示屏幕
isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
mService = service;
// 从WMS中获取DisplayInfo等信息赋值给mDisplayInfo
initializeDisplayBaseInfo();
mDividerControllerLocked = new DockedStackDividerController(service, this);
mPinnedStackControllerLocked = new PinnedStackController(service, this);
mDimLayerController = new DimLayerController(this);
// 先把下面4个window container插入这个window container容器中
// These are the only direct children we should ever have and they are permanent.
super.addChild(mBelowAppWindowsContainers, null);
super.addChild(mTaskStackContainers, null);
super.addChild(mAboveAppWindowsContainers, null);
super.addChild(mImeWindowsContainers, null);
// Add itself as a child to the root container.
mService.mRoot.addChild(this, null); //把当前这个屏幕加入到rootWindowContainer
// TODO(b/62541591): evaluate whether this is the best spot to declare the
// {@link DisplayContent} ready for use.
mDisplayReady = true; // 这个屏幕已经创建好了
}
```
这个方法里面其实主要就是从Display中获取一些屏幕的基础数据保存在DisplayInfo中,然后会创建一些Controller和Container,这些我们暂不细说,这里设计到一些WMS中的概念,后面我们会接下WMS相关的概念,就知道是什么作用了,这里我们只要知道这些都是保存具体窗口相关的就可以了。最后我们看到有mService.mRoot.addChild(this, null)这句代码,也就是把当前这个DisplayContent添加到RootWindowContainer中,RootWindowContainer前面我们已经知道了这个里面保存的就是DisplayContent元素,所以这里我们就是了每当new一个DisplayContent的时候,就会把自己添加到RootWindowContainer中去,这样一个屏幕就被管理起来了。目前我们就知道创建一个DisplayContent会自动把自己添加到RootWindowContainer就可以了,后面创建具体窗口的时候我们还会看到类似的操作,我们接着看第二部分代码:
```java
if (mService.mDisplayManagerInternal != null) {
// 这里调用的是LocalService中的setDisplayInfoOverrideFromWindowManager方法
// 设置DisplayInfo屏幕信息给DisplayManager中的逻辑屏幕
mService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(
displayId, displayInfo);
mService.configureDisplayPolicyLocked(dc);
// 下面是为屏幕正常触摸的监听回调
// TODO(multi-display): Create an input channel for each display with touch capability.
if (displayId == DEFAULT_DISPLAY && mService.canDispatchPointerEvents()) {
dc.mTapDetector = new TaskTapPointerEventListener(
mService, dc);
mService.registerPointerEventListener(dc.mTapDetector);
mService.registerPointerEventListener(mService.mMousePositionTracker);
}
}
return dc;
```
这部分代码主要做了2件事情,第一件是把前面获取的DisplayInfo屏幕信息设置给DisplayManager中的逻辑屏幕,第二件是注册一些屏幕触摸交互的监听。这两部分对于我们后面分析WMS关系都不太大,不过我们还是稍稍看下设置逻辑屏幕数据的代码,以便加深一点理解,关于输入事件的处理,我们以后会专门通过InputManager模块来详细分析。
这里首先调用DisplayManagerInternal的setDisplayInfoOverrideFromWindowManager,DisplayManagerInternal是DisplayManager的一个内部类LocalService的父类:
```java
private final class LocalService extends DisplayManagerInternal {
...........
@Override
public void setDisplayInfoOverrideFromWindowManager(int displayId, DisplayInfo info) {
setDisplayInfoOverrideFromWindowManagerInternal(displayId, info);
}
............
}
```
这个内部类中的方法实际调用了DisplayManagerService,比如这里额setDisplayInfoOverrideFromWindowManager方法会继续调用setDisplayInfoOverrideFromWindowManagerInternal方法:
```java
private void setDisplayInfoOverrideFromWindowManagerInternal(
int displayId, DisplayInfo info) {
synchronized (mSyncRoot) {
LogicalDisplay display = mLogicalDisplays.get(displayId);
if (display != null) {
if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) {
sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED);
scheduleTraversalLocked(false);
}
}
}
}
```
这个方法根据传入的displayId获取LogicalDisplay,这个类就是在DisplayManager中描述一个逻辑屏幕的数据结构,这里会调用LogicalDisplay的setDisplayInfoOverrideFromWindowManagerLocked方法设置这个逻辑屏幕的DisplayInfo
```java
public boolean setDisplayInfoOverrideFromWindowManagerLocked(DisplayInfo info) {
if (info != null) {
if (mOverrideDisplayInfo == null) {
mOverrideDisplayInfo = new DisplayInfo(info);
mInfo = null;
return true;
}
if (!mOverrideDisplayInfo.equals(info)) {
mOverrideDisplayInfo.copyFrom(info);
mInfo = null;
return true;
}
} else if (mOverrideDisplayInfo != null) {
mOverrideDisplayInfo = null;
mInfo = null;
return true;
}
return false;
}
```
这个方法可以看到会把从WMS中获取到的DisplayInfo赋值给mOverrideDisplayInfo,之后这个屏幕的信息就是使用这个从WMS中获取到的信息了。我们回到前面的createDisplayContent方法,设置完了DisplayInfo给DisplayManager后,最后还会调用WMS的configureDisplayPolicyLocked方法:
```java
void configureDisplayPolicyLocked(DisplayContent displayContent) {
// 调用PhoneWindowManager的setInitialDisplaySize方法
// 主要根据屏幕的宽高,计算屏幕旋转角度和SystemUI显示不显示等
mPolicy.setInitialDisplaySize(displayContent.getDisplay(),
displayContent.mBaseDisplayWidth,
displayContent.mBaseDisplayHeight,
displayContent.mBaseDisplayDensity);
// 获取displayInfo
DisplayInfo displayInfo = displayContent.getDisplayInfo();
// 设置屏幕的过扫描区域
mPolicy.setDisplayOverscan(displayContent.getDisplay(),
displayInfo.overscanLeft, displayInfo.overscanTop,
displayInfo.overscanRight, displayInfo.overscanBottom);
}
```
这里我们看到了会调用WindowManagerPolicy类中的方法,他的实现类是PhoneWindowManager,这个类主要是处理屏幕的一些相关操作,比如这里会初始化屏幕的大小以及设置屏幕的过扫描区域,所以WindowManagerPolicy是对屏幕窗口属性信息控制的一个类,前面我们提到过屏幕其实也有好几种,一般默认的就是比如手机的主屏,其他的比如现在折叠手机还有外屏,或者外接的虚拟屏等,对于这些不同的屏幕处理也会是不同的,这里不同的屏幕也会通过WindowManagerPolicy或者不同的方法来处理,现在这里我们主要关注主屏的处理,上面的方法中会调用setInitialDisplaySize和setDisplayOverscan这2个方法,都是针对主屏的,前一个方法是初始化屏幕的大小,后一个是设置过扫描区域。后面的设置过扫描这个方法比较容易,我们先看下这个方法:
```java
public void setDisplayOverscan(Display display, int left, int top, int right, int bottom) {
// TODO(multi-display): Define policy for secondary displays.
// 如果是默认屏幕,设置扫描的四边位置
if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
mOverscanLeft = left;
mOverscanTop = top;
mOverscanRight = right;
mOverscanBottom = bottom;
}
}
```
这个方法很简单,判断下是否是默认的主屏,然后把过扫描的区域设置进去。这个就不多说了,我们回到上面看下初始化屏幕大小的方法:
```java
public void setInitialDisplaySize(Display display, int width, int height, int density) {
// This method might be called before the policy has been fully initialized
// or for other displays we don't care about.
// TODO(multi-display): Define policy for secondary displays.
if (mContext == null || display.getDisplayId() != Display.DEFAULT_DISPLAY) {
return;
}
mDisplay = display;
final Resources res = mContext.getResources();
// 长短边,表示宽和高
int shortSize, longSize;
// 宽大于高,说明是横屏
if (width > height) {
// 走这里是横屏
shortSize = height;
longSize = width;
// 横屏旋转角度0度
mLandscapeRotation = Surface.ROTATION_0;
// 对面一边旋转角度180度
mSeascapeRotation = Surface.ROTATION_180;
// 这里表示顺时针还是逆时针,不同方向竖屏的旋转角度是不同的
// 下面是设置竖屏的方向
if (res.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)) {
// 竖屏旋转角度90度
mPortraitRotation = Surface.ROTATION_90;
// 竖屏对面边旋转180度
mUpsideDownRotation = Surface.ROTATION_270;
} else {
mPortraitRotation = Surface.ROTATION_270;
mUpsideDownRotation = Surface.ROTATION_90;
}
} else {
// 到这里是竖屏
shortSize = width;
longSize = height;
// 竖屏默认0度
mPortraitRotation = Surface.ROTATION_0;
// 对面一边旋转角度190度
mUpsideDownRotation = Surface.ROTATION_180;
// 根据顺时针和逆时针设置横屏的旋转方向,同样包括正常边和对面边
if (res.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)) {
mLandscapeRotation = Surface.ROTATION_270;
mSeascapeRotation = Surface.ROTATION_90;
} else {
mLandscapeRotation = Surface.ROTATION_90;
mSeascapeRotation = Surface.ROTATION_270;
}
}
```
这个初始方法还是比较好理解的,我们分两部分来看,上面是前半部分。首先是判断如果不是默认的主屏就会返回。接着会根据传入的参数宽和高,判断是横屏和竖屏,这里的判断很简单,横边大于竖边自然就是横屏,反之就是竖屏。横竖屏确立了,那么接下去就得到了长的一边和短的一边的值,以及四条边各自的旋转角度。前半段代码主要就是计算横竖屏相关的属性,下面我们接着看后半段。
```java
.........
// 长短边转为dp
int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / density;
int longSizeDp = longSize * DisplayMetrics.DENSITY_DEFAULT / density;
// Allow the navigation bar to move on non-square small devices (phones).
// 如果小的设备,导航栏可以显示在不同的地方
mNavigationBarCanMove = width != height && shortSizeDp < 600;
// 是否显示导航栏
mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
// Allow a system property to override this. Used by the emulator.
// See also hasNavigationBar().
// 下面是设置模拟器的是否显示导航栏
String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
if ("1".equals(navBarOverride)) {
mHasNavigationBar = false;
} else if ("0".equals(navBarOverride)) {
mHasNavigationBar = true;
}
// For demo purposes, allow the rotation of the HDMI display to be controlled.
// By default, HDMI locks rotation to landscape.
// 设置HDMI屏幕的旋转角度
if ("portrait".equals(SystemProperties.get("persist.demo.hdmirotation"))) {
mDemoHdmiRotation = mPortraitRotation;
} else {
mDemoHdmiRotation = mLandscapeRotation;
}
mDemoHdmiRotationLock = SystemProperties.getBoolean("persist.demo.hdmirotationlock", false);
// For demo purposes, allow the rotation of the remote display to be controlled.
// By default, remote display locks rotation to landscape.
// 设置远程屏幕的旋转角度
if ("portrait".equals(SystemProperties.get("persist.demo.remoterotation"))) {
mDemoRotation = mPortraitRotation;
} else {
mDemoRotation = mLandscapeRotation;
}
mDemoRotationLock = SystemProperties.getBoolean(
"persist.demo.rotationlock", false);
// Only force the default orientation if the screen is xlarge, at least 960dp x 720dp, per
// http://developer.android.com/guide/practices/screens_support.html#range
// 在屏幕是 960dp x 720dp以上的情况,才强制用默认的
mForceDefaultOrientation = longSizeDp >= 960 && shortSizeDp >= 720 &&
// 强制用默认的旋转
res.getBoolean(com.android.internal.R.bool.config_forceDefaultOrientation) &&
// For debug purposes the next line turns this feature off with:
// $ adb shell setprop config.override_forced_orient true
// $ adb shell wm size reset
// 强制旋转没有打开
!"true".equals(SystemProperties.get("config.override_forced_orient"));
}
```
后半段代码先是根据屏幕的像素密度,计算出前面长短边的dp值,然后会初始化是否要显示导航栏以及在模拟器上的显示,还会根据是外接HDMI的屏幕还是通过wifi链接远程的虚拟屏幕等的旋转角度,最后在某些分辨率的屏幕上,比如这里代码中的960X720dp分辨率以上的屏幕,可能需要强制默认的旋转角度等。这些都是初始化屏幕的一些属性,上面的代码中也做了简单的注释,这些就是对一个屏幕尺寸的初始化过程。
前面说了这么多,基本上WindowManagerService的启动就说到这里,从启动中,我们重点介绍了在WMS的初始化过程中创建了各个屏幕,最后会封装为一个DisplayContent对象保存在RootWindowContainer中,之后我们继续分析窗口创建的时候会看到,一个个的窗口其实都会在这里DisplayContent中保存他们相关的句柄,所以从上面这一窜代码走下来,我们大概能看到整个的结构是RootWindowContainer中会保存着所有的DisplayContent,而DisplayContent中有着各种窗口,这里的各种窗口其实没有说的这么简单,他们的层级关系也是比较复杂的,在说具体WMS的流程前,下面我们就先来看一下和WMS相关的一些数据结构类,通过这些数据结构,可以从大的方面来理解这个WMS中各个元素的关系,可能如果之前不了解这些的会感到有点抽象,不过没关系,后面我们分析具体WMS流程的时候会一直看到这些数据结构的,在大概有一定理解程度的基础上去看WMS的流程,就会觉得容易一些。
# WindowContainer
关于WindowContainer,上面我们已经遇见过了,RootWindowContainer和DisplayContent都是一个WindowContainer,从字面上我们就可以理解这个就是一个窗口的容器,我们看下他的源码:
```java
class WindowContainer<E extends WindowContainer> implements Comparable<WindowContainer> {
private WindowContainer mParent = null;
protected final WindowList<E> mChildren = new WindowList<E>();
@CallSuper
protected void addChild(E child, Comparator<E> comparator) {
if (child.getParent() != null) {
throw new IllegalArgumentException("addChild: container=" + child.getName()
+ " is already a child of container=" + child.getParent().getName()
+ " can't add to container=" + getName());
}
// 寻找一个z序合适的位置插入
int positionToAdd = -1;
if (comparator != null) {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
if (comparator.compare(child, mChildren.get(i)) < 0) {
positionToAdd = i;
break;
}
}
}
if (positionToAdd == -1) {
mChildren.add(child);
} else {
mChildren.add(positionToAdd, child);
}
// Set the parent after we've actually added a child in case a subclass depends on this.
child.setParent(this);
}
}
```
上面这段代码是WindowContainer中的部分代码,可以看到WindowContainer是一个泛型类,这个泛型也是一个WindowContainer,所以WindowContainer就是一个递归的容器,每个元素里面又是一个容器,而一旦这个泛型确定了,里面元素的类型也就确定了。
接着我们看到里面有parent和mChildren这2个变量,一个是他的父容器,一个是他的子容器,他们的含义自然不必多说。接着看下他的addChild方法,当往这个容器里面添加子元素的时候就会调用这个方法,这个方法主要就是往mChildren中添加一个子元素,这里比较特殊的是这个方法的第二个参数是一个Comparator,在添加子元素的时候,会更加这个比较器把元素添加到合适的位置,具体的使用我们在后面分析WMS流程的时候可以看到,简单说就是有些场景添加一个窗口的时候,这个窗口的位置不一定肯定在头或尾,而是需要计算出来的,所以这里的比较器就可以有用处的。
说完了WindowContainer,我们来看看他的实现类,首先就是我们前面文章中提到的RootWindowContainer类:
```java
class RootWindowContainer extends WindowContainer<DisplayContent> {
...........
}
```
我们不仔细分析RootWindowContainer这个类,就看下他的声明。经过前面对WindowContainer的分析,这里我们看到他的泛型是DisplayContent,这个也印证了前面说的他的子元素都是DisplayContent的说明。既然看了RootWindowContainer,那么再来看一下DisplayContent:
```java
class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer> {
// 保存Activity的容器
private final TaskStackContainers mTaskStackContainers = new TaskStackContainers();
// 非app的windowContainer,并且在app的windowContainer上面的windowContainer,比如状态栏
private final NonAppWindowContainers mAboveAppWindowsContainers =
new NonAppWindowContainers("mAboveAppWindowsContainers");
// 非app的windowContainer,并且在app的windowContainer下面的windowContainer,比如壁纸
private final NonAppWindowContainers mBelowAppWindowsContainers =
new NonAppWindowContainers("mBelowAppWindowsContainers");
// 输入法窗口的window container
private final NonAppWindowContainers mImeWindowsContainers =
new NonAppWindowContainers("mImeWindowsContainers");
private final HashMap<IBinder, WindowToken> mTokenMap = new HashMap();
@Override
protected void addChild(DisplayChildWindowContainer child,
Comparator<DisplayChildWindowContainer> comparator) {
throw new UnsupportedOperationException("See DisplayChildWindowContainer");
}
}
```
以上这段代码也是DisplayContent这个类的部分代码,我们看到泛型是DisplayContent.DisplayChildWindowContainer,这个泛型类我们等下看,因为对于DisplayContent来说,是不会有子元素的,想想DisplayContet是描述和一个屏幕有关的,屏幕是不会有子屏幕的,所以这里的泛型对于DisplayContent没什么作用,这个泛型是给其他的WindowContainer用的,另外这里的addChild方法我们看到会直接抛出异常,也就是不会添加子元素,这样也就杜绝的万一有的微操作。
另外,这里我们可以看到下面有四个变量。TaskStackContainers是保存和Activity相关的,如果对于AMS比较了解的话,他类似于AMS中的ActivityStack。mTaskStackContainers这个变量里面保存的一般是显示在Activity之上的窗口,比如像状态栏这种。而mBelowAppWindowsContainers是保存的显示在Activity之下的窗口,比如像壁纸。mImeWindowsContainers是保存和输入法相关的窗口。这四个变化简单说就是保存不同窗口类型的,除了TaskStackContainers外,其余按个变量都是NonAppWindowContainers类型的,我们先看下这个类型代码:
```java
private final class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
..............
}
```
我们不详细看具体实现,仅看声明部分就可以知道这个类的子元素都是WindowToken类型的,这个类型下面也会介绍,现在只要知道他是代表一个具体窗口的WindowContainer就可以了,也就是说他们就是一个我们看得到的窗口了。所以说除了TaskStackContainers外,其余三个变量里面保存的就是一个实实在在的窗口,了解了这个,我们在看下TaskStackContainers这个类:
```java
private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
..........
}
```
上面我们说了,这个类类似于AMS中的ActivityStack,如果不熟悉的同学还是建议先去看下AMS,虽然说不同模块是相对独立的,但是有些概念是相同的,一旦理解了后对理解其他模块是有益处的,所以还是建议需要理解一下AMS中的数据结构。对AMS中了解ActivityStack的同学来说,都知道他是保存TaskRecord的,即一个任务栈,而TaskRecord中是保存Activity的,所以在AMS中,有ActivityStack -> TaskRecord -> Activity这种层级关系。在WMS中其实也是一样的,在后面创建Activty的流程中可以看到在这种层级关系的建立过程中,AMS和WMS是对应了,一旦在AMS中创建了这种关系,同时也会在WMS中创建这种关系,这也是对于两个模块在这个相同逻辑额处理上尽量保持一致减少出错。
在上面TaskStackContainers中我们看到他们泛型是TaskStack,我们上面说TaskStackContainers对应于AMS中的ActivityStack,那么这里的子元素TaskStack按照上面的推理应该对应于AMS的TaskRecord,我们看下TaskStack这个类:
```java
public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLayerUser,
BoundsAnimationTarget {
.............
}
```
这里我们看到他的子元素类型是Task,这个Task就是对应于AMS中的TaskRecord,在WMS中他是Task这个类,我们在进一步跟进Task类,看看他的子元素是什么:
```java
class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerUser {
...........
}
```
从上面代码可以看到他的子元素是AppWindowToken,他在WMS中就代表了一个Activity,出了Activity外,其余的窗口都会用WindowToken来表示,对于Activity由于是我们用到最多的一个窗口,也比较复杂,所以单独用AppWindowToken这个类来表示,但是他的父类也是WindowToken。
# WindowToken
说到这里,自然而然的我们就感觉WindowToken是不是就是代表了一个窗口了?的确在前面提到的DisplayContent中,一个窗口就有一个WindowToken(在WMS中用WindowState表示),在DisplayContent中有个mTokenMap变量,这个变量中保存的就是在这个屏幕中的所有窗口,即所有的WindowToken,所以上层对窗口的管理,其实首先就是需要获得这个窗口的WindowToken,我们看下他的构造函数:
```java
// 当新window产生的时候,创建这个窗口的WindowToken
// 参数一 WMS
// 参数二 _token 是DisplayContent的mTokenMap的key,比如ActivityRecord的token,toast在notifacationService中创建的Binder,或者ViewRootImpl的W
// 参数三 type 窗口类型
// 参数四 persistOnEmpty 为true表示非WMS中new的,false表示是WMS中创建的
// 参数五 dc 屏幕
WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
DisplayContent dc, boolean ownerCanManageAppTokens) {
mService = service;
token = _token;
windowType = type;
mPersistOnEmpty = persistOnEmpty;
mOwnerCanManageAppTokens = ownerCanManageAppTokens;
// 通知屏幕DisplayContent有新的窗口加入了
onDisplayChanged(dc);
}
```
这里构造方法的参数,上面已经做了注释,其中这里参数token在后面讲解窗口流程的时候会在说,这里只要理解为表示一个WindowToken的,同样你如果熟悉AMS的话,在ActivityRecord中就有Token,这里的Token是类似的含义,甚至如果是Activity的话,就是AMS中ActivityRecord的token。另一个参数type表示的是窗口的类型,在android的窗口中,总共分为三种窗口类型,应用窗口,即一个Activity。子窗口,比如PopupWindow。系统窗口,比如Toast就是系统窗口。我们大概看一眼窗口类型的定义,在WindowManager这个类中,这个类在后面我们也会介绍,由于窗口类型也是非常的多,这里我们也就截取部分看下,了解一下即可:
```java
// app窗口的初始值是1
public static final int FIRST_APPLICATION_WINDOW = 1;
// 一个app中的base窗口值是1,其他的在他上面的窗口值会比他大
public static final int TYPE_BASE_APPLICATION = 1;
// 普通app界面窗口类型
public static final int TYPE_APPLICATION = 2;
// 在一个app启动窗口
public static final int TYPE_APPLICATION_STARTING = 3;
// 在一个app显示前等待被画出来的画面
public static final int TYPE_DRAWN_APPLICATION = 4;
// app窗口类型的最大值是99
public static final int LAST_APPLICATION_WINDOW = 99;
// 子窗口类型初始值是1000
public static final int FIRST_SUB_WINDOW = 1000;
// 出现在父窗口的最顶层
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
// 出现在父窗口的最顶层,不是父窗口的子窗口
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
// 子窗口类型的最大值是1999
public static final int LAST_SUB_WINDOW = 1999;
// 系统窗口的初始值是2000
public static final int FIRST_SYSTEM_WINDOW = 2000;
// 状态栏窗口,这个界面可以设置为锁屏界面
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
// 搜索条窗口
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
// 锁屏窗口
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
// Toast窗口,已经废弃,现在使用TYPE_APPLICATION_OVERLAY代替
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
// 输入法类型窗口
public static final int TYPE_INPUT_METHOD = FIRST_SYSTEM_WINDOW+11;
...................
// 系统窗口类型值最大2999
public static final int LAST_SYSTEM_WINDOW = 2999;
```
这里截取了一些窗口类型,我们看到应用类型窗口是从1到99,子窗口类型是从1000到1999,系统窗口类型是从2000到2999,这里也不用解释什么,我们就了解下不同类型窗口的值范围就可以了,具体在创建窗口的时候可以注意下类型这个参数就可以,我们回到上面WindowToken的构造方法,除了保存传入的参数外,最后会调用onDisplayChanged方法,我们跟进这个方法:
```java
// 把token加入到mTokenMap变量中,通知屏幕window有变动
void onDisplayChanged(DisplayContent dc) {
// 把这个token加入到屏幕中的mTokenMap变量
dc.reParentWindowToken(this);
mDisplayContent = dc; // 把屏幕赋值给这个token
SurfaceControl.openTransaction();
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowState win = mChildren.get(i);
win.mWinAnimator.updateLayerStackInTransaction();
}
SurfaceControl.closeTransaction();
// 这个token如果有子windowContainer的话,每个windowContainer应该都有token?
// 如果这个token所在屏幕发生变化,那些子token也会发生变化,所以会也要执行onDisplayChanged方法
super.onDisplayChanged(dc);
}
```
这个方法的参数是一个DisplayContent,表示这个窗口被添加到哪个屏幕。这个方法首先会调用DisplayContent的reParentWindowToken方法,把当前这个windowToken添加到DisplayContent中,之后把DisplayContent保存到这个WindowToken中。之后会设置surfaceflinger中的层,这块我们先不管,之后会专门分析Surfaceflinger模块,最后机会如果这个窗口有子元素同样把子元素也添加到这个DisplayContent中,逻辑和这里是一样的,所以我们这里主要看下DisplayContent的reParentWindowToken方法:
```java
// 先看这个token是否已经在一个屏幕中了,如果在了先做一下移除等的处理
// 最后把这个token要加入屏幕中的mTokenMap变量
void reParentWindowToken(WindowToken token) {
// 看看这个token有没有DisplayContent
final DisplayContent prevDc = token.getDisplayContent();
if (prevDc == this) { // 如果token已经有当前这个DisplayContent了,说明绑定了,return
return;
}
// 如果这个token在另一个屏幕中,那么从哪个屏幕的mTokenMap中把这个token移除
// 并且这个token不是一个Activity
if (prevDc != null && prevDc.mTokenMap.remove(token.token) != null
&& token.asAppWindowToken() == null) {
// Removed the token from the map, but made sure it's not an app token before removing
// from parent.
// 从这个token的父windowContainer中移除他,并且设置他的parent为null
token.getParent().removeChild(token);
}
// 添加这个token到这个DisplayContent中(屏幕中)的mTokenMap变量
addWindowToken(token.token, token);
}
```
这个方法理解也不难。首先看看参数中的WindowToken是否已经有了一个DisplayContent,如果已经有了,并且就是当前这个,那么就什么都不用做了,返回。如果有DisplayContent但是不是当前要添加的这个,那么从之前那个DisplayContent的mTokenMap中移除这个窗口,同时从他的父元素中移除,最后添加到这个屏幕中。这里我们看一下把WindowToken添加到DisplayContent中的方法addWindowToken:
```java
private void addWindowToken(IBinder binder, WindowToken token) {
// 从rootWindowContainer中获取这个token对应的屏幕
final DisplayContent dc = mService.mRoot.getWindowTokenDisplay(token);
// 如果这个token已经在一个屏幕中了,那么报错
if (dc != null) {
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);// 把这个token加入这个屏幕中
if (token.asAppWindowToken() == null) { // 这个token不是Activity类型的,分别加入一下类型windContainer中
// Add non-app token to container hierarchy on the display. App tokens are added through
// the parent container managing them (e.g. Tasks).
switch (token.windowType) {
case TYPE_WALLPAPER: // 壁纸类型的token
mBelowAppWindowsContainers.addChild(token);
break;
case TYPE_INPUT_METHOD: // 输入法窗口
case TYPE_INPUT_METHOD_DIALOG: // 输入法对话框
mImeWindowsContainers.addChild(token);
break;
default:
mAboveAppWindowsContainers.addChild(token);
break;
}
}
}
```
这里首先会调用RootWindowContainer的getWindowTokenDisplay方法,这个方法会遍历所有DisplayContent,从每个DisplayContent中查找有没有这个WindowToken,如果有的话不支持在添加,会抛出异常,否则就会添加到这个DisplayContent的mTokenMap,mTokenMap是一个key位IBinder,value为WindowToken的map,最后会判断这个WindowToken是不是AppWindowToken,也就是不是Activity,如果不是Activity的话,前面我们分析DisplayContent的时候说过有四个不同类型的WindowContainer,其中TaskStackContainers是保存TaskStack的,这个都是和Activity相关的,这里不会处理,我们也会在后面分析创建Activity的时候再说,其余类型的窗口就会放入到对应的WindowContainer中,这里看到有壁纸,输入法等等等窗口,都会加入到对应的WindowContainer中。至此WIndowToken的创建就完成了,通过上面的分析我们可以看到添加一个窗口的WindowToken,除了会添加到对应DisplayContent外,还会添加到对应的父容器中,比如Activity就是Task中(对应AMS的TaskRecord),其他类型窗口会添加上上面说的三个WindowContainer中,可见其他类型的窗口相对来说还是比较独立的,所以没有绝对的依赖的一个层次关系,所以会单独放到一个WindowContainer中。看过了WindowToken类,我们在看下AppWindowToken,这个是代表一个Activity。
# AppWindowToken
```java
AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
int configChanges, boolean launchTaskBehind, boolean alwaysFocusable,
AppWindowContainerController controller, Configuration overrideConfig, Rect bounds) {
this(service, token, voiceInteraction, dc, fullscreen, overrideConfig, bounds);
setController(controller); // 设置AppWindowContainerController
mInputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
mShowForAllUsers = showForAllUsers;
mTargetSdk = targetSdk;
mOrientation = orientation;
layoutConfigChanges = (configChanges & (CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION)) != 0;
mLaunchTaskBehind = launchTaskBehind;
mAlwaysFocusable = alwaysFocusable;
mRotationAnimationHint = rotationAnimationHint;
// Application tokens start out hidden.
hidden = true;
hiddenRequested = true;
}
```
关于创建AppWindowToken我们会在后面分析Activity流程的时候遇到,这里先看下他的构造方法,这里主要还是保存传过来的一些参数,其中这里有个AppWindowContainerController参数,这个对象就是实际操作窗口的,比如这里的创建AppWindowToken其实也是这个类来创建后才会走到这里的,后面我们会看到这个过程,其他的比如控制窗口显示或者移动到其他任务栈中等等,这里不做过多细节描述,后面分析流程时候再看。
这里第一个方法this会继续调用同名的覆写方法:
```java
AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
DisplayContent dc, boolean fillsParent, Configuration overrideConfig, Rect bounds) {
super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true, dc,
false /* ownerCanManageAppTokens */);
appToken = token; // 表示这个窗口属于哪个应用,是ActivityRecord的token
mVoiceInteraction = voiceInteraction;
mFillsParent = fillsParent;
mInputApplicationHandle = new InputApplicationHandle(this);
// 创建窗口动画的类
mAppAnimator = new AppWindowAnimator(this, service);
// 回调配置变化方法?
if (overrideConfig != null) {
onOverrideConfigurationChanged(overrideConfig);
}
// 窗口大小
if (bounds != null) {
mBounds.set(bounds);
}
}
```
这里会调用超类的构造方法,也就是WindowToken的构造方法,这个在之前已经分析过了,会把当前这个WindowToken添加到DisplayContent中。当然有同学会问,这个AppWindowToken是怎么添加到任务栈中的,这个暂时还是卖个关子,分析Activity窗口流程的时候再看。
# WindowContainerController
上面说到了WindowController类,那么我们也就看下这个类,这个类是处理windowToken的,我们看下他的源码:
```java
// 这里泛型,第一个是表示控制哪个Container,比如一个Activity就是AppWindowToken
// 第二个泛型是回调函数
class WindowContainerController<E extends WindowContainer, I extends WindowContainerListener> {
final WindowManagerService mService;
// 即包含所有DisplayContent的Container,即所有屏幕
final RootWindowContainer mRoot;
// 即包含所有key为WindowToken,value为WindowState的Map
final WindowHashMap mWindowMap;
// The window container this controller owns.
E mContainer; // 具体控制哪个WindowContainer
// Interface for communicating changes back to the owner.
final I mListener; // 回调接口
WindowContainerController(I listener, WindowManagerService service) {
mListener = listener;
mService = service;
// 即包含所有DisplayContent的Container,即所有屏幕
mRoot = mService != null ? mService.mRoot : null;
// 即包含所有key为WindowToken,value为WindowState的Map
mWindowMap = mService != null ? mService.mWindowMap : null;
}
// 把一个受控制的WindowContainer设置给当前控制器
void setContainer(E container) {
if (mContainer != null && container != null) {
throw new IllegalArgumentException("Can't set container=" + container
+ " for controller=" + this + " Already set to=" + mContainer);
}
mContainer = container;
}
void removeContainer() {
// TODO: See if most uses cases should support removeIfPossible here.
//mContainer.removeIfPossible();
if (mContainer != null) {
mContainer.setController(null);
mContainer = null;
}
}
boolean checkCallingPermission(String permission, String func) {
return mService.checkCallingPermission(permission, func);
}
}
```
这个类不是很长,我们主要看下他的构造方法,构造方法里面可以看到会把RootWindowContainer传过来,这样他就可以找到所有的DisplayContent,另外还会传入一个WMS中的WindowHashMap对象,这个也是一个Map保存的是WindowState对象,这个对象也是非常重要的,从名字就可以看出他描述了一个窗口当前的状态,比如计算窗口大小,调整窗口的层级等等,还是比较复杂的,这个我们在后文分析到的时候通过流程的实例来理解。
回到上面WindowContainerController中,每个WindowContainerController都对应一个WindowContainer,mContainer就是他所控制的WindowContainer,通过setContainer方法会设置mContainer。WindowContainerController是一个父类,实际使用过程中具体的WindowContainer会继承他实现自己的逻辑,我们前面介绍的几个和Activity相关的WindowContainer都有对应的WindowContainerController,我们都看一下。
# AppWindowContainerController
首先看一下AppWindowContainerController这个类:
```java
public AppWindowContainerController(TaskWindowContainerController taskController,
IApplicationToken token, AppWindowContainerListener listener, int index,
int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
WindowManagerService service, Configuration overrideConfig, Rect bounds) {
super(listener, service);
mHandler = new H(service.mH.getLooper()); // WMS所在进程的Looper,即这个Handler会调用systemServer进程的Looper
mToken = token; // ActivityRecord中的IApplicationToken
synchronized(mWindowMap) {
// 从所有屏幕中找出有没有这个AppWindowToken
AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder());
// 如果已经有了,退出
if (atoken != null) {
// TODO: Should this throw an exception instead?
Slog.w(TAG_WM, "Attempted to add existing app token: " + mToken);
return;
}
// 获取这个Activity所在的task,即从taskController中取出task
final Task task = taskController.mContainer;
// 如果没有task,退出
if (task == null) {
throw new IllegalArgumentException("AppWindowContainerController: invalid "
+ " controller=" + taskController);
}
// 创建AppWindowToken
atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
alwaysFocusable, this, overrideConfig, bounds);
if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
+ " controller=" + taskController + " at " + index);
// 把AppWindowToken插入到task中
task.addChild(atoken, index);
}
}
```
这里我们可以看到,首先从参数中会获得一个token,这个token其实就是ActivtyRecord的IApplicationToken,这是一个IBinder,后面我们会看到这个的来源。接着,由于前面看WindowContainerController的时候我们知道了他持有一个RootWindowContainer,这里有所有的DisplayContainer,而DisplayContainer里面又有所有的WindowToken,所以这里会通过这个Activity的token去查找是否已经有这个窗口了,如果找到就退出,说明已经有了不要添加了。否则通过参数传入的TaskWindowContainerController获取他控制的Task,Task前面我们也说过了,类似于AMS中的TaskRecord,在WMS中就是Task,他也是一个WindowContainer,所以他也有Controller,就是参数传入的TaskWindowContainerController,这个类我们下面会看一下。从TaskWindowContainerController中获得控制的task,然后调用createAppWindow创建AppWindowToken,这个构造方法前面也看过了,创建成功后,会添加到task中,作为子元素。
# TaskWindowContainerController
上面说到了TaskWindowContainerController,下面就看下他的构造方法:
```java
public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
StackWindowController stackController, int userId, Rect bounds,
Configuration overrideConfig, int resizeMode, boolean supportsPictureInPicture,
boolean homeTask, boolean toTop, boolean showForAllUsers,
TaskDescription taskDescription, WindowManagerService service) {
super(listener, service);
mTaskId = taskId; // Task的id
// WMS进程的Looper,即SystemServer
mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
synchronized(mWindowMap) {
if (DEBUG_STACK) Slog.i(TAG_WM, "TaskWindowContainerController: taskId=" + taskId
+ " stack=" + stackController + " bounds=" + bounds);
// 获取这个task所在的taskStack,即从stackController中获取taskStack
final TaskStack stack = stackController.mContainer;
// 如果taskStack是null,报错
if (stack == null) {
throw new IllegalArgumentException("TaskWindowContainerController: invalid stack="
+ stackController);
}
EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId);
// 创建task
final Task task = createTask(taskId, stack, userId, bounds, overrideConfig, resizeMode,
supportsPictureInPicture, homeTask, taskDescription);
final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
// 把task添加到taskStack中
stack.addTask(task, position, showForAllUsers, true /* moveParents */);
}
}
```
看过上面AppWindowContainerController后,再看这个TaskWindowContainerController是不是觉得结构很相似。这里参数传入的taskId就是AMS中taskRecord的id,另一个参数StackWindowController就是Stack的控制器,他是TaskStack的控制器,TaskStack前面也说过了,对应于AMS中的ActivityStack,在WMS中就是TaskStack,然后下面会创建一个Task加入到TaskStack中,这里的套路是一样的,也就不多说了。
# StackWindowController
接着在看下StackWindowController这个控制器:
```java
public StackWindowController(int stackId, StackWindowListener listener,
int displayId, boolean onTop, Rect outBounds, WindowManagerService service) {
super(listener, service);
mStackId = stackId; // taskStack的id
// 创建WMS的Looper的Handler
mHandler = new H(new WeakReference<>(this), service.mH.getLooper());
synchronized (mWindowMap) {
// 获取屏幕
final DisplayContent dc = mRoot.getDisplayContent(displayId);
if (dc == null) {
throw new IllegalArgumentException("Trying to add stackId=" + stackId
+ " to unknown displayId=" + displayId);
}
// 增加一个taskStack到displayContent(屏幕)中,实际是添加到
// DisplayContent的TaskStackContent中
final TaskStack stack = dc.addStackToDisplay(stackId, onTop);
stack.setController(this); // 设置taskStack的控制器是自己
getRawBounds(outBounds); // 设置taskStack尺寸
}
}
```
这里的逻辑也是类似的。首先从参数中获取stackId,也就是ActivityStack的id,接着根据参数中的displayId找到对应的DisplayContent,之后会调用DisplayContent的addStackToDisplay方法创建一个WMS中的TaskStack,这个TaskStack会添加到DisplayContent的mTaskStackContainers变量中,这个变量也就是之前介绍DisplayContent时候说的四个WindowContainer中的一个,他的子元素就是TaskStack,我们看下DisplayContent的addStackToDisplay方法:
```java
TaskStack addStackToDisplay(int stackId, boolean onTop) {
if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
+ mDisplayId);
// 看看现在有没有这个taskStack
TaskStack stack = getStackById(stackId);
if (stack != null) { // 如果已经有这个类型的stack(可以理解为AMS中的ActivityStack)
// It's already attached to the display...clear mDeferRemoval and move stack to
// appropriate z-order on display as needed.
stack.mDeferRemoval = false;
// We're not moving the display to front when we're adding stacks, only when
// requested to change the position of stack explicitly.
// 调整这个taskStack到一个合适的位置
mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack,
false /* includingParents */);
} else {
// 如果现在没有taskStack,创建一个
stack = new TaskStack(mService, stackId);
// 把这个新的stack加入到TaskStackContainers中
mTaskStackContainers.addStackToDisplay(stack, onTop);
}
if (stackId == DOCKED_STACK_ID) { // 分屏
//如果是分屏taskStack的话,注意是创建和移除的时候,会处理下分屏之间分割线和输入法窗口的问题
mDividerControllerLocked.notifyDockedStackExistsChanged(true);
}
return stack;
}
```
这里首先会调用getStackById方法,根据stackId来遍历这个DisplayContent中是否已经有这个TaskStack了,如果有的话会根据情况调整这个TaskStack在DisplayContent中的位置,这个调整方法是TaskStackContainers的positionChildAt,我们稍后看,先把这段代码看完。
如果没有的话,就会创建一个TaskStack,然后调用TaskStackContainers的addStackToDisplay方法加入到TaskStackContainers中,这个方法下面也会跟进看一下。最后如果添加的这个TaskStack是分屏的话,会处理下分屏直接分割线以及输入法窗口的问题,注意是在分屏TaskStack添加和移除的时候,分割线和输入法窗口的位置肯定会有变化,所以需要处理一下他们的位置获取显示等问题,这个我们了解下就可以,不进行细说。
下面我们主要分别看下上面这个方法中使用到的调整TaskStack方法positionChildAt和添加TaskStack方法addStackToDisplay。先看前一个方法:
```java
void positionChildAt(int position, TaskStack child, boolean includingParents) {
// 如果要求添加或者调整的taskStack总是显示在最上面的(画中画),但是当前要求的位置不是在最上面,所以有了矛盾
if (StackId.isAlwaysOnTop(child.mStackId) && position != POSITION_TOP) {
// 获取这个taskStack当前的位置
final int currentPosition = mChildren.indexOf(child);
// Moving to its current position, as we must call super but we don't want to
// perform any meaningful action.
super.positionChildAt(currentPosition, child, false /* includingParents */);
return;
}
// 根据是否要显示在最上面,返回最终这个stack应该所在的位置
final int targetPosition = findPositionForStack(position, child, false /* adding */);
// 最终把child这个taskStack调整到targetPosition这个位置
super.positionChildAt(targetPosition, child, includingParents);
setLayoutNeeded();
}
```
这里首先会判断这个要调整额TaskStack是否应该总是显示在最上面,判断的方法是StackId的isAlwaysOnTop,我们简单看一眼:
```java
public static boolean isAlwaysOnTop(int stackId) {
return stackId == PINNED_STACK_ID;
}
```
其实就是判断这个TaskStack的类型,这里可以看到如果是PINNED_STACK_ID类型的,就是画中画,我们平时会看到界面上有些特殊功能的窗口会一直浮动在最前端,总是会显示给用户的窗口,就是画中画窗口。回到上面的方法,如果是一个总是显示在最前端的TaskStack,但是现在调整的位置又不是在最顶端,那么还是保持在当前的位置,由于如果是一个已经存在的TaskStack一般调整的位置参数要么是最顶层,要么是最底层,所以这里position的值是POSITION_TOP或者POSITION_BOTTOM,他们分别是Int的MAX_VALUE和MIN_VALUE,所以需要获取当前这个TaskStack的实际在集合中下标才能知道确切当前的位置然后再调用超类的positionChildAt调整到当前为止,不过虽然执行了超类的positionChildAt方法,还是调整到当前位置,所以根据注释中的说明是没什么实际意义的,这个我们也不管,也没什么错误。
如果要求最终的位置和实际这个TaskStack没什么矛盾产生的话,那么会调用findPositionForStack方法返回一个最终的目标位置,最后还是调用超类的positionChildAt方法来把TaskStack插入到DisplayContent中。我们先看看返回最终目标位置的findPositionForStack方法:
```java
private int findPositionForStack(int requestedPosition, TaskStack stack, boolean adding) {
// 当前容器最后一个下标
final int topChildPosition = mChildren.size() - 1;
// 是否插入到最上面
boolean toTop = requestedPosition == POSITION_TOP;
// 是否插入顶部
// 如果这个插入的是新添加了,那么总数是多一个,那么插入的下标就是topChildPosition + 1
toTop |= adding ? requestedPosition >= topChildPosition + 1
: requestedPosition >= topChildPosition;
// 插入目标位置
int targetPosition = requestedPosition;
// 如果要求插入的是最上面一个taskStack,但是要求插入的这个stack不是画中画,而画中画stack是存在的,那么下面可能会调整插入的位置
if (toTop && stack.mStackId != PINNED_STACK_ID
&& getStackById(PINNED_STACK_ID) != null) {
// The pinned stack is always the top most stack (always-on-top) when it is present.
// 画中画总是会在最上面一个stack,获取最上面的taskStack
TaskStack topStack = mChildren.get(topChildPosition);
// 如果最上面不是画中画stack,报错
if (topStack.mStackId != PINNED_STACK_ID) {
throw new IllegalStateException("Pinned stack isn't top stack??? " + mChildren);
}
// 到这里的话,说明最上面是画中画stack,但是要求插入的stackId不是画中画,所以会调整到画中画下面的stack
// 如果是新添加的,位置是topChildPosition,否则topChildPosition-1
targetPosition = adding ? topChildPosition : topChildPosition - 1;
}
return targetPosition;
}
```
虽然前面我们是由于分析调整一个存在TaskStack才走到这里的,但是其实这个方法对应添加一个新TaskStack也是适用的,这里这个方法的最后一个参数adding表示是否是添加一个新的TaskStack,如果是的话回根据要求插入的位置是否是当前最后一个子元素集合中下标+1,这样就可以判断是是不是插入到集合的最后一个了。如果是一个调整的操作,那么也会判断是否调整位置是否是大于最后一个元素,是的话说明也是调整到栈的顶部,下面会根据是否是顶部继续做一些兼容的处理。
前面我们看到如果一个TaskStack是画中画类型的,必须是要保持在顶部的,如果现在这个添加或者调整的TaskStack也是要求在顶部,但是他不是画中画,并且当前DisplayContent中已经有了画中画这个TaskStack,那么当前这个TaskStack的位置就不能是顶部了,需要保持在画中画TaskStack的下面,这个根据是否是新添加的还是调整的目标位置修改为topChildPosition或者topChildPosition-1。到这里最后TaskStack的目标位置就计算出话来了,最后还会调用WindowContainer的positionChildAt方法来进入最终的插入操作,我们看下这个方法:
```java
void positionChildAt(int position, E child, boolean includingParents) {
// 如果这个child已经有parent了,并且不是当前这个,那么报错
if (child.getParent() != this) {
throw new IllegalArgumentException("removeChild: container=" + child.getName()
+ " is not a child of container=" + getName()
+ " current parent=" + child.getParent());
}
// 如果position值不在当前容器元素中,做一些异常处理
if ((position < 0 && position != POSITION_BOTTOM)
|| (position > mChildren.size() && position != POSITION_TOP)) {
throw new IllegalArgumentException("positionAt: invalid position=" + position
+ ", children number=" + mChildren.size());
}
// 如果最终位置大于等于最大的下标,设置为POSITION_TOP。如果等于0,设置为POSITION_BOTTOM
if (position >= mChildren.size() - 1) {
position = POSITION_TOP;
} else if (position == 0) {
position = POSITION_BOTTOM;
}
switch (position) {
// 最终的位置是最后一个
case POSITION_TOP:
// 如果最后一个不是child
if (mChildren.peekLast() != child) {
// 先从容器中把child移除
mChildren.remove(child);
// 再把child添加到最后
mChildren.add(child);
}
// includingParents为true,不是child父类也要移动到top
if (includingParents && getParent() != null) {
getParent().positionChildAt(POSITION_TOP, this /* child */,
true /* includingParents */);
}
break;
case POSITION_BOTTOM:
// 如果最终位置是第一个,那么如果对一个不是child
if (mChildren.peekFirst() != child) {
// 先移除
mChildren.remove(child);
// 在加入第一个
mChildren.addFirst(child);
}
// includingParents为true,不是child父类也要移动到bottom
if (includingParents && getParent() != null) {
getParent().positionChildAt(POSITION_BOTTOM, this /* child */,
true /* includingParents */);
}
break;
default:
// 如果不是第一个或者最后一个,那么如果容器中已经有这个child了,先移除,再插入到position为止
mChildren.remove(child);
mChildren.add(position, child);
}
}
```
这个方法我们分两部分来看,上面是第一部分,这里比较简单,开头做了一些校验的判断,看看这个子元素的父元素是不是这个WindowContainer,然后看看最终的位置position是否在正常范围内。如果一切都正常的话,如果是顶部元素,那么position就设置为POSITION_TOP,如果是第一个元素,position就设置为POSITION_BOTTOM。
有了具体的positon后,后面的代码也比较容易理解了,根据前面最后目标位置,其实就是顶部,底部,或者中间三种情况,分别往集合中的头部,尾部,或者指定postion位置添加元素就可以了,这里也不用详细描述,相信一看也就懂了。
# 添加新TaskStack到TaskStackContainers中
前面这一系列代码我们都是从DisplayContent的addStackToDisplay方法引出来的,是往DisplayContent中添加一个TaskStack,前面我们分析的是对于一个已经存在的TaskStack进行调整,现在我们在看一下对于一个新添加的TaskStack是怎么处理的。前面在DisplayContent方法中,我们已经看过了需要调用TaskStackContainers的addStackToDisplay方法,我们这就看下这个方法:
```java
// 把taskstack插入displayContent,并且更新taskStack中和屏幕相关数据,比如尺寸,旋转,密度等
void addStackToDisplay(TaskStack stack, boolean onTop) {
// 如果是桌面的taskStack,把桌面stack赋值给mHomeStack,方便后面的寻找
if (stack.mStackId == HOME_STACK_ID) {
if (mHomeStack != null) {
throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first.");
}
mHomeStack = stack; // 把home的stack赋值给mHomeStack,方法后面使用
}
// 寻找一个插入位置,添加taskStack到屏幕中
addChild(stack, onTop);
// 更新taskStack中和屏幕相关数据,比如尺寸,旋转,密度等
stack.onDisplayChanged(DisplayContent.this);
}
```
这里首先判断是否是桌面的TaskStack,如果是的话并且已经存在了就抛出异常,正常来说走到这里的话,说明TaskStackContainers是没有这个TaskStack的,但是由于桌面TaskStack比较特殊,所以会保存在mHomeStack这个变量中,方便使用,而且桌面的TaskStack一般来说也不会被移除,所以这里做这个判断也是为了防止不小心桌面TaskStack被移除了的情况。
之后就会调用addChild方法往TaskStackContainers中插入这个TaskStack,最后会调用TaskStack的onDisplayChanged方法,把当前DisplayContent中的屏幕信息更新到这个TaskStack中。我们先看下addChild这个方法:
```java
private void addChild(TaskStack stack, boolean toTop) {
// 寻找一个合适的插入位置,toTop为true表示插入到容器的最后一个,也就是显示在屏幕的最上面
final int addIndex = findPositionForStack(toTop ? mChildren.size() : 0, stack,
true /* adding */);
// 添加taskstack到TaskStackContainers中
addChild(stack, addIndex);
setLayoutNeeded();
}
```
这个方法不长,首先调用findPositionForStack来计算插入的位置,这个方法我们前面已经分析过了,这里也不多说,返回插入到集合的下标值后,调用addChild插入到TaskStackContainers中,最后调用setLayoutNeeded设置下需要更新布局,这里就是会触发View的onLayout的标记,这里我们就知道下就可以,关于View的机制后续会专门在集中分析。这里我们跟进addChild方法看下:
```java
void addChild(E child, int index) {
// 如果child已经有父容器了,报错
if (child.getParent() != null) {
throw new IllegalArgumentException("addChild: container=" + child.getName()
+ " is already a child of container=" + child.getParent().getName()
+ " can't add to container=" + getName());
}
// 添加child到mChildren的index位置上
mChildren.add(index, child);
// Set the parent after we've actually added a child in case a subclass depends on this.
// 设置child的父容器为自己
child.setParent(this);
}
```
这里方法比较简单,我们看一眼就行,把一个TaskStack对象插入到mChildren集合中下标为index的位置,然后把这个插入的TaskStack的父元素设置为本TaskStackContainers,这样一个TaskStack就添加到了DisplayContent的TaskStackContainers中了。
我们在回到上面addStackToDisplay中,最后还有一个根据DisplayContent的数据更新TaskStack的方法,我们在继续跟进看下:
```java
void onDisplayChanged(DisplayContent dc) {
// 如果这个taskStack已经有屏幕了,报错
if (mDisplayContent != null) {
throw new IllegalStateException("onDisplayChanged: Already attached");
}
mDisplayContent = dc; // 赋值屏幕
// 创建背景变灰作用的DimLayer
mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId(),
"animation background stackId=" + mStackId);
Rect bounds = null;
// 获取分屏TaskStack
final TaskStack dockedStack = dc.getDockedStackIgnoringVisibility();
// 分屏模式
if (mStackId == DOCKED_STACK_ID
|| (dockedStack != null && StackId.isResizeableByDockedStack(mStackId)
&& !dockedStack.fillsParent())) {
// The existence of a docked stack affects the size of other static stack created since
// the docked stack occupies a dedicated region on screen, but only if the dock stack is
// not fullscreen. If it's fullscreen, it means that we are in the transition of
// dismissing it, so we must not resize this stack.
bounds = new Rect();
dc.getLogicalDisplayRect(mTmpRect);
mTmpRect2.setEmpty();
if (dockedStack != null) {
dockedStack.getRawBounds(mTmpRect2);
}
final boolean dockedOnTopOrLeft = mService.mDockedStackCreateMode
== DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
getStackDockedModeBounds(mTmpRect, bounds, mStackId, mTmpRect2,
mDisplayContent.mDividerControllerLocked.getContentWidth(),
dockedOnTopOrLeft);
} else if (mStackId == PINNED_STACK_ID) { // 画中画
// Update the bounds based on any changes to the display info
getAnimationOrCurrentBounds(mTmpRect2);
boolean updated = mDisplayContent.mPinnedStackControllerLocked.onTaskStackBoundsChanged(
mTmpRect2, mTmpRect3);
if (updated) {
bounds = new Rect(mTmpRect3);
}
}
// 更新下这个这个taskStack的屏幕尺寸,旋转,密度等数据
updateDisplayInfo(bounds);
// 更新容器子元素task的显示数据
super.onDisplayChanged(dc);
}
```
由于是新创建了一个TaskStack,所以这个方法主要就是根据他添加到的DisplayContent中屏幕的相关信息,来设置这个TaskStack的显示属性,比较大小等等。这里首先把这个DisplayContent赋值给TaskStack保存,然后创建一个控制背景灰显的DimLayer,这个和SurfaceFlinger相关的我们先不管,主要知道这个是用于比如我们弹出一个对话框后,背景会灰显作用的就可以了。
之后如果是分屏或者画中画的话,由于这两种情况下,窗口都是非全屏的,所以会分别计算他们的尺寸,这里getStackDockedModeBounds方法会计算分屏窗口的尺寸,getAnimationOrCurrentBounds会获取画中画窗口的支持,这两个方法这里也不跟进了,否则就分析的太细了,我们主要看最后updateDisplayInfo方法会更新这个TaskStack的大小等信息。
```java
void updateDisplayInfo(Rect bounds) {
// 屏幕不能为null
if (mDisplayContent == null) {
return;
}
// 遍历taskStack中的每个task
for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
// 更新下每个task的显示信息
mChildren.get(taskNdx).updateDisplayInfo(mDisplayContent);
}
// 如果有这个taskStack指定的尺寸的话,设置给taskStack,根据前面调用方法,应该是画中画和分屏的时候才会有值
if (bounds != null) {
setBounds(bounds);
return;
} else if (mFillsParent) {
// 如果是全屏的话,从displayContent中获取尺寸
setBounds(null);
return;
}
// 进入到这里说明,上面的bounds==null,并且不是全屏的,把当前的尺寸mBounds,赋值给mTmpRect2
mTmpRect2.set(mBounds);
// 获取旋转
final int newRotation = mDisplayContent.getDisplayInfo().rotation;
// 获取密度
final int newDensity = mDisplayContent.getDisplayInfo().logicalDensityDpi;
// 一般来说如果旋转和显示密度不变的话,说明屏幕设置没有改动过,比如分辨率没变,所以主要更新下尺寸就可以了
// 如果选择和密度变的话,可能屏幕设置改变了,所以会在onConfigurationChanged方法中回调处理。
// 旋转和密度一样的话,调用setBounds,更新这个taskStack对应的屏幕尺寸
if (mRotation == newRotation && mDensity == newDensity) {
setBounds(mTmpRect2);
}
// If the rotation or density didn't match, we'll update it in onConfigurationChanged.
}
```
这里首先还是判断这个TaskStack所属的DisplayContent是否为null,是的话就返回了。接着会更新TaskStack的子元素,即Task,所以会调用每个子元素的updateDisplayInfo方法,这个稍等下在看。之后就是根据前面传入的参数bounds,调用setBounds来设置当前TaskStack的尺寸,这里如果参数bounds非null或者参数是null,但是mFillsParent为true,表示更新前这个TaskStack是全屏的,那么都会调用setBounds方法来更新这个TaskStack的尺寸,这个方法我们也稍后看下。
如果这里的参数bounds是null,但是之前非全屏的话,就表示尺寸有变化了,因为参数是null表示的是全屏,而之前是非全屏,所以需要更新下现在的尺寸。这里更新的时候会做个判断,如果从DisplayContent中获取的旋转角度和像素密度一样,说明主要的一些配置信息是不变的,所以只要更新下尺寸大小就可以了,不过这里我也没有完全理解透,我个人的理解是这里更新的尺寸mTmpRect2是之前的尺寸,所以其实尺寸也是不变的,所以这里我也感觉没有完全理解这里要调用setBounds的含义。如果旋转角度和像素密度有变化的话,会通过onConfigurationChanged来更新,所以这里也不用管,关于触发onConfigurationChanged的地方我们在后面分析创建窗口流程的时候会遇到,这里先不深入。顺便提一个,这里触发旋转和像素密度变化的场景,一个是横竖屏,一个是修改屏幕的分辨率,都是导致调用onConfigurationChanged的场景。
上面看到设置TaskStack尺寸的方法是setBounds,我们看下这个方法:
```java
private boolean setBounds(Rect bounds) {
// 是否全屏
boolean oldFullscreen = mFillsParent;
// 初始显示角度
int rotation = Surface.ROTATION_0;
// 初始密度值
int density = DENSITY_DPI_UNDEFINED;
// 屏幕非空
if (mDisplayContent != null) {
// 把逻辑屏幕的尺寸赋给mTmpRect
mDisplayContent.getLogicalDisplayRect(mTmpRect);
// 获取屏幕旋转角度
rotation = mDisplayContent.getDisplayInfo().rotation;
// 获取屏幕的密度
density = mDisplayContent.getDisplayInfo().logicalDensityDpi;
// 是否全屏
mFillsParent = bounds == null;
// 全屏的话,尺寸会从屏幕中获取,即mTmpRect,非全屏bounds自己就是尺寸
if (mFillsParent) {
bounds = mTmpRect; // 获取当前屏幕尺寸
}
}
if (bounds == null) { // 进入这里说明是全屏,尺寸会从mTmpRect获取,但是是空,没获取尺寸,return
// Can't set to fullscreen if we don't have a display to get bounds from...
return false;
}
// 新的状态和老的一样,不用设置,返回
if (mBounds.equals(bounds) && oldFullscreen == mFillsParent && mRotation == rotation) {
return false;
}
if (mDisplayContent != null) {
mDisplayContent.mDimLayerController.updateDimLayer(this);
mAnimationBackgroundSurface.setBounds(bounds);
}
// 把当前屏幕的尺寸,旋转,密度保存在taskStack中
mBounds.set(bounds);
mRotation = rotation;
mDensity = density;
// 更新下调节尺寸
updateAdjustedBounds();
return true;
}
```
这个方法会从DisplayContent中获取旋转角度,像素密度和是否全屏等信息,之后会和当前这个TaskStack的尺寸,是否全屏以及旋转角度比较,如果都一样的话,说明不用修改什么,就返回了。否则最后更新尺寸等值,这样就更新好了。
这个方法主要就是根据当前的屏幕信息DisplayContent类来更新TaskStack,前面我们还看到更新TaskStack的时候,还会更新他的子元素Task的信息,调用的是Task的updateDisplayInfo方法,也是根据DisplayContent来更新的,我们也跟过去看一下:
```java
void updateDisplayInfo(final DisplayContent displayContent) {
if (displayContent == null) {
return;
}
if (mFillsParent) { // 全屏
setBounds(null, Configuration.EMPTY);
return;
}
// 获取逻辑屏幕的旋转
final int newRotation = displayContent.getDisplayInfo().rotation;
if (mRotation == newRotation) { // 旋转不变return,否则后面会计算旋转变化后的Rect
return;
}
mTmpRect2.set(mBounds); // 把当前task的尺寸赋值给mTmpRect2
// 如果这个task不能改变大小
if (!StackId.isTaskResizeAllowed(mStack.mStackId)) {
// 那么把当前屏幕大小设置给他
setBounds(mTmpRect2, getOverrideConfiguration());
return;
}
// 计算旋转变化后的mTmpRect2
displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
// 如果旋转变化后的屏幕大小或者位置有变化
if (setBounds(mTmpRect2, getOverrideConfiguration()) != BOUNDS_CHANGE_NONE) {
// 获取TaskWindowContainerController
final TaskWindowContainerController controller = getController();
if (controller != null) {
controller.requestResize(mBounds, RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
}
}
}
```
这个方法主要也是调用setBounds方法来设置Task的大小,这里会区分几种不同情况来设置。首先如果是全屏的,设置bounds的参数就是null,这个会获取DisplayContent中俄屏幕信息来设置Task的尺寸。之后如果旋转不变的话,这里是不会处理的。另外如果当前这个TaskStack的大小是不可改变的话,那么还是会沿用之前的尺寸,即mBounds的值。不可改变大小是指自由模式以外的TaskStack都是属于不可改变大小的,我们看一眼isTaskResizeAllowed这个方法:
```java
public static boolean isTaskResizeAllowed(int stackId) {
return stackId == FREEFORM_WORKSPACE_STACK_ID;
}
```
可以看到这个方法返回true只有自由模式的TaskStack,所谓自由模式,是类似于我们PC Windows上那种窗口可以拖动放大缩小的模式,其实正常我们使用手机一般不太会遇到这种模式,所以大部分情况下,所有Task都会使用初始化时候的尺寸大小,主要是手机可以外接在显示器上,就会有自由模式这种情况了,我们返回上面方法。
最后在自由模式下,并且发生了旋转,还会计算下旋转后的尺寸大小,最后还是通过setBounds方法来更新。Task的更新和TaskStack基本都差不多,主要是通过setBounds方法来更新,相对来说Task除了自由模式更新相对更少一些。
好了,上面介绍了这么多都是从SystemServer进程初始化WMS开始引申出来的,最后我们再来看2个类,这个在后面创建Activity的时候就可以看到,所以这里先简单介绍下。第一个是Window类,他是一个抽象类:
```java
public abstract class Window {
.........
public abstract void setContentView(@LayoutRes int layoutResID);
..............
}
```
这个是一个抽象类,比如刚开始接触WMS的同学,肯定觉得这个类可能是WMS中最关键的一个类之一,其实相对本文前面介绍的这么多内容来说,Window可能并没有我们开始时候觉得的那么重要,甚至这个类没有也不影响WMS对窗口的管理,比如一些系统窗口就不需要Window就可以实现。这个类中的方法也很多,这里我只截取了一个大家最为熟悉的setContentView方法,这个方法就是Activity中设置我们自己定义的xml布局文件用的,所以简单的说Window这个类其实是封装View的一个类,他主要是加载View相关的数据,以及处理和View之间的交互,由于他是一个抽象类,所以他的实现类就是PhoneWindow,这个类我们这里就不看了,后面在分析Activity的时候会再看到他,我们这里主要知道有Window和PhoneWindow的关系就可以了。
另外还有个接口ViewManager,我们看下这个接口:
```java
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
```
这个接口很简单,里面就三个方法,从名字就可以看出来,分别是添加view,更新view和移除view,结合前面的Window类我们说他是加载窗口的,实际上执行的类就是这里ViewManager的实现类,他的实现类是WindowManager:
```java
public interface WindowManager extends ViewManager {
............
}
```
这里也是一个接口,我们这里也不细看他,在看他的实现类WindowManagerImpl:
```java
public final class WindowManagerImpl implements WindowManager {
// 这个构造方法会传入一个phoneWIndow
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
}
```
这里我们看到的构造方法,会传入一个Context和Window,这里的Window就是PhoneWindow,所以他会持有PhoneWindow。我们这里不说具体他们的用法,这篇文章主要是大致介绍下WMS涉及到的一些类和数据结构,为后面分析具体流程做一点铺垫。
这里对于Window和WindowManager的理解,简单说就是Window看作为一个View封装,而WindowManager就是操作和管理具体的每个Window的实现类,当然这里后面还会涉及到一些相关类,我们这里就不再继续说了,等到后面分析流程的时候遇到再说。
好了,介绍了这么多WMS相关的内容,由于都会相对独立的一块一块的讲,可能不太熟悉WMS的同学理解起来很抽象,不过没关系,后面会开始说WMS的流程,结合流程再返回来看看这里独立的每块内容,多理解理解就会觉得容易了,这里由于介绍的东西比较多,其实还有相互之间有联系的,下面会通过图把这里主要的数据接口相互之间的关系联系起来,这样就容易理解了。

以上这幅图是以WindowContainer为中心的类之间的关系图,这些类都是WindowContainer的子类,在WMS中管理的核心就是围绕以上这些类来的,这些我们在后续的分析具体窗口流程的时候会体会到。

最后这幅图是三种WindowContainer和他们对应的WindowContainerController之间的关系,WindowContainer和WindowContainerController之间也是一一对应的关系,从前面我们分析各个WindowContainer中可以知道,其实都是WindowContainerController来创建的并且把他们添加到父元素中,主要就是上图中的三种WindowContainerController,这个下面我们创建Activity的时候会在过一下。
WMS的相关概念就说到这里了,说了不少,但是WMS本身也是比较抽象的,所以可能有不理解的地方,下面我们通过创建一个Activity的流程,从WMS的角度来理解Activty中窗口的意义。
WMS(一)启动与主要概念