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类中;
相关推荐
javaDocker29 分钟前
业务架构、数据架构、应用架构和技术架构
架构
找藉口是失败者的习惯29 分钟前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey2 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
JosieBook2 小时前
【架构】主流企业架构Zachman、ToGAF、FEA、DoDAF介绍
架构
.生产的驴3 小时前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构
大白要努力!3 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
丁总学Java4 小时前
ARM 架构(Advanced RISC Machine)精简指令集计算机(Reduced Instruction Set Computer)
arm开发·架构
天空中的野鸟4 小时前
Android音频采集
android·音视频
ZOMI酱5 小时前
【AI系统】GPU 架构与 CUDA 关系
人工智能·架构