窗口融合方案

FDE环境提供linux图形应用在Android图形系统显示,为了提供更友好,更一致地用户体验,需对Android窗口和X11窗口等图形系统进行融合处理。包含的内容有:Android和X11窗口无缝切换,光标等焦点,输入法无缝切换。

Android技术分析

Android窗口管理与合成

Android窗口管理和合成由WindowManager和SurfaceFlinger服务实现,窗口元数据(属性)须在WindowManager和SurfaceFlinger之间同步;而窗口显示数据通过buffer方式直接传递给SufafceFlinger。

  1. Image stream producers:数据内容生产者,如:相机预览,多媒体播放,图形应用程序等

  2. Image stream consumers

    1. 最常见的图像流消费者是SurfaceFlinger系统服务,获取当前可见的Surface,使用窗口管理器提供的信息将它们合成到显示器上。SurfaceFlinger是唯一可以修改显示内容的服务。SurfaceFlinger使用OpenGL和Hardware Composer来组成一组Surface。消费的是Surface

    2. OpenGL ES应用程序,如相机应用程序使用相机预览图像流,消费的是SurfaceTexture(摄像头产生的纹理数据)。

  3. Window positioning:最常见的窗口位置定位是WindowManager系统服务,监督window的生命周期、输入和焦点事件、屏幕方向、视图转换、动画、位置、转换、z轴顺序等;

  4. Native framework:实现BufferQueue功能,为App与SurfaceFlinger等服务提供数据交换基础设施。

  5. Hardware abstract layer

    1. Hardware Composer:提供硬件合成功能的封装

    2. Gralloc:提供申请图形内存的封装

窗口管理

窗口结构

  • Activity:在Android应用程序中,Activity是用户界面的主要交互点和组织者。每个Activity都与一个Window紧密相关联,Window代表了一个可绘制区域,负责显示Activity的用户界面。Activity由Activity Management Service管理。

  • Window:Window是顶层窗口外观和行为策略的抽象基类,Window实例用于添加到窗口管理器中的顶级视图。Window包含整个用户界面的容器,包括所有的View和ViewGroup,作为窗口的外观和行为策略。View代表视图,ViewGroup表示试图组,也是一个View,View和ViewGroup组成一个View树, window中的第一个View为根View。PhoneWindow是Android Window接口的实现,当为应用程序窗口类型时,需要指定activity的token。

  • DecorView:DecorView(装饰视图)是一个ViewGroup,它包含了应用程序的内容视图(ContentView),状态栏,系统栏等。

  • TitleView:标���栏通常包含应用程序的标题和操作按钮(例如返回按钮)。

  • ContentView:内容视图是开发者定义的用户界面布局,包括按钮、文本框、图像等元素。

  • StatusView:状态栏位于屏幕的顶部,通常包括系统通知、时间和电池状态等信息。

Activity启动和构建Window流程图见下:

窗口类型

共有3大主要类别窗口:

  1. 应用程序窗口 (Application Windows):应用程序窗口是普通应用程序界面的基本组成部分,用于显示应用程序的用户界面,如活动(Activity)和对话框。可以包括标题栏、内容视图和系统状态栏。它们通常可以获得焦点,并且可以与用户交互。

  2. 子窗口 (Sub-Windows):子窗口是应用程序窗口的一部分,通常用于显示特定功能或内容的一部分,例如弹出菜单、对话框、浮动窗口等。子窗口依赖于其父窗口。

  3. 系统窗口 (System Windows):系统窗口用于显示系统级信息、控制界面元素和系统通知。包括状态栏、导航栏、锁屏、通知栏等。

  4. 其它特殊窗口:

    1. Toast窗口:用于显示短暂的通知消息。它们是一种轻量级的提示框,通常不需要用户交互。

    2. 悬浮窗口:一种浮动在其他窗口上方的窗口,通常用于显示实时信息或小工具。

    3. 键盘窗口:用于捕获和处理用户的键盘输入。键盘窗口通常是系统级窗口,由输入法管理器控制。

    4. 系统提示框:用于显示系统级提示,如权限请求、应用更新等。

窗口管理器

Android提供了一个窗口管理器服务WindowManagerService,管理所有应用程序窗口的显示和布局。监督window的生命周期、输入和焦点事件、屏幕方向、视图转换、动画、位置、变换、z轴顺序等;发送所有的窗口元数据给SurfaceFlinger,这样SurfaceFlinger使用这些数据在显示器合成Surface。

每个Activity向WindowManagerService申请一个WindowManager示例,该WindowManager示例作为与WindowManagerService通信的代理类。

多窗口风格

Android的Window Manager提供了3种多窗口风格:

  • Split-screen:分屏风格,(只对宽边进行分屏)。如:当高度比宽度长时,上下分屏两个;当宽度比高度长时,左右分屏两个。

  • Freedom:自由窗口风格,可对窗口进行缩放,窗口间任意堆叠,同级窗口间有个z-order序,即窗口的堆叠顺序。

  • Picture-in-Picture:画中画风格,将视频窗口缩小显示在其它应用的上面

只能在3种模式中选择一种模式,对于窗口融合系统来说,需要选择Freedom风格

View和Window的类图

View和Window抽象类图见下:

从类图看出,View是个抽象类,作为响应输入输出的主要类,其实现了KeyEvent,TouchEvent,Draw等事件操作。ViewGroup实现了ViewManager操作,也是一个View,采用组合模式结构。Window是个抽象类,提供了构建View布局的接口,以及关联WindowManager的接口,ViewGroup和View采用组合模式,在window中所有View子类的实例化都用View表示。WindowManager是一个抽象类,实现ViewManager接口,WindowManager管理的Window实际为DecorView,一个View的子类,从而将对window的管理变换成对View的管理。

View层次结构只涉及Window内部的窗口布局,WindowManager不管理窗口内部的View,只管理Window本身,处理Window间的关系。

SurfaceView是一个拥有buffer的View实现。Surface是生产者和消费者交换buffer的接口。Surface有个z-order的层级关系,一个Window与一个Surface对应,因此window的层级关系与Surface保持一致。Surface包含一个BufferQueue,该BufferQueue与SurfaceFlinger关联。

Window和Window Manager实现类图见下:

响应事件的回调函数说明:

  1. 窗口和尺寸的变化,获取尺寸的接口有:View::getLeft, View::getTop, View::getRight, view::getBottom

    1. View::onMeasure获得boardSize

    2. View::onLayout获得新的尺寸,坐标值为相对值,由于为顶级窗口,其值一般为屏幕坐标

    3. View::onSizeChanged尺寸发生变化,无坐标,只有新旧宽高值。

  2. 输入事件获取:onKeyDown, onKeyUp, onGenericMotionEvent

  3. 焦点事件获取: onFocusChanged, onWindowFocusChanged

  4. 窗口重绘:onDraw, Canvas::getClipBounds可获取剪接区,一般为可见区域。

窗口合成

Android提供了SurfaceFlinger服务,对窗口内容进行合成,该服务使用HWComposer实现硬件合成窗口。图形应用程序通过BufferQueue将渲染数据缓冲传递给SurfaceFlinger,或者直接将渲染数据缓冲传递给HWComposer。SurfaceFlinger将多个窗口合成至一个图形缓冲中,通过Buffer Queue将图形缓冲数据传递给HWComposer。HWComposer根据窗口的层级通过硬件合成窗口,显示在屏幕上。

在Android设备中,一般支持4个窗口的硬件合成,如:系统栏、状态栏、背景图、应用程序渲染图层等。从上图中可知:状态栏和系统栏的窗口内容通过SurfaceFlinger合成一个图层;背景图使用一个图层;应用组件使用一个图层;然后交由HWComposer合成至屏幕。当图层多余4个时,需由SurfaceFlinger将多余图层合成一张图再交由HWComposer合成。

Layer

Layer包含一个Surface和SurfaceControl,也定义了与其它Layer交互的属性,主要属性列表如下:

Property

Description

Positional

定义图层(Layer)在其屏幕中的位置。包括诸如层边的位置及其相对于其他层的Z轴顺序等信息

Content

定义如何呈现图层上显示的内容。包括诸如crop(填充图层)和transform(显示图层的旋转或翻转内容)等信息。

Composition

定义图层应该如何与其他图层合成。包括混合模式和用于alpha合成的alpha值等信息。

Optimization

提供层合成的辅助信息,硬件合成器(HWC)设备可以使用这些信息来优化其执行合成的方式。包括诸如层的可见区域以及层的更新区域信息。

SurfaceFlinger按照Layer的层级进行合成,根据图层的属性可以有效合成数据。Layer的层级关系与Surface保持一致。

BufferQueue

当应用程序在前台运行时会向WindowManager申请Buffer,通过BufferQueue获取buffer,其流程如下:

Surface包含一个BufferQueue,为应用程序提供了对BufferQueue的操作。对BufferQueue的操作说明如下:

Producer和Consumer之间通过BufferQueue通信,WindowManager将App与SurfaceFlinger建立联系。App为producer,SurfaceFlinger为Consumer

  1. App指定尺寸,像素格式,其它标志等,通过dequeue(dequeueBuffer)获得一个render buffer。

  2. App将内容渲染至buffer中,然后将buffer queue(queueBuffer)至队列中

  3. SurfaceFlinger通过acquire(acquireBuffer)获取已渲染的buffer进行合成。

  4. SurfaceFlinger对合成或者显示完成的buffer释放(releaseBuffer)至队列中。

Input

在创建Window时,通过WindowManagerService向InputManagerService注册,并通过InputChannel创建Window与InputDispatcher连接。具体流程可参见附录的《Input机制》。

  1. 输入设备驱动程序负责通过Linux输入协议将特定于设备的信号转换为标准的输入事件格式

  2. Android EventHub组件通过打开与每个输入设备关联的evdev驱动程序从内核读取输入事件

  3. Android InputReader组件根据设备类解码输入事件,将Linux输入协议事件代码根据输入设备配置、键盘布局文件和各种映射表被转换成Android事件代码,并生成Android输入事件流。。

  4. InputReader将输入事件发送给InputDispatcher,后者将它们转发到适当的Window。

场景分析

以上图为例,X开头的为X11窗口,蓝色调;A开头的为Android窗口,红色调;屏幕背景为绿色。X11窗口顺序X1->X2->X3,其中X2和X3无遮挡;Android窗口顺序A1->A2->A3,其中A2和A3无遮挡。从图中可看出融合窗口的顺序:X1->A1->X2->A2->X3->A3。

每个窗口都有自己的画布(surface),各自都画在自己的窗口上。X11使用composite扩展协议支持顶层窗口使用自己的画布;而Android的window都有自己的画布。

从X11 Server看来,X2和X3整个窗口被重新绘制,X1只重新绘制了未被X2遮挡的窗口,Android合成器按照融合窗口顺序合成,X11窗口多绘制的部分被android窗口重绘了。由此可见Android合成器需知道整个融合窗口的层次关系。

X1窗口置顶

  1. 内容变化:X1遮挡A1,X2和A2部分内容,被遮挡的内容不需要显示,而X1露出来的区域需要重绘;

  2. 窗口序变化,变化后总序为: A1->X2->A2->X3->A3->X1;X11窗口的顺序变为X2->X3-> X1

  3. 焦点切换:焦点从原来A3窗口转移至X1窗口,光标图形改为X1的光标图形。

X2窗口置顶

  1. 内容变化:X2遮挡A2部分内容,X2新暴露了部分内容。相对于X11窗口来说,X2从未被遮挡(X11 server不能感知Android窗口),不会重绘任何窗口。

  2. 窗口序变化,变化后总序为: X1->A1->A2->X3->A3->X2;X11窗口的顺序变为X1->X3-> X2

  3. 焦点切换:焦点从原来A3窗口转移至X2窗口,光标图形改为X2的光标图形。

X3窗口置顶

  1. 内容变化:X3遮挡A3部分内容,X3新暴露了部分内容。相对于X11窗口来说,X3从未被遮挡(X11 server不能感知Android窗口)。

  2. 窗口序变化,变化后总序为: X1->A1->X2->A2-> A3->X3;X11窗口的顺序未变

  3. 焦点切换:焦点从原来A3窗口转移至X3窗口,光标图形改为X3的光标图形。

A1窗口置顶

  1. 内容变化:A1遮挡X2和A2部分内容,A1新暴露了部分内容。从X11窗口的角度,X1,X2,X3的覆盖情况未发生任何改变,X11 Server不向client发送Expose事件。

  2. 窗口序变化,变化后总序为: X1 ->X2->A2 ->X3->A1-> A3;X11窗口的顺序未变

  3. 焦点切换:焦点从原来A3窗口转移至A1窗口,光标图形改为A1的光标图形。

A2窗口置顶

  1. 内容未变

  2. 窗口序变化,变化后总序为:X1->A1->X2->X3->A3->A2

焦点切换:焦点从原来A3窗口转移至A2窗口,光标图形改为A2的光标图形

技术方案

总体思路:提供一个窗口融合层FushionWindow,提供X11 Server在Android中的运行环境,同时作为X11 Server和Android图形系统的适配层。

FushionWindow实现X11窗口管理和窗口合成功能,对每个X11顶层窗口对应创建一个Android window,并为每个Android Window创建DecorView和ContentView。Android Window可复用Android的SurfaceFlinger合成功能,以及View响应输入事件功能。

X11 Server和FushionWindow以Android Service的形式存在。

Input事件适配

实现View以下接口或者重新定义类似虚函数,可捕获Input事件,可参照termux-x11的InputStub接口的定义和实现。可采用服务接口建立于X11 Server的连接,然后通过Socket进行通信。

View Interface

Virtual Function

Description

OnTouchListener

onTouchEvent

响应触摸事件

OnLongClickListener

N/A

长按鼠标按钮事件

OnKeyListener

N/A

键盘事件

OnHoverListener

onHoverEvent

悬停事件

OnGenericMotionListener

onGenericMotionEvent

光标移动事件

OnFocusChangeListener

onWindowFocusChanged

改变焦点事件

OnClickListener

N/A

点击事件

OnLayoutChangeListener

onLayout/ onSizeChanged

View边界发生变化事件,移动或缩放

Termux-X11定义了六类事件,并对每类事件定义相应的数据接口,然后通过socket传输这些内容。

typedef enum {

EVENT_SCREEN_SIZE,

EVENT_TOUCH,

EVENT_MOUSE,

EVENT_KEY,

EVENT_UNICODE,

EVENT_CLIPBOARD_SYNC

} eventType;

X11 Window Manager

实现EWMH扩展协议,ICCCM扩展协议中与WM相关部分,实现composite扩展协议,对每个top-level window创建一个独立的pixmap,并创建一个Android window与top-level window一一对应。并为每个Window创建DecorView和ContentView。

拉起X11 App时,在Fushion Window System中创建对应的Window和View,通过Window和View显示X11图形,从而支持X11 client创建top-level window,如对话框等,这些窗口重新对应Android的window和view,同时分配Surface buffer,从而显示多个窗口。

页框的实现可由Android的TitleView实现,也可由X11 WindowManager实现。由Android实现页框可使窗口风格保持一致。

X11窗口管理,一般来说点击某个窗口,该窗口显示在顶层,Android WindowManager将X11窗口对应的Window显示在顶层,X11 WindowManager将X11窗口显示在X11 server的顶层,此时的X11窗口树可以保持一致。

但对于有模态对话框的X11窗口来说,X11窗口须在模态对话框之下,此时,Android WindowManager已将X11窗口显示在顶层,而X11 WindowManager将模态对话框置顶,将X11窗口显示在模态对话框的下方。此时X11窗口树接口不一致。是否可有方法将Android window dialog也设成模态对话框,此时含义于X11窗口管理一致,有待查证。

 

X11-Server端的Input

需要模拟X11的Input Device设备,以便集成至X11 Server框架中。

  • 实现DeviceProc回调函数,处理设备的状态变化

  • 调用AddInputDevice,指定DeviceProc创建Input设备

  • 调用ActivateDevice/EnableDevice激活/使能设备

具体实现可参照xwayland和termux-x11

  • xwayland支持设备热插拔:wayland提供了wl_seat_interface等协议

  • termux-x11不支持设备热插拔,程序启动时在InitInput函数中模拟了Input设备。

实现DDX函数 ProcessInputEvents 来处理Input事件,一般调用 mieqProcessInputEvents,该函数最后调用miPointerUpdateSprite更新光标精灵。

X11-Server光标处理

  1. 实现显示光标精灵的回调函数(miPointerSpriteFuncRec)

  2. 实现光标屏幕处理回调函数,如:跨屏等(miPointerScreenFuncRec)

  3. 调用 miPointerInitialize 注册回调函数

注:具体实现可参照xwayland和termux-x11

// 1. 实现显示光标精灵的回调函数

miPointerSpriteFuncRec miSpritePointerFuncs; // sw cursor

miPointerSpriteFuncRec drmmode_sprite_funcs; // hw cursor

miPointerSpriteFuncRec xwl_pointer_sprite_funcs; // xwayland

miPointerSpriteFuncRec loriePointerSpriteFuncs; // termux-x11

 

// 2. 实现光标屏幕处理回调函数,如:跨屏等

miPointerScreenFuncRec xwl_pointer_screen_funcs; // xwayland

miPointerScreenFuncRec loriePointerCursorFuncs; // termux-x11

 

// 3. 调用 miPointerInitialize 注册上述回调函数

窗口合成

在X11中,当drawable上的内容有变化时,根据damage报告的区域进行合成至屏幕显示,该功能由X11 compositor完成。

从场景分析中可看出,X11窗口在X11的视角,其窗口可视区域比混合窗口的可视区域大,Android窗口遮挡的区域,X11窗口无法感知。因此,X11窗口渲染的区域比屏幕上可视区域要大。对于动态画面来说,X11应用程序会定时这些可��区域中的绘制图像,资源消耗相对多一些。在窗口合成前,需将X11窗口的pixmap与Android的surface的内容进行同步(Buffer可以共享),然后SurfaceFlinger根据窗口的(Layer)层次关系进行合成。

对X11窗口,使用damage扩展协议,获得窗口的damage区域,若未内容发生变化,则不需要同步至surface。

硬件渲染

X11硬件渲染包括server端和client端的硬件渲染,原生的server端使用glamor来实现硬件渲染功能,client端使用cario 2D硬件渲染和GLX或EGL实现3D硬件渲染。DRI3硬件直接渲染依赖glamor提供的GPU内存分配和共享功能。然而,由于Android和Linux系统GPU内存导入或导出等内存共享的实现机制不同(Android采用gralloc,而linux使用GBM)。DRI3等直接渲染方案无法直接在Android环境下使用。

X11 Server在Android环境下运行,采用gralloc分配GPU内存,使用binder机制共享 。从Android12开始,gralloc采用dmabuf机制实现堆管理,ION被弃用。gralloc的mapper 2.1版本提供了getTransportSize获取buffer对应句柄的接口。

对于支持DRI3 X11硬件渲染,重新定义实现dri3_screen_info_rec数据结构,结合新版的gralloc实现,在dri3_fds_from_pixmap_proc等回调函数中适配gralloc和gbm接口,重新实现glamor提供的GPU内存分配和共享功能。

附录

Input机制

以下流程图来自博客:安卓 Input 机制

Window注册Input事件

WindowManagerService为ViewRootImpl和InputDispatcher通过InputChannel建立连接。

Input事件生成与分发

Input事件由InputDispatcher分发至焦点Window,由此可见InputDispather需要知道Input设备与focusedWindow窗口的关系。

InputDispatcher分发事件

此节内容来自于Android Input输入事件处理流程分享

InputDispatcher通过findFocusedWindowTargetsLocked方法查找到焦点窗口,然后调用dispatchEventLocked朝焦点窗口上分发事件。

 

InputReader处理事件

 

 

EventHub处理事件

 

 

ViewRootImpl Input事件分发

将Input事件先分发至特定的View中,然后再发送至DecoView,最后交由Activity。