基于Android R版本分析
Android 窗口结构
Android窗口是根据显示屏幕来管理的,每个显示屏幕的窗口层级分为37层,0~36层。每层可以放置多个窗口,上层窗口覆盖下层窗口;
窗口结构包含多个概念:WindowContainer、RootWindowContainer、DisplayContent、TaskDisplayArea、Task、ActivityRecord、WindowToken、WindowState等;
WindowContainer
为直接包含窗口或者通过子层级形式包含窗口的类,定义了普遍功能。
RootWindowContainer、DisplayContent、TaskDisplayArea、Task、ActivityRecord、WindowToken、WindowState都是直接或间接的继承该类;
scala
class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<E>
implements Comparable<WindowContainer>, Animatable, SurfaceFreezer.Freezable,
BLASTSyncEngine.TransactionReadyListener {
// List of children for this window container. List is in z-order as the children appear on
// screen with the top-most window container at the tail of the list.
protected final WindowList<E> mChildren = new WindowList<E>();
}
WindowContainer中定义了一个WindowList类型的集合,WindowList继承ArrayList,通过mChildren变量,可以构建一种树形结构;
-
RootWindowContainer:根窗口容器,通过遍历RootWindowContainer,可以找到窗口树上的窗口,它的Children Window为DisplayContent;
-
DisplayContent:该类是对应着显示屏幕,Android是支持多屏幕的,所以可能存在多个DisplayContent对象。每个DisplayContent都对应着唯一ID,在添加窗口的时候可以通过指定这个ID决定其将显示在哪个屏幕中;
DisplayContent是一个非常具有隔离性的概念,处于不同DisplayContent的两个窗口在布局、显示顺序以及动画处理上不会产生任何耦合;
-
TaskDisplayArea:DisplayContent的Children Window,对应着窗口层次的第二层,第二层作为应用层,对应的Layer定义:
iniint APPLICATION_LAYER = 2;
Task的容器,TaskDisplayArea代表了屏幕上一块专门用来存放App窗口的区域;
-
Tokens:DisplayContent的Children,Tokens的children为WindowToken;
-
ImeContainer:DisplayContent的Children,是输入法窗口的容器,ImeContainer的Children为WindowToken类型;
-
Task:任务,其Children可以为Task,也可以为ActivityRecord;
-
ActivityRecord extends WindowToken:对应着应用进程中的Activity,ActivityRecord是继承WindowToken,其Children为WindowState;
-
WindowState:WindowState对应着一个窗口,用于描述窗口的状态信息以及和WindowManagerService进行通信,表示一个窗口的所有属性;
-
WindowToken:窗口Token,用来做Binder通信,同时也是一种标识。应用组件在需要新的窗口时,必须提供WindowToken以表明自己的身份,并且窗口的类型必须与所有的WindowToken的类型一致;
-
Choreographer:用户控制窗口动画、屏幕选择等操作,它拥有从显示子系统获取Vsync同步事件的能力,从而可以在合适的时机通知渲染动作,避免在渲染过程中因为发生屏幕重绘而导致的画面撕裂。WMS使用Choreographer负责驱动所有的窗口动画、屏幕旋转动画、墙纸动画的渲染;
-
Animator:所有窗口动画的总管(WindowStateAnimator对象),在ChoreoGrapher的驱动下,逐个渲染所有的动画;
ConfigurationContainer
scala
public abstract class ConfigurationContainer<E extends ConfigurationContainer> {}
ConfigurationContainer包含了具有覆盖configuration并以层次结构组织的类的通用逻辑;
ConfigurationContainer是一个Container,类似WindowContainer容器,ConfigurationContainer容器用于存放Configuration对象;
ConfigurationContainer有两个子类:
- WindowContainer
- WindowProcessController:保存一些进程相关的信息,是WMS和AMS沟通的媒介;
ConfigurationContainer虽然没有参与层次结构的构建,但是却借助了WindowContainer构建起来的层次结构,从上到下进行了Configuration的分发;
adb shell dumpsys activity a:打印全屏状态下的ActivityRecord的Configuration信息;
Configuration
kotlin
public final class Configuration implements Parcelable, Comparable<Configuration> {}
这个类描述了所有可能影响应用程序检索的资源的设备配置信息,包括用于指定的配置选项(语言区域列表和缩放)以及设备的配置(如输入模式、屏幕大小和屏幕方向);
Configuration中包含了一个变量:WindowConfiguration,WindowConfiguration持有了窗口状态相关的配置信息,作为Configuration的一部分,跟随Configuration一起进行分发;
WindowConfiguration
kotlin
public class WindowConfiguration implements Parcelable, Comparable<WindowConfiguration> {}
这个类用于描述窗口配置或者状态信息的类,用于那些直接或间接包含窗口的容器;
yaml
configuration={1.0 ?mcc?mnc [zh_CN_#Hans,en_US] ldltr sw369dp w411dp h369dp 560dpi smll hdr widecg land finger -keyb/v/h -nav/h winConfig={ mBounds=Rect(0, 1498 - 1440, 2960) mAppBounds=Rect(0, 1498 - 1440, 2792) mWindowingMode=split-screen-secondary mDisplayWindowingMode=fullscreen mActivityType=undefined mAlwaysOnTop=undefined mRotation=ROTATION_0} s.10}
mBounds
java
private Rect mBounds = new Rect(); // mBounds=Rect(0, 1498 - 1440, 2960)
代表了Container的边界。例如Task,调用WindowConfiguration的getBounds()方法,返回的就是这个bounds,这个bounds代表的就是Task的边界;
mAppBounds
yaml
private Rect mAppBounds; // mAppBounds=Rect(0, 1498 - 1440, 2792)
代表的是App的可用边界,对比mBounds,区别在于mAppBounds的计算考虑到了insets,需要考虑StatusBar和NavigationBar的高度,而mBounds的边界不考虑insets;
同时,mAppBounds一个比较重要的作用就是,计算Configuration的screenWidthDp和screenHeightDp;
- screenWidthDp = w 411dp
- screenHeightDp = h 369dp
- densityDpi = 560dpi
mWindowingMode & mDisplayWindowingMode
arduino
/** The current windowing mode of the configuration. */
private @WindowingMode int mWindowingMode; // split-screen-secondary
/** The display windowing mode of the configuration */
private @WindowingMode int mDisplayWindowingMode; // fullscreen
- WindowingMode:代表了当前container处于哪一种多窗口模式;
- DisplayWindowingMode:代表了当前container所在的Display处于哪一种多窗口模式里;
mActivityType
arduino
/** The current activity type of the configuration. */
private @ActivityType int mActivityType; // undefined
代表了当前container的Activity类型;
Activity Type | Desc | Value |
---|---|---|
ACTIVITY_TYPE_UNDEFINED | 一般container刚刚创建时使用的模式,在后续使用中需要选择一种非ACTIVITY_TYPE_UNDEFINED的Activity类型 | 0 |
ACTIVITY_TYPE_STANDARD | 标准模式,大部分App都是类型 | 1 |
ACTIVITY_TYPE_HOME | Launcher类型的App,如google原生Launcher中的ActivityRecord都是这种类型 | 2 |
ACTIVITY_TYPE_RECENTS | Recent或者Overview类型,系统中只有一个Activity是这个类型:RecentsActivity - Android Q 是通过判断packageName = com.android.systemui.recents来进行赋值的 - Android R 是通过判断对应Component是否为Recent组件且是否与Recent组件共享相同的uid | 3 |
ACTIVITY_TYPE_ASSISTANT | Assistant类型,目前接触的不多,多和语音助手服务有关 | 4 |
ACTIVITY_TYPE_DREAM | Dream类型,壁纸类型相关的Activity | 5 |
mAlwaysOnTop
arduino
/** The current always on top status of the configuration. */
private @AlwaysOnTop int mAlwaysOnTop; // undefined
用来声明当前container是否总是处于顶层;
ALWAYS Type | Desc | Value |
---|---|---|
ALWAYS_ON_TOP_UNDEFINED | 当前没有定义Always on top | 0 |
ALWAYS_ON_TOP_ON | 当前在此配置中处于开启状态 | 1 |
ALWAYS_ON_TOP_OFF | 当前该配置为关闭状态 | 2 |
我们看一下ALWAYS_ON_TOP的判断条件:
kotlin
/**
* Returns true if the container associated with this window configuration is always-on-top of
* its siblings.
* @hide
*/
public boolean isAlwaysOnTop() {
if (mWindowingMode == WINDOWING_MODE_PINNED) return true; // PIP模式
if (mActivityType == ACTIVITY_TYPE_DREAM) return true; // 壁纸场景
if (mAlwaysOnTop != ALWAYS_ON_TOP_ON) return false; // 没有开启置顶开关
return mWindowingMode == WINDOWING_MODE_FREEFORM
|| mWindowingMode == WINDOWING_MODE_MULTI_WINDOW;
}
这几种场景下,需要有优先级的排序,PIP优先级最高,其次Dream。针对除这两种以外的场景,需要满足两个条件:
- mAlwaysOnTop = ALWAYS_ON_TOP_ON
- mWindowingMode = WINDOWING_MODE_FREEFORM || WINDOWING_MODE_MULTI_WINDOW
mRotation
arduino
private int mRotation = ROTATION_UNDEFINED; // ROTATION_0
当前container的旋转角度,只和当前container所在的display相关,和container所在层级结构无关;
一般情况下,各级的container都是直接继承Display的rotation,但是ActivityRecord中针对尺寸兼容模式有额外的逻辑;
WindowContainerTransaction
java
public final class WindowContainerTransaction implements Parcelable {}
WindowContainerTransaction表示一些WindowContainer上应该一次性应用的操作集合,一般会配合WindowContainerTransactionCallback一起使用,以接收包含同步操作结果的事务;
WindowContainerTransaction类和Transaction类比较相似,Transaction是应用在SurfaceControl上的操作集合,WindowContainerTransaction是应用在WindowContainer上的操作集合,WindowContainerTransaction实现了Parcelable,这为其在系统服务端和App端之间的传输提供了支持;
WindowContainerTransaction # Change (属性)
用于保存单个WindowContainer上的更改,包括Configuration的修改;
java
/**
* Holds changes on a single WindowContainer including Configuration changes.
* @hide
*/
public static class Change implements Parcelable {
public static final int CHANGE_FOCUSABLE = 1; // 焦点
public static final int CHANGE_BOUNDS_TRANSACTION = 1 << 1; // bounds Transaction
public static final int CHANGE_PIP_CALLBACK = 1 << 2; // 画中画 callback
public static final int CHANGE_HIDDEN = 1 << 3; // 隐藏/暴露
public static final int CHANGE_BOUNDS_TRANSACTION_RECT = 1 << 4; // bounds rect
private final Configuration mConfiguration = new Configuration(); // 对应的就是在ConfigurationContainer中声明的Configuration实例,这个类描述了所有可能影响应用程序检索的资源的设备配置信息,包括用于指定的配置选项(语言区域列表和缩放)以及设备的配置(如输入模式、屏幕大小和屏幕方向);
private boolean mFocusable = true; // 焦点持有情况
private boolean mHidden = false; //
private int mChangeMask = 0; // 指向的是CHANGED的类型,包含CHANGE_FOCUSABLE、CHANGE_BOUNDS_TRANSACTION......等等
private @ActivityInfo.Config int mConfigSetMask = 0;
private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;
private Rect mPinnedBounds = null; // 画中画边界值
private SurfaceControl.Transaction mBoundsChangeTransaction = null; // SurfaceControl的事务处理器
private Rect mBoundsChangeSurfaceBounds = null; // Container Bounds变化后的SurfaceBounds值
private int mActivityWindowingMode = -1;
private int mWindowingMode = -1;
}
@ActivityInfo.Config
less
/** @hide */
@IntDef(flag = true, prefix = { "CONFIG_" }, value = {
CONFIG_MCC, // 对IMSI MCC(移动国家码)的变更
CONFIG_MNC, // 对IMSI MNC(移动网络码)的变更
CONFIG_LOCALE, // 对区域设置的变更
CONFIG_TOUCHSCREEN, // 对触摸屏类型的变更
CONFIG_KEYBOARD, // 对键盘类型的变更
CONFIG_KEYBOARD_HIDDEN, // 对隐藏/暴露的键盘或者导航的变更
CONFIG_NAVIGATION, // 对导航类型的变更
CONFIG_ORIENTATION, // 对屏幕方向的变更
CONFIG_SCREEN_LAYOUT, // 对屏幕布局的变更
CONFIG_UI_MODE, // 对处理UI模式的变更
CONFIG_SCREEN_SIZE, // 对屏幕大小的变更
CONFIG_SMALLEST_SCREEN_SIZE, // 对处理最小屏幕大小的变更
CONFIG_DENSITY, // 对屏幕密度的变更
CONFIG_LAYOUT_DIRECTION, // 对布局方向的变更
CONFIG_COLOR_MODE, //
CONFIG_FONT_SCALE, // 对字体缩放因子的变更
})
@Retention(RetentionPolicy.SOURCE)
public @interface Config {}
@WindowConfiguration.WindowConfig
less
/** @hide */
@IntDef(flag = true, prefix = { "WINDOW_CONFIG_" }, value = {
WINDOW_CONFIG_BOUNDS, // Container的边界
WINDOW_CONFIG_APP_BOUNDS, // App的可用边界
WINDOW_CONFIG_WINDOWING_MODE, // WindowingMode
WINDOW_CONFIG_ACTIVITY_TYPE, // ActivityType
WINDOW_CONFIG_ALWAYS_ON_TOP, // 是否置顶
WINDOW_CONFIG_ROTATION, // 屏幕旋转
WINDOW_CONFIG_DISPLAY_WINDOWING_MODE, // DisplayWindowingMode
})
public @interface WindowConfig {}
核心成员变量:mConfiguration,Configuration中包含了很多的配置属性,但是WindowContainerTransaction并不支持对Configuration中所有的属性进行修改,主要是screenSize、windowingMode和bounds等;
WindowContainerTransaction # HierarchyOp (层级)
保存层次结构中reparent/reorder操作的信息;
php
/**
* Holds information about a reparent/reorder operation in the hierarchy. This is separate from
* Changes because they must be executed in the same order that they are added.
* @hide
*/
public static class HierarchyOp implements Parcelable {
private final IBinder mContainer; // 一般指向的是子Task,子WindowContainer对应的token
// If this is same as mContainer, then only change position, don't reparent.
private final IBinder mReparent; // 一般指向的是ActivityStack,父WindowContainer对应的token
// Moves/reparents to top of parent when {@code true}, otherwise moves/reparents to bottom.
// 当{@code true}时,move/reparents 移动到父元素的顶部,否则 move/reparents 移动到底部
// 表示reparent操作后是否需要将子WindowContainer移动到父WindowContainer的top
private final boolean mToTop;
}
WindowContainerTransaction 机制
WindowContainerTransaction 创建
ini
WindowContainerTransaction wct = new WindowContainerTransaction();
WindowContainerTransaction 属性修改
scss
wct.setFocusable(mSplits.mPrimary.token, !mMinimized); // 设置焦点持有情况
scss
WindowManagerProxy.applyHomeTasksMinimized(
mSplitLayout, mSplits.mSecondary.token, wct);
/**
* Assign a fixed override-bounds to home tasks that reflect their geometry while the primary
* split is minimized. This actually "sticks out" of the secondary split area, but when in
* minimized mode, the secondary split gets a 'negative' crop to expose it.
*/
static boolean applyHomeTasksMinimized(SplitDisplayLayout layout, WindowContainerToken parent,
@NonNull WindowContainerTransaction wct) {
// Resize the home/recents stacks to the larger minimized-state size
final Rect homeBounds;
final ArrayList<ActivityManager.RunningTaskInfo> homeStacks = new ArrayList<>();
boolean isHomeResizable = getHomeAndRecentsTasks(homeStacks, parent);
// 计算homeBounds
if (isHomeResizable) {
homeBounds = layout.calcResizableMinimizedHomeStackBounds();
} else {
// home is not resizable, so lock it to its inherent orientation size.
homeBounds = new Rect(0, 0, 0, 0);
for (int i = homeStacks.size() - 1; i >= 0; --i) {
if (homeStacks.get(i).topActivityType == ACTIVITY_TYPE_HOME) {
final int orient = homeStacks.get(i).configuration.orientation;
final boolean displayLandscape = layout.mDisplayLayout.isLandscape();
final boolean isLandscape = orient == ORIENTATION_LANDSCAPE
|| (orient == ORIENTATION_UNDEFINED && displayLandscape);
homeBounds.right = isLandscape == displayLandscape
? layout.mDisplayLayout.width() : layout.mDisplayLayout.height();
homeBounds.bottom = isLandscape == displayLandscape
? layout.mDisplayLayout.height() : layout.mDisplayLayout.width();
break;
}
}
}
for (int i = homeStacks.size() - 1; i >= 0; --i) {
// For non-resizable homes, the minimized size is actually the fullscreen-size. As a
// result, we don't minimize for recents since it only shows half-size screenshots.
if (!isHomeResizable) {
if (homeStacks.get(i).topActivityType == ACTIVITY_TYPE_RECENTS) {
continue;
}
// 设置WindowingMode
wct.setWindowingMode(homeStacks.get(i).token, WINDOWING_MODE_FULLSCREEN);
}
// 设置Bounds
wct.setBounds(homeStacks.get(i).token, homeBounds);
}
layout.mTiles.mHomeBounds.set(homeBounds);
return isHomeResizable;
}
等等,WindowContainerTransaction还提供了很多接口:
- setAppBounds
- setScreenSizeDp
- setBoundsChangeTransaction
- setActivityWindowingMode
- setHidden
上述方法的结构:
less
@NonNull
public WindowContainerTransaction setXxx(
@NonNull WindowContainerToken container, boolean Xxx) {
Change chg = getOrCreateChange(container.asBinder());
chg.mXxx = Xxx;
chg.mChangeMask |= Change.CHANGE_Xxx;
return this;
}
- container:指定需要被修改的container;
- Xxx:需要修改的属性值;
WindowContainerTransaction 事务请求
这里就涉及到的WindowOrganizer,通过WindowOrganizerController将ContainerTransaction发送;
less
/**
* Apply multiple WindowContainer operations at once.
* @param t The transaction to apply.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
public static void applyTransaction(@NonNull WindowContainerTransaction t) {
try {
getWindowOrganizerController().applyTransaction(t);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Apply multiple WindowContainer operations at once.
* @param t The transaction to apply.
* @param callback This transaction will use the synchronization scheme described in
* BLASTSyncEngine.java. The SurfaceControl transaction containing the effects of this
* WindowContainer transaction will be passed to this callback when ready.
* @return An ID for the sync operation which will later be passed to transactionReady callback.
* This lets the caller differentiate overlapping sync operations.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
public int applySyncTransaction(@NonNull WindowContainerTransaction t,
@NonNull WindowContainerTransactionCallback callback) {
try {
return getWindowOrganizerController().applySyncTransaction(t, callback.mInterface);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
上述两个方法都可以发送Transaction请求,区别在于:
- applyTransaction:属于异步事务请求,没有对应的TransactionCallback绑定用于接收Transaction处理结果回调;
- applySyncTransaction:同步事务请求,有Callback事务结果上报;
WindowContainerTransactionCallback
用于接收WindowContainerTransaction请求完成之后接收包含同步操作结果的事务;
java
@TestApi
public abstract class WindowContainerTransactionCallback {
public abstract void onTransactionReady(int id, @NonNull SurfaceControl.Transaction t);
/** @hide */
final IWindowContainerTransactionCallback mInterface =
new IWindowContainerTransactionCallback.Stub() {
@Override
public void onTransactionReady(int id, SurfaceControl.Transaction t) {
WindowContainerTransactionCallback.this.onTransactionReady(id, t);
}
};
}
总结
- 客户端创建一个WindowContainerTransaction对象;
- 调用WindowContainerTransaction的相关方法,这一步需要将期望修改的WindowContainer对应的WindowContainerToken对象作为参数传入;
- 通过WindowOrganizer将WindowContainerTransaction发送到服务端,最终服务端读取WindowContainerTransaction中保存的参数完成相应操作;
- 服务端操作完成之后,通过WindowContainerTransactionCallback将操作结果返回给客户端,客户端根据返回结果进行后续的操作;
WindowContainerTransaction支持以下两类修改:
- 修改WindowContainer的属性,包括WindowingMode和Configuration之类,这类修改保存在WindowContainerTransaction.Change类中;
- 修改WindowContainer的层级,既可以将一个子容器从当前父容器移入另外一个新的父容器中,也可以仅仅调整子容器在当前父容器中的位置,这类修改保存在WindowContainerTransaction.HierarchyOp类中;