c. display & window之freeform

Android N引入了Multi-Window,Freeform(自由窗口)模式是其中的一种。自由窗口模式下可以实现窗口的自由缩放、移动。而我们知道每个应用都有一个窗口,所以当所有的应用窗口都没有独占屏幕的时候,就可以同时在屏幕自由的显示所有应用,达到跟PC桌面一样的效果,当然这个自由窗口受限于screen width等窗口width相关的配置,并不等同于开发者里面的强制桌面模式。

如果你想要在大/超大屏幕上使用安卓系统,freeform是比较合适的,虽然兼容适配特性还没有TV,phone等设备好,但实际在系统层面的支持已经有具备了相当的能力。

本文会介绍freeform模式的功能开启方式,在framework的代码实现细节和提供给用户和开发者使用的功能特性。

1. freeform功能开启

官方开启功能的方法是在 N上的使用的,后续的版本加入了复杂的多屏窗口逻辑,开启方法最好是参考AOSP里面开启freeform的模拟器的配置,以下是基于安卓12,参考goldfish的tablet freeform 模拟器配置,配置被WindowManagerService和ActivityTaskManagerService 读取后做实现,具体可以查代码逻辑。

  • services/core/java/com/android/server/wm/WindowManagerService.java

Settings.Global.putString(resolver, Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, "1"); Settings.Global.putString(resolver, Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, "1"); Settings.Global.putString(resolver, Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH, "vendor/etc/display_settings_freeform.xml");
  • data/etc/android.software.freeform_window_management.xml

<?xml version="1.0" encoding="utf-8"?> <!-- Copyright (C) 2015 The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <permissions> <feature name="android.software.freeform_window_management" /> </permissions>
  • services/core/java/com/android/server/wm/ActivityTaskManagerService.java

public void retrieveSettings(ContentResolver resolver) { Settings.Global.putInt(resolver, DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 1); Settings.Global.putInt(resolver, DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 1);
  • display_settings_freeform.xml

  • overlay/frameworks/base/core/res/res/values/config.xml

device.mk里面修改

以上配置方法可以实现在不同设备上实现freeform,不局限于特定配置的应用,也不局限于特定配置的设备,效果如下:

image-20240218-023334.png

2. freeform功能实现

activity其实提供了自由窗口的启动方式,系统应用可以直接调用,三方应用则可以通过反射方式(如果在高版本不行,建议自行研究黑科技绕过系统反射限制):

在当前类freeformWindowManagement/mSupportsFreeformWindowManagement都没有地方使用,只是保存了数据库,让mRoot.onSettingsRetrieved();

  • /frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java

遍历displaycontent,更新settings的数据,在函数updateSettingsForDisplay执行,而displaycontent就是display在framework层的数据结构,window属于display的下层数据结构,即一个系统可以有很多display(默认只有一个),有很多window都显示在这个display上,window的configuration都依赖于displaycontent。

先看一下mDisplayWindowSettings,他持有的SettingsProvider就是数据提供者,真正实现类是DisplayWindowSettingsProvider,描述如下:

也就是说对display settings的初始化配置,可以在这两个文件里面修改和覆盖,然后在WMS初始化的时候也可以用代码动态设置,这是大的逻辑,当然具体能支持配置哪些参数,需要检查里面的代码。

updateSettingsForDisplay更新了display 的windowingmode, e.g. if this device is PC and if this device supports freeform。windowingmode即是描述显示模式。

可以看到这个mSupportsFreeformWindowManagement,这里的判断是否支持,是否设置freeform,然后返回结果。这个逻辑是针对所有display的,当然也会针对所有window,activity。

对于新建的window,windowstate是一个window的数据结构。系列第一篇写到window在WMS和客户端的创建过程:

window相关的配置保存在Configuration包括res,locale等等

而window布局相关的配置在WindowConfiguration

比如WindowingMode

上文的reconfigureDisplayLocked也会填充WindowConfiguration的数据

当取出Configuration使用时,会把mTempConfiguration从全局Configuration来更新

也就是拿的default display的配置

总的来说,windowingmode会保存到WMS和displaycontent,这样创建新的display和window的时候,都会使用配置的windowingmode。

3. freeform功能特性

这里部分介绍一下freeform模式与fullscreen模式相比独有的特性,这些特性部分工作得很好,也可以针对性的进行二次开发,更好的满足需求。

3.1 标题栏DecorCaptionView

我们知道所有的activity都有一个 PhoneWindow ,在创建的时候,会创建 DecorViewDecorView 会判断是否要创立一个 DecorCaptionView。 然后在 DecorView 中动态改变中,有参数变量,经过onWindowSystemUiVisibilityChanged(View的可见性改变) 以及 onConfigurationChanged(各种触发装备改变的条件) 的回调,对DecorView 进行更新是否要新建一个小窗的视图。

  • /frameworks/base/core/java/com/android/internal/policy/DecorView.java、

  • /frameworks/base/core/java/com/android/internal/widget/DecorCaptionView.java

DecorCaptionView提供了标题,最大化,关闭,移动窗口等功能

关闭和最大化功能的实现如上,移动窗口则是如下函数实现

startMovingTask不单需要使用的DecorCaptionview上,如果当前是小窗模式,同时也没有显示DecorCaptionview,应用的其他view也可能需要移动整个应用,这时候就需要使用这个函数。

3.2 窗口边框ViewOutlineProvider

目前国内的厂商的小窗口,都是依照DecorCaptionView自定义了一个view,来包裹在外面实现边框等的美化。

比如制作边框圆角,是经过 View 的 setOutlineProvider,提供了一个android.view.ViewOutlineProvider,来设置android.graphics.Outline实现圆角或者其他模式。

3.3 全局显示策略DisplayPolicy

作为系统里边操控显现,dock栏、状况栏、导航栏的风格的首要类, 在每次制作布局之后,都会走到如下applyPostLayoutPolicyLw 函数,进行显示规则堆叠之后,在导航栏更新透明度的时候,将其标记中不透明的深色背景和淡色背景清空。

/frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java

更新高度和背景阴影

3.4 拖拽DragState和窗口层级Dimmer(此部分内容待验证,仅做记录)

image-20240218-025323.png

借助 Android 拖放框架,您可以向应用中添加交互式拖放功能。通过拖放,用户可以在应用中的 View 之间复制或拖动文本、图片、对象(可以通过 URI 表示的任何内容),也可以使用多窗口模式在应用之间拖动这些内容。

Enable drag and drop  |  Views  |  Android Developers

DragState 类主要用于管理和跟踪拖拽操作的状态。拖拽操作通常涉及用户在屏幕上拖动某个视图或对象,并在特定位置释放它。以下是 DragState 类的主要作用和功能:

  1. 管理拖拽动作的状态:DragState 类负责跟踪拖拽操作的各个状态,例如开始拖拽、拖拽中、拖拽结束等。它可以存储和更新与拖拽状态相关的信息。

  2. 处理拖拽手势:这个类可能包含了处理用户拖拽手势的逻辑,例如监测用户手指的移动、计算拖拽物体的位置、响应用户的拖拽操作等。

  3. 协调拖拽操作DragState 类可能与其他系统组件(如窗口管理器或视图系统)协同工作,以确保拖拽操作在屏幕上正确执行。它可能需要调整被拖拽物体的位置,更新UI,或者触发其他操作。

  4. 提供拖拽状态信息:这个类通常会提供有关拖拽状态的信息,例如拖拽物体的位置、拖拽的类型、拖拽的源对象等。这些信息可以供其他组件使用。

  5. 处理拖拽的释放:当用户释放拖拽物体时,DragState 类可能需要执行特定的操作,例如将拖拽物体放置在新的位置、触发操作、或者完成拖拽操作。

  6. 支持拖拽的可视化效果:在一些情况下,DragState 类可能需要管理与拖拽相关的可视化效果,例如拖拽物体的影子或者拖拽物体的预览。

Dimmer 类的作用是为实现窗口容器(WindowContainer)添加“调暗层”(DimLayer)支持,通过在不同的 Z 层级上创建具有不同透明度的黑色层,从而实现调暗的效果。该类主要用于在窗口容器中管理和应用调暗效果。

该类的主要方法和功能如下:

  • dimAbovedimBelow 方法 这些方法用于在指定的窗口容器上方或下方添加调暗层。可以设置调暗层的透明度(alpha)和相对于指定容器的 Z 层级。

  • resetDimStates 该方法标记所有的调暗状态为等待下一次调用 updateDims 时完成。在调用子容器的 prepareSurfaces 之前调用此方法,以允许子容器继续请求保持调暗。

  • updateDims 方法 在调用子容器的 prepareSurfaces 后,通过此方法来更新调暗层的位置和尺寸。根据容器的更新,它可以设置调暗层的位置、大小以及调整动画。

  • stopDim 方法 用于停止调暗效果,可以在不再需要调暗时调用。

  • 内部类 DimState 表示调暗状态的内部类,包含调暗层的 SurfaceControl、调暗状态等信息。

Dimmer 类用于管理和应用调暗效果,使窗口容器在不同 Z 层级上添加透明度变化的调暗层,以达到调暗的效果。 在 Android 系统中用于处理窗口切换、过渡动画以及调整显示效果时很有用, 如果要对小窗进行圆角的处理,这一层也是要处理的, 不然会出现黑色矩形边的问题。

其他需要适配的特性,此外还有有一些其他特性,需要开发者二次开发,才能更符合用户使用习惯。

3.5 窗口尺寸TaskLaunchParamsModifier

修改ActivityOptions里面修改activity显示的区域,最终在TaskLaunchParamsModifier生效,com.android.server.wm.TaskLaunchParamsModifier#onCalculate才是最终控制activity窗口显示各种具体数据的运算逻辑。

3.6 前后台状态ActivityStack

我们知道Android系统对activity的管理非常严格,这种严格有时候让有一些基本能力都具备,既可以理解成安全性考虑,也可以理解成谷歌犯懒,只提供需要的API接口。这种严格体现在activity的前后台状态时,你会发现获得这个状态是非常难的,从系统端到应用端完全不具备这个能力。

3.7 AppTransition

修改窗口弹出来和关闭的动画来适配桌面模式,com.android.server.wm.AppTransition#loadAnimation里面定义了各种应用进出的动画,如果不对其进行适配,窗口模式应用的进出动画会按照原先手机模式来运行,并不匹配。

3.8 内存

比如JVM内存,lmkd参数最好是进行相应的调整,否则运行效率肯定不会让你满意。

总结

freeform模式控制的是窗口,他让应用像运行在桌面一样,显示区域可以自行控制,这本身就是Android系统可以实现的功能,只是窗口的交互逻辑还不完善。实现方案从效率来看也不够优秀,Java开发的图形程序可以满足基本的使用需求,但是如果作为大屏操作系统,他的运行效率、安全性,硬件适配能力是无法逾越的鸿沟,在窗口这部分核心架构使用这么久的情况下,如果没有更复杂的场景,大可能也不会进行修改了,所以在此基础上优化和发展Android的多屏多窗口能力有更多的挑战。