WindowContainerTransaction概念详解

基于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定义:

    ini 复制代码
    int 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);
        }
    };
}

总结

  1. 客户端创建一个WindowContainerTransaction对象;
  2. 调用WindowContainerTransaction的相关方法,这一步需要将期望修改的WindowContainer对应的WindowContainerToken对象作为参数传入;
  3. 通过WindowOrganizer将WindowContainerTransaction发送到服务端,最终服务端读取WindowContainerTransaction中保存的参数完成相应操作;
  4. 服务端操作完成之后,通过WindowContainerTransactionCallback将操作结果返回给客户端,客户端根据返回结果进行后续的操作;

WindowContainerTransaction支持以下两类修改

  • 修改WindowContainer的属性,包括WindowingMode和Configuration之类,这类修改保存在WindowContainerTransaction.Change类中;
  • 修改WindowContainer的层级,既可以将一个子容器从当前父容器移入另外一个新的父容器中,也可以仅仅调整子容器在当前父容器中的位置,这类修改保存在WindowContainerTransaction.HierarchyOp类中;
相关推荐
Casual_Lei27 分钟前
ClickHouse 的底层架构和原理
clickhouse·架构
服装学院的IT男36 分钟前
【Android 13源码分析】WindowContainer窗口层级-4-Layer树
android
CCTV果冻爽2 小时前
Android 源码集成可卸载 APP
android
码农明明2 小时前
Android源码分析:从源头分析View事件的传递
android·操作系统·源码阅读
秋月霜风3 小时前
mariadb主从配置步骤
android·adb·mariadb
Python私教4 小时前
Python ORM 框架 SQLModel 快速入门教程
android·java·python
cooldream20094 小时前
828华为云征文 | 在华为云X实例上部署微服务架构的文物大数据管理平台的实践
微服务·架构·华为云·文物大数据平台
编程乐学5 小时前
基于Android Studio 蜜雪冰城(奶茶饮品点餐)—原创
android·gitee·android studio·大作业·安卓课设·奶茶点餐
码拉松5 小时前
千万不要错过,优惠券设计与思考初探
后端·面试·架构
problc6 小时前
Android中的引用类型:Weak Reference, Soft Reference, Phantom Reference 和 WeakHashMap
android