单进程模式与多进程模式
应用程序管理器可以在两种不同的模式下运行系统 UI 和 QML 应用程序
- 单进程模式:系统 UI 和 QML 应用程序都运行在 一个 单一进程中,该进程属于系统 UI。此模式仅由 QML 应用程序支持 - 使用
qml
或qml-inprocess
运行时的应用程序。 - 多进程模式:系统 UI 和 QML 应用程序都在自己的专用进程中运行。
对于每个 Qt 应用程序,最终都是一个进程,你只能有一个 Qt 平台(QPA)插件。为了与底层窗口系统交互,Qt 需要先加载此插件。QPA 插件决定你可以打开多少个顶层窗口;但 Qt 一次只能加载一个此类插件,并且在运行时无法切换。如果你使用的是底层插件,例如嵌入式设备上不与窗口系统交谈的 EGL 全屏,你只能打开一个全屏窗口:系统 UI。
现在,每个顶层窗口只能有一个场景图,每个 QtQuick 项的树都需要绑定到唯一的一个场景图。因此,无论应用程序是在系统 UI 的进程内运行,还是在单独的进程中运行,你系统 UI 中都将始终只有一个窗口、一个场景图和一个 QML 引擎。
单进程模式
单进程模式允许
- 特定的系统应用程序以最大性能运行,且没有合成
- 将系统缩小到目标硬件,该硬件缺少 RAM/GPU 资源以运行在多进程模式下
- 在不支持多进程模式的平台上进行开发,例如 Windows、macOS、Android、QNX,以及带有损坏 Wayland 驱动程序的目标
然而,使用单进程模式带来的代价是稳定性降低和测试性下降:你不能孤立地测试你的组件,这使得诊断错误或崩溃变得困难。
在操作系统中,进程的概念与隔离的概念交织在一起。进程之间不能轻易影响彼此 - 操作系统将尽其所能确保这一点。这种隔离对安全性和稳定性至关重要。例如,如果进程崩溃,它不会沿途拖垮另一个进程。
有时,这种隔离可能会阻碍你作为开发者,如果你需要跨越这个边界与另一个进程进行通信。那么,你需要付出努力通过使用专门的进程间通信机制(通常作为 API 提供)来正确地完成这项工作。
如果你的所有代码都在同一个进程中运行,那么你可以避免使用麻烦并且异步的进程间通信通道。相反,你可以直接访问所需的变量和调用函数。
配置
由于单进程模式只有一个 QML 引擎,它被系统 UI 和所有应用程序共享,因此任何通过 am-config.yaml
等方式提供的 QML 导入路径都会在引擎解析导入语句时被考虑。由应用程序清单文件(info.yaml
)提供的导入语句必须在应用程序启动后才会考虑,即使在应用程序停止加载后也会被读取。
在多进程模式下,只考虑特定于应用程序的导入路径。此外,由于容器可能的限制和安全原因,无法在 info.yaml
文件中使用绝对导入路径。
通常情况下,在配置中定义的路径在单进程模式下可能作为绝对路径提供给 QML;但在多进程模式下作为相对路径。
同样,作为 info.yaml
部分的自定义 pluginPath
在单进程模式下的表现与多进程模式不同。在多进程模式下启动新进程时,新的 pluginPath
非常早地就被添加到 Qt 中,甚至在大多数系统初始化之前。这确保了当使用 QPluginLoader 时,pluginPath
是正确的。相比之下,在单进程模式下,我们需要将额外的 pluginPath
添加到已经在运行的 QApplication 中。这种更改是否有效取决于插件如何加载:如果 pluginPath
每次需要加载新插件时都会被重新评估。
注意:在单进程模式下,一些配置选项不起作用,例如:quicklaunch
、quicklaunchQml
、crashAction
等。
构建和运行时选项
应用程序管理器配置了多进程支持,只要 Qt 的 Wayland 合成器模块可用。要禁用多进程支持,请明确指定 -config force-single-process
选项。如果您指定了 -config force-multi-process
选项,而且合成器不可用,配置步骤将失败。
如果应用程序管理器只支持单进程模式,使用 qml
运行时的 QML 应用程序将始终在 System UI 的同一进程中运行。在这种情况下,省略原生应用程序,因为它们只能在专用进程中运行。这种差异在于入口点:native
运行时有可执行文件,而 qml
运行时有一个主要的 QML 文件。对于后者,应用程序管理器在多进程模式下提供了可执行文件(《appman-launcher》)。
如果应用程序管理器以多进程模式构建,您仍然可以在命令行上通过传递 --force-single-process
强制它在单进程模式下运行。这会产生与上述描述相同的运行时行为。即使应用程序管理器在多进程模式下运行,这也并不意味着 QML 应用程序得到专属的进程:如果它们使用 qml-inprocess
运行时,它们将在 System UI 内部进程执行。
应用程序生命周期
一个需要关注的关键点是,当单进程模式下的应用程序崩溃时,整个程序会被终止。相比之下,当多进程模式下的应用程序崩溃时,System UI 和其他应用程序继续运行。在多进程模式下,System UI 甚至会在应用程序崩溃时接收到通知,并对其做出反应,例如重新启动应用程序。
在应用程序中使用 QML 单例有一些影响。在 QML 中,单例存在于他们的私有但全局的上下文中——它们甚至可以在多个 QML 引擎实例之间共享。单例在首次使用时一次性实例化,并且在整个进程中保持存在。这意味着如果应用程序在单进程模式下终止,已经实例化的任何单例都将持续并保持其当前状态。因此,当应用程序再次启动时,单例的状态可能与多进程情况不同,在多进程情况下,单例将重新实例化。
应用程序窗口
窗口在单进程和多进程模式下的表示方式不同。窗口通过WindowManager::windowAdded信号从应用程序暴露到系统UI。为了便于使用并作为Qt标准qmlscene
和qml
工具的替代,可以在应用程序中使用纯QML窗口,甚至使用Item作为根元素。但是,如果您需要应用程序在单进程和多进程模式之间具有高度的相似性,您必须使用ApplicationManagerWindow。使用ApplicationManagerWindow还有其他好处,例如窗口属性。
在多进程模式下,一个ApplicationManagerWindow从Window派生;而在单进程模式下,则从QtObject派生。因此,来自Window的几个属性在单进程模式中没有任何效果,尽管ApplicationManagerWindow仍然定义这些属性以保证兼容性。以下是一些示例:
- ApplicationManagerWindow通过两种不同的方式暴露给系统UI。
- 在多进程模式下,通过网络边界通过Wayland协议发送窗口内容表面的句柄。系统UI将其作为表面Item获取,这与应用程序的窗口在层次结构上相互独立。
- 在单进程模式下,ApplicationManagerWindow直接将其
contentElement
Item提供给系统UI。因此,应用程序可以访问系统UI本身或任何其他运行中的应用程序中的Item,因为它们都在共享同一个QML场景。
- 在ApplicationManagerWindow的单进程版本中,定义在Window中的一些属性、函数和信号尚未重新实现。
- 由于上述属性或方法中描述的错误,在代码块中会遇到,这会导致在多进程模式中后续语句无法执行;但在单进程模式下并非如此。
资源消耗
在多核心系统上,多进程模式的CPU使用可能更加高效,因为操作系统可以调度更多的进程并且可能更好地利用核心。
然而,在多进程模式下,每个应用程序的内存消耗比单进程模式更高。GPU需要更多的内存来分配额外的窗口表面缓冲区。此外,包含应用程序资产的纹理不共享。如果两个应用程序渲染相同的图像文件,在多进程模式下将创建两个纹理;而单进程模式下只创建一个。
由于额外的数据结构,每个应用程序的CPU内存消耗更高。例如,在一个应用程序运行时,在多进程模式下有QML引擎的两个实例:一个用于系统UI,一个用于应用程序。在单进程模式中只有一个实例,因为一切都在系统UI范围内运行。此外,在多进程模式下可能复制资产。这可以通过使用共享图像提供商或者通过在将图像上传到GPU内存后,通过QSG_TRANSIENT_IMAGES环境变量从CPU内存中移除图像来缓解。
另一方面,多进程模式的一个大优势是,不再需要的应用程序可以被终止,从而释放其分配的资源。在单进程模式下,应用程序永远不会真正终止,因此其内存不会被释放,导致总消耗量随着应用程序的启动而稳步增长,无论它们是否已停止。QML引擎不允许您卸载对象层次结构的一部分。
在一个应用程序中支持单进程和多进程模式
如果你的应用程序需要同时支持单进程模式和多进程模式,你必须在应用程序和系统界面之间定义一套进程间通信(IPC)接口,并始终遵循它们。考虑将这些接口纳入你的审阅政策。虽然你可以使用任何适合你用例的IPC机制,但应用程序管理器包括一个完全抽象单进程与多进程区别的IPC机制。此机制还使得为QML开发者创建新接口变得容易。
请注意,应用程序管理器的IPC不适用于整个中间件的接口。它的唯一目的是避免出现需要跟踪的大量一次性守护进程,涉及需要在系统界面和某些或所有应用程序之间共享的各种状态信息。
©2019 Luxoft瑞典AB。本文件中包含的文档贡献是其各自所有者的版权。所提供的文档是根据免费软件基金会发布的《GNU自由文档许可证》第1.3版条款许可的,请参阅http://www.gnu.org/licenses/fdl.html。Qt及其标志是芬兰及其它国家的Qt公司有限责任公司的商标。所有其他商标归各自所有者所有。