QML 磁盘缓存机制深度研究报告:架构原理、内部实现与工程实践
此外,本报告还将基于 qtdeclarative 模块的底层源代码,解析其核心算法(如哈希命名、魔数校验等),并对比分析运行时缓存与 Qt 6 引入的提前编译(AOT)技术(如 qmlsc 和 qmlcachegen)在性能与应用场景上的差异。上述步骤中,前两步(解析与字节码编译)是确定性的:只要源代码、Qt 版本和 CPU 架构不变,生成的字节码就是恒定的。展望未来,随着 Qt 7 的规划讨论,
QML 磁盘缓存机制深度研究报告:架构原理、内部实现与工程实践
1. 执行摘要
在现代图形用户界面(GUI)开发领域,声明式语言(Declarative Languages)因其高效的开发模式和直观的代码结构而占据了重要地位。Qt 框架中的 QML(Qt Meta-Object Language)正是这一趋势的杰出代表。然而,声明式语言通常依赖于运行时解释或即时编译(JIT),这在带来灵活性的同时也引入了显著的性能开销,特别是在应用程序启动和复杂界面加载阶段。为了缓解这一矛盾,Qt 引入了 QML 磁盘缓存(QML Disk Cache)机制。
本报告旨在对 QML 磁盘缓存机制进行详尽、深入的剖析。报告将从 QML 引擎的编译管线切入,深入探讨磁盘缓存的架构设计、二进制文件格式(.qmlc 和 .jsc)、缓存生成与失效的判定逻辑、以及跨平台的存储策略。此外,本报告还将基于 qtdeclarative 模块的底层源代码,解析其核心算法(如哈希命名、魔数校验等),并对比分析运行时缓存与 Qt 6 引入的提前编译(AOT)技术(如 qmlsc 和 qmlcachegen)在性能与应用场景上的差异。
通过本报告,读者将获得关于 QML 磁盘缓存的全景式认知,不仅能够理解其“黑盒”下的运作机理,还能掌握在实际工程中优化启动速度、排查缓存相关故障以及定制编译策略的高级技巧。
2. 引言:声明式 UI 的编译挑战与缓存的必要性
2.1 QML 引擎的执行困境
QML 作为一种描述性的脚本语言,其本质是文本。当 Qt 应用程序启动时,QQmlEngine 需要加载这些文本文件,并将其转化为计算机能够执行的指令。在一个标准的、未经过优化的加载流程中,这一过程包含多个计算密集型步骤:
- 词法分析(Lexing)与语法分析(Parsing):引擎首先读取 .qml 或 .js 文件的原始字节,将其分解为标记(Tokens),并构建抽象语法树(AST)。这一步骤的时间复杂度通常与源代码的长度成线性关系,但在嵌入式设备或移动端,解析数千行代码的耗时是不可忽视的。
- 编译(Compilation):AST 被进一步遍历并编译为 Qt Quick 内部 V4 虚拟机(V4 VM)的字节码(Bytecode)。这些字节码定义了对象的创建、属性的绑定(Bindings)以及信号槽的连接逻辑。
- 优化与即时编译(JIT):对于热点代码,V4 引擎的 JIT 编译器可能会将其进一步转化为机器码以提升运行时性能。
上述步骤中,前两步(解析与字节码编译)是确定性的:只要源代码、Qt 版本和 CPU 架构不变,生成的字节码就是恒定的。然而,在没有缓存机制的情况下,每次应用程序启动,CPU 都必须重复执行这些繁重的计算任务。这不仅浪费了处理能力,更直接导致了用户感知到的“启动卡顿”或“界面加载延迟”。
2.2 磁盘缓存的介入机制
QML 磁盘缓存的设计初衷,正是为了消除这种重复计算。其核心思想是“一次编译,多次运行”。当 QML 引擎首次加载一个文件并完成字节码编译后,它会将生成的编译单元(Compilation Unit)序列化并持久化到本地存储设备(即磁盘)。
在随后的运行中,引擎在解析源文件之前,会首先检查是否存在对应的缓存文件。如果缓存存在且通过了有效性验证,引擎将直接通过内存映射(Memory Mapping, mmap)技术加载预编译的字节码,从而完全跳过词法分析、语法分析和字节码生成阶段。这种机制将原本的计算密集型任务(Compute-bound)转化为输入输出密集型任务(I/O-bound),而在现代操作系统和硬件架构下,读取预处理好的二进制数据通常比解析文本快得多。
3. QML 磁盘缓存的架构与工作流
3.1 编译单元(Compilation Unit)的概念
在深入缓存文件之前,必须理解 Qt 内部的“编译单元”概念。根据 qtdeclarative 的源代码分析,特别是 qv4compileddata.cpp 和 qv4executablecompilationunit.cpp,一个编译单元不仅仅是字节码的集合,它是一个封装了完整模块信息的复杂数据结构。
一个典型的 QML 编译单元包含:
- 字节码段(Bytecode Segment):V4 虚拟机的指令集,涵盖了函数逻辑、表达式求值等。
- 字符串表(String Table):源代码中出现的所有标识符、属性名和字符串字面量的去重集合。这有助于减少运行时的内存占用。
- 对象结构定义(Object Structure):QML 是层级化的对象树,编译单元描述了这种层级关系(例如,一个 Window 包含一个 Rectangle,Rectangle 包含一个 Text)。
- 依赖图谱(Dependency Graph):记录了当前文件依赖的其他 QML 组件或 JavaScript 模块,用于运行时解析导入(Imports)。
3.2 缓存生命周期管理
QML 磁盘缓存的生命周期由 QQmlEngine 的加载器(QQmlTypeLoader)自动管理,对开发者通常是透明的,但理解其状态流转对于调试至关重要。
- 缓存生成(Write):
- 触发条件:当引擎加载一个 .qml 或 .js 文件时,如果检测到缓存目录中没有对应的缓存文件,或者现有缓存文件已失效。
- 执行动作:引擎在内存中完成编译后,将 QV4::CompiledData::Unit 序列化,并写入缓存目录。默认情况下,如果缓存目录不可写(例如权限不足),引擎会静默失败并仅在内存中保留字节码。
- 缓存读取(Read):
- 触发条件:加载文件路径在缓存目录中有对应哈希命名的文件。
- 验证:引擎读取文件头,进行魔数(Magic Bytes)、版本号、时间戳等多重校验。
- 加载:通过 mmap 将文件映射到进程地址空间。这意味着操作系统可以按需加载页面(Page Fault),而不是一次性读取整个文件,从而显著降低内存压力并提升启动速度。
- 缓存失效与更新(Invalidation):
- 如果源文件被修改(时间戳变更),或者 Qt 库本身升级(版本号变更),旧的缓存文件被视为“陈旧”(Stale)。
- 引擎会丢弃旧缓存,重新编译源文件,并覆盖写入新的缓存文件。
4. 深入二进制:文件格式与内部结构
QML 磁盘缓存并不是简单的内存转储,它采用了一种专门设计的二进制格式,以支持快速校验和跨进程共享。主要的缓存文件扩展名为 .qmlc(用于 QML 文件)和 .jsc(用于 JavaScript 文件)。
4.1 文件头与魔数(Magic Bytes)
任何二进制格式解析的第一步都是识别文件头。根据 qtdeclarative 源码中的 Unit::verifyHeader 函数 6,缓存文件以一个特定的“魔数”开始。
- 魔数定义:分析显示,QML 缓存文件的头部魔数为 0x71 0x76 0x34 0x63 0x64 0x61 0x74 0x61。将其转换为 ASCII 字符,即为 qv4cdata(Qt V4 Compiled Data)。
- 作用:这是缓存文件的“身份证”。如果文件开头的 8 个字节不是这个序列,QML 引擎会立即判定该文件不是合法的缓存文件并拒绝加载,从而防止因加载错误文件导致的崩溃。
4.2 头部元数据与校验字段
紧随魔数之后,是严格对齐(通常是 16 字节对齐)的元数据字段,用于确保缓存的兼容性。
| 偏移量/字段 | 数据类型 | 描述与作用 | 校验逻辑 |
|---|---|---|---|
| Data Structure Version | quint32 | 定义缓存文件内部数据布局的版本。 | 随着 Qt 版本的迭代,V4 引擎的内部数据结构可能会调整(例如 Qt 6.7 到 6.8)。如果版本不匹配(如源码中定义的 QV4_DATA_STRUCTURE_VERSION 变为 0x42),缓存失效。 |
| Qt Version | quint32 | 编译该缓存文件的 Qt 库版本(如 QT_VERSION)。 | 这是一个极其严格的检查。即使是补丁版本的差异(如 6.5.0 到 6.5.1),通常也会导致缓存失效。这是为了避免因编译器逻辑微调而引入的潜在二进制不兼容。 |
| Source Time Stamp | qint64 | 源文件的最后修改时间(mtime)。 | 引擎对比缓存中记录的时间戳与文件系统中源文件的时间戳。如果源文件更新(时间戳更大),则缓存过期。在某些特殊构建环境(如 NixOS)中,由于时间戳被固定(1970-01-01),这可能引发问题。 |
| Library Version Hash | char | 构建标识哈希(Build Hash)。 | 确保加载缓存的 Qt 库与生成缓存的库具有相同的编译配置(Flags)。如果开发者自行编译 Qt 并开启了特殊选项,标准发布的 Qt 库生成的缓存将无法加载。 |
4.3 数据段与对齐
在头部校验通过后,剩余的部分是实际的编译单元数据。源码中使用了 alignas(16) 指令,这表明数据段在内存中是按照 16 字节边界对齐的。这种对齐对于某些 CPU 架构(如 ARM)的高效访问至关重要,并且有利于 SIMD 指令的潜在优化。
值得注意的是,.qmlc 和 .jsc 文件仅包含 字节码(Bytecode) 和 文档结构。它们 不包含 编译后的原生 C++ 代码(Machine Code)。这意味着即使加载了缓存,代码在运行时仍然运行在 V4 虚拟机之上,或者由 JIT 编译器在运行时进一步处理。这与 qtquickcompiler 生成的 C++ 代码有本质区别。
5. 存储策略与路径解析算法
QML 磁盘缓存在文件系统中的存储位置并非随意选择,而是遵循严格的跨平台标准和哈希命名规则。
5.1 默认存储路径:QStandardPaths
Qt 使用 QStandardPaths::CacheLocation 枚举来获取操作系统的标准缓存目录。这意味着缓存位置会根据运行的操作系统而变化。
以下是各主流平台的默认存储路径映射表:
| 操作系统 | 基础路径 (QStandardPaths::CacheLocation) | QML 缓存子目录结构 | 备注 |
|---|---|---|---|
| Linux / Unix | ~/.cache/<APPNAME> | ~/.cache/<APPNAME>/qmlcache | 遵循 XDG Base Directory 规范。如果设置了 XDG_CACHE_HOME,则优先使用该变量。 |
| Windows | C:\Users\<USER>\AppData\Local\<APPNAME>\cache | …\cache\qmlcache | 位于用户的 AppData\Local 目录下,通常用于存放非漫游(Non-roaming)的临时数据。 |
| macOS | ~/Library/Caches/<APPNAME> | ~/Library/Caches/<APPNAME>/qmlcache | 位于用户库的缓存目录中,是 macOS 应用的标准行为。 |
| Android | /data/user/0/<pkg>/cache 或 /data/data/<pkg>/cache | <AppCacheDir>/qmlcache | 这是一个受保护的内部目录,普通用户无法访问,需 Root 权限或通过 adb 调试工具查看。 |
| iOS | <Sandbox>/Library/Caches | <Sandbox>/Library/Caches/qmlcache | 位于应用的沙盒容器内,系统可能会在存储空间不足时清理该目录。 |
5.2 缓存文件名生成算法
在 qmlcache 目录下,你不会看到 Main.qmlc 这样的文件名。相反,文件名是一串看似随机的十六进制字符串(例如 363465653439…qmlc)。这是为了解决文件名冲突(不同目录下的同名文件)和路径长度限制问题。
根据 qv4executablecompilationunit.cpp 中的实现逻辑 7,文件名的生成过程如下:
- 输入获取:引擎获取源文件的 绝对路径(Absolute File Path)。
- 路径哈希:使用加密哈希算法对绝对路径字符串进行计算。
- 在较新的 Qt 版本(Qt 6+)中,源码显示使用的是 SHA-1 算法:QCryptographicHash fileNameHash(QCryptographicHash::Sha1); fileNameHash.addData(localSourcePath.toUtf8());。
- 在旧版本或某些特定配置中,可能曾使用 MD5。
- 后缀添加:将生成的哈希值转换为十六进制字符串,并根据源文件类型附加后缀(.qml -> .qmlc, .js -> .jsc)。
- 源码逻辑:const QString cacheFileSuffix = QFileInfo(localSourcePath + QLatin1Char(‘c’)).completeSuffix();。
- 结果:最终的文件名形式为 <SHA1-HASH>.qmlc。
关键推论:
- 路径依赖性:由于哈希基于绝对路径,如果将项目文件夹移动到磁盘上的另一个位置,所有文件的绝对路径都会改变,导致哈希值变化。因此,Qt 会认为之前的缓存无效,并生成全新的缓存文件。这也是为什么简单的复制 qmlcache 目录到另一台机器上通常无效的原因,除非两台机器的文件路径完全一致。
- 冲突避免:即使项目中有两个 utils.js,只要它们位于不同的目录,其绝对路径就不同,生成的哈希也不同,因此不会发生缓存覆盖。
5.3 自定义缓存路径
在某些场景下(如嵌入式系统希望将缓存重定向到 /tmp 以减少 Flash 磨损,或 CI 环境),开发者需要更改默认路径。Qt 提供了环境变量 QML_DISK_CACHE_PATH 来实现这一需求。
- 配置示例:export QML_DISK_CACHE_PATH=/tmp/my_custom_qml_cache
- 应用场景:
- 只读文件系统:如果默认的 Home 目录只读,可以将缓存指向 tmpfs(内存盘)。虽然重启后缓存丢失,但能在单次运行期间提升性能。
- 沙盒环境:在严格受限的容器中,指定一个明确可写的路径。
6. 配置、控制与调试接口
Qt 提供了一套丰富的环境变量和 API,允许开发者精细控制磁盘缓存的行为。这对于性能调优、故障排查和开发流程至关重要。
6.1 环境变量详解
下表总结了影响 QML 磁盘缓存的关键环境变量及其作用机制:
| 环境变量 | 选项 / 值 | 功能描述 | 典型应用场景 |
|---|---|---|---|
| QML_DISABLE_DISK_CACHE | 1 或 true | 彻底禁用 磁盘缓存。引擎将不会读取现有缓存,也不会写入新缓存。 | 开发阶段:当你频繁修改 QML 文件并希望立即看到效果(热重载)时,禁用缓存可以避免“修改未生效”的困惑。 故障排查:如果怀疑缓存文件损坏导致 Crash,禁用缓存是验证该猜想的第一步。 |
| QML_FORCE_DISK_CACHE | 1 或 true | 强制启用 磁盘缓存,即使 QML 调试器(Debugger)已连接。 | 默认情况下,连接调试器会禁用缓存以确保调试信息的准确性。此变量用于在调试模式下测试缓存性能,但可能导致断点失效。 |
| QML_DISK_CACHE_PATH | 路径字符串 | 覆盖默认的缓存存储目录。 | 嵌入式部署:重定向到临时分区。 多实例隔离:确保不同应用实例使用不同的缓存目录。 |
| QML_DISK_CACHE | 组合值 (逗号分隔) | 精细控制缓存的读写行为。 | 可选值包括: - qmlc-read: 仅读取,不生成。 - qmlc-write: 仅生成,不读取。 - aot: 启用 AOT 编译单元的加载。 - qmlc: 等同于 qmlc-read,qmlc-write(默认行为)。 |
6.2 编程式控制:清除缓存 API
在 Qt 6 中,QQmlEngine 暴露了更直接的 C++ API 来管理缓存,这对于实现“动态换肤”或“热更新”功能的开发者尤为重要。
- void QQmlEngine::clearComponentCache():
- 功能:清除引擎内部所有已加载组件的缓存数据。这不仅涉及磁盘缓存的句柄,也涉及内存中的类型数据。
- 风险:这是一个破坏性操作。调用此函数后,所有之前加载的组件定义都将失效。如果此时仍有对象实例(Objects)依赖于这些组件,且引擎试图访问其元数据,可能会导致未定义行为。因此,官方建议在调用此函数前,确保没有存活的 QML 对象,或者仅在全量重载 UI 时使用。
- 使用场景:在实现应用内“重新加载界面”功能时,必须调用此函数,否则引擎会继续使用内存中旧的组件定义,即使磁盘上的文件已更新。
- void QQmlEngine::trimComponentCache():
- 功能:清理当前未被使用的组件缓存。
- 区别:相比 clearComponentCache 的全量清除,trim 更加温和,仅回收垃圾内存,类似于垃圾回收机制(GC)。
7. 深度对比:运行时磁盘缓存 vs. 提前编译 (AOT)
在 Qt 6 时代,开发者经常混淆“磁盘缓存”与“Qt Quick Compiler”。虽然两者都旨在提升加载速度,但其技术原理和适用阶段截然不同。
7.1 运行时磁盘缓存 (Runtime Disk Cache)
- 本质:JIT 产物的持久化。
- 工作阶段:应用程序 运行时 (Run-time)。
- 内容:V4 字节码 + 数据结构。
- 优势:
- 零配置:无需修改构建系统,默认开启。
- 灵活性:适用于从网络下载的、用户编写的或任何动态加载的 QML 文件。
- 透明性:对开发者透明,自动处理失效和更新。
- 劣势:
- 首次启动慢:用户第一次打开应用时,必须进行编译并写入缓存,首次体验无加速。
- 仍需解释执行:缓存中的是字节码,运行效率低于原生 C++ 代码。
7.2 Qt Quick Compiler (qmlsc / qmlcachegen)
- 本质:提前编译 (Ahead-of-Time, AOT)。
- 工作阶段:应用程序 构建时 (Build-time)。
- 工具链:
- qmlcachegen:开源版工具。将 QML 编译为字节码,并包装在 C++ 源文件中(作为资源)。这避免了运行时的解析步骤,效果类似于“预填充的磁盘缓存”。
- qmlsc:商业版工具(Qt for Device Creation)。功能更强大,能将部分 QML/JS 逻辑直接编译为 原生 C++ 代码(Native Code)。
- 优势:
- 极致启动速度:数据直接编译进可执行文件(.exe / .so),无文件 I/O,无解析开销。
- 运行时性能:qmlsc 生成的原生代码可以直接调用 C++ 接口(Direct Mode),绕过 QML 属性查找机制,执行效率接近原生 C++。
- 即时生效:用户首次启动即可享受最高性能。
- 劣势:
- 构建复杂性:需要在 CMake 中使用 qt_add_qml_module 并正确配置依赖。
- 静态限制:仅适用于构建时已知的 QML 文件。无法优化运行时动态下载的脚本。
7.3 性能对比总结
| 特性 | 无缓存 (Plain) | 运行时磁盘缓存 (Disk Cache) | AOT (qmlcachegen) | AOT Native (qmlsc) |
|---|---|---|---|---|
| 解析开销 | 高 | 无 (读取缓存) | 无 (编译进二进制) | 无 |
| 首次启动 | 慢 | 慢 (需生成缓存) | 快 | 极快 |
| 二次启动 | 慢 | 快 | 快 | 极快 |
| 代码执行 | 解释/JIT | 解释/JIT | 解释/JIT | 原生机器码 |
| 适用范围 | 所有文件 | 所有文件 | 静态资源 | 静态资源 (商业版) |
工程建议:对于发布的产品,应尽可能使用 CMake 的 qt_add_qml_module 启用 AOT 编译,以确保应用核心部分的性能。运行时磁盘缓存则作为一种补充机制,用于处理那些未被 AOT 覆盖的场景(如插件、动态资源)。
8. 平台差异与跨平台部署考量
尽管 Qt 承诺“一次编写,到处运行”,但在磁盘缓存的具体表现上,不同操作系统的文件系统特性和权限管理带来了显著差异。
8.1 Linux 与嵌入式 Linux
- 路径:遵循 XDG 标准。
- 问题:在构建基于 Yocto 或 Buildroot 的嵌入式系统时,/home/root 或 /home/user 往往是挂载在 Flash 上的。频繁的缓存写入可能会加速 Flash 磨损。
- 策略:建议在生产环境中使用 QML_DISK_CACHE_PATH=/tmp/… 将缓存置于 RAM 中(如果启动速度不是首要瓶颈,而是关注寿命),或者在构建镜像时预热缓存。
- NixOS 特例:NixOS 等为了实现可重现构建(Reproducible Builds),将所有文件的时间戳重置为 1970 年。这直接破坏了基于 mtime 的缓存验证逻辑,导致缓存被反复判定为失效。社区的解决方案通常涉及 Patch Qt 源码,改用内容哈希而非时间戳,或者强制忽略时间戳检查。
8.2 Android
- 沙盒机制:Android 应用的文件系统是严格隔离的。QML 缓存位于私有数据区。
- 清理困难:普通用户无法通过文件管理器删除缓存。如果因为缓存损坏导致应用启动崩溃,用户通常只能通过系统设置中的“清除应用数据”来解决,但这会同时清除用户配置和数据库。因此,Android 应用在启动时捕获异常并自动尝试清理缓存(通过 C++ 代码)是一个健壮的设计模式。
8.3 iOS
- 沙盒与清理:iOS 系统对 Library/Caches 目录有自动管理机制。当设备存储空间不足时,系统可能会静默删除该目录下的文件。Qt 应用必须能够处理“缓存突然消失”的情况(这在 Qt 内部已处理,会自动重新生成)。
- 签名限制:iOS 极其严格的代码签名(Code Signing)机制限制了 JIT 的使用。虽然 QML 磁盘缓存主要存储字节码(不涉及执行权限内存页的动态生成),但在 iOS 上,qmlsc 生成的静态 C++ 代码(AOT)比依赖 JIT 的字节码更具优势,因为 JIT 在 iOS 的某些进程状态下是被禁用的。
9. 故障排查与运维指南
在实际开发与运维中,QML 磁盘缓存可能成为“幽灵 bug”的来源。以下是常见问题及其解决方案。
9.1 典型故障现象
- 修改 QML 无效:修改了代码,部署后界面仍显示旧的效果。
- 原因:时间戳检查失效(例如时钟回拨,或构建系统未更新文件时间),导致引擎加载了旧缓存。
- 解决:设置 QML_DISABLE_DISK_CACHE=1 验证。
- 启动崩溃 (Crash on Startup):
- 原因:缓存文件损坏(写入中断),且引擎的校验逻辑未能拦截(极其罕见);或 Qt 库版本混合使用(如动态链接库版本不一致)。
- 解决:手动删除缓存目录。
- 日志报错 “Magic bytes do not match”:
- 原因:缓存文件头被破坏,或者非缓存文件被错误地放入了缓存目录。
9.2 缓存清理脚本
对于桌面应用,可以在应用更新器(Updater)或卸载器中加入清理逻辑。
Bash (Linux/macOS):
# 这里的 APPNAME 需要替换为实际的应用名称
rm -rf ~/.cache/MyApp/qmlcache
rm -rf ~/Library/Caches/MyApp/qmlcache
C++ (应用内自清理):
C++
#include <QStandardPaths>
#include <QDir>
void clearQmlCache() {
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
QDir qmlCacheDir(cachePath + “/qmlcache”);
if (qmlCacheDir.exists()) {
qmlCacheDir.removeRecursively();
qDebug() << “QML cache cleared from:” << qmlCacheDir.absolutePath();
}
}
这段代码建议在检测到版本更新后的首次启动时运行。
10. 结论与展望
QML 磁盘缓存是 Qt Quick 框架中一个精密而关键的子系统。它在无需开发者干预的情况下,巧妙地利用了文件 I/O 与 CPU 计算之间的性能不对称,显著提升了声明式 UI 的加载性能。
通过本文的深度剖析,我们可以得出以下核心结论:
- 机制透明但可控:虽然它是自动运行的,但通过 QML_DISK_CACHE_PATH 等环境变量和 clearComponentCache 等 API,开发者完全有能力掌控其行为。
- AOT 是未来趋势:随着 Qt 6 对 qmlsc 和 CMake 构建系统的推广,将编译压力前置到构建阶段(Build-time)是必然趋势。磁盘缓存将逐渐退居二线,主要服务于动态加载场景。
- 版本敏感性:缓存对 Qt 版本和文件路径的高度敏感性要求我们在进行 DevOps 实践(如 Docker 镜像构建、OTA 升级)时,必须充分考虑缓存的清理与重建策略。
展望未来,随着 Qt 7 的规划讨论,社区正在探索放宽版本检查限制的可能性,例如移除补丁版本的强校验 9,这将进一步提升缓存的持久性和利用率。对于追求极致性能的开发者而言,深入理解这一机制,是打造流畅、瞬开级用户体验的必修课。
引用的著作
- Qt Quick Compiler | Qt Qml | Qt 6.10.0, 访问时间为 十一月 20, 2025, https://doc.qt.io/qt-6/qtqml-qtquick-compiler-tech.html
- The QML Disk Cache | Qt QML 5.15.1, 访问时间为 十一月 20, 2025, https://qthub.com/static/doc/qt5/qtqml/qmldiskcache.html
- The QML Disk Cache | Qt QML 6.2.0, 访问时间为 十一月 20, 2025, https://thinkinginqt.com/doc/qtqml/qmldiskcache.html
- The QML Disk Cache - Qt Documentation, 访问时间为 十一月 20, 2025, https://doc.qt.io/qt-6/qmldiskcache.html
- Deploying QML Applications | Qt | Qt Documentation (Pro) - Felgo, 访问时间为 十一月 20, 2025, https://felgo.com/doc/qt/qtquick-deployment/
- qv4compileddata.cpp source code [qtdeclarative/src/qml/common/qv4compileddata.cpp] - Codebrowser, 访问时间为 十一月 20, 2025, https://codebrowser.dev/qt6/qtdeclarative/src/qml/common/qv4compileddata.cpp.html
- qv4executablecompilationunit.cpp source code [qtdeclarative/src/qml/jsruntime/qv4executablecompilationunit.cpp] - Codebrowser, 访问时间为 十一月 20, 2025, https://codebrowser.dev/qt5/qtdeclarative/src/qml/jsruntime/qv4executablecompilationunit.cpp.html
- Decompile FILENAME_qml.cpp - Qt Forum, 访问时间为 十一月 20, 2025, https://forum.qt.io/topic/126220/decompile-filename_qml-cpp
- [Development] Regarding QML cache strict version compatibility - Mailing Lists, 访问时间为 十一月 20, 2025, https://lists.qt-project.org/pipermail/development/2025-March/046178.html
- Qt/KDE applications segfault on start due to stale qmlcache · Issue #177720 · NixOS/nixpkgs, 访问时间为 十一月 20, 2025, https://github.com/NixOS/nixpkgs/issues/177720
- QStandardPaths - Qt for Python, 访问时间为 十一月 20, 2025, https://doc.qt.io/qtforpython-6.5/PySide6/QtCore/QStandardPaths.html
- QStandardPaths Class - Qt - Developpez.com, 访问时间为 十一月 20, 2025, https://qt.developpez.com/doc/6.6/qstandardpaths/
- QStandardPaths Class | Qt Core | Qt 6.10.0, 访问时间为 十一月 20, 2025, https://doc.qt.io/qt-6/qstandardpaths.html
- Deploying QML Applications - Qt Documentation, 访问时间为 十一月 20, 2025, https://doc.qt.io/qt-6/qtquick-deployment.html
- qqmltypedata.cpp source code [qtdeclarative/src/qml/qml/qqmltypedata.cpp] - Codebrowser, 访问时间为 十一月 20, 2025, https://codebrowser.dev/qt5/qtdeclarative/src/qml/qml/qqmltypedata.cpp.html
- How to generate md5 of file in Qt? [duplicate] - Stack Overflow, 访问时间为 十一月 20, 2025, https://stackoverflow.com/questions/33197881/how-to-generate-md5-of-file-in-qt
- Cache filename should use hash · Issue #307 · dylanaraps/pywal - GitHub, 访问时间为 十一月 20, 2025, https://github.com/dylanaraps/pywal/issues/307
- The QML Disk Cache - Qt - Developpez.com, 访问时间为 十一月 20, 2025, https://qt.developpez.com/doc/6.0/qmldiskcache/
- 487043 – Extreme stutters/hangs when using certain desktop effects when “~/.cache” is on slow storage - KDE bug tracker, 访问时间为 十一月 20, 2025, https://bugs.kde.org/show_bug.cgi?id=487043
- Can QML caching in Qt 5.8 be disabled for a particular project? - Stack Overflow, 访问时间为 十一月 20, 2025, https://stackoverflow.com/questions/41922581/can-qml-caching-in-qt-5-8-be-disabled-for-a-particular-project
- Disabling QML cache caused unusable desktop: - Development - KDE Discuss, 访问时间为 十一月 20, 2025, https://discuss.kde.org/t/disabling-qml-cache-caused-unusable-desktop/20205
- qtquickcompiler - Qt Forum, 访问时间为 十一月 20, 2025, https://forum.qt.io/topic/125715/qtquickcompiler
- QQmlEngine Class | Qt QML| Felgo Documentation, 访问时间为 十一月 20, 2025, https://felgo.com/doc/qt5/qqmlengine/
- QQmlEngine Class - Qt - Developpez.com, 访问时间为 十一月 20, 2025, https://qt.developpez.com/doc/6.0/qqmlengine/
- Live Reloading or Hot Reloading with QML - QML Guide, 访问时间为 十一月 20, 2025, https://qml.guide/live-reloading-hot-reloading-qml/
- Changing qml style at runtime - c++ - Stack Overflow, 访问时间为 十一月 20, 2025, https://stackoverflow.com/questions/67962752/changing-qml-style-at-runtime
- QQmlEngine Class | Qt Qml | Qt 6.10.0, 访问时间为 十一月 20, 2025, https://doc.qt.io/qt-6/qqmlengine.html
- The Numbers: Performance benefits of the new Qt Quick Compiler, 访问时间为 十一月 20, 2025, https://www.qt.io/blog/the-numbers-performance-benefits-of-the-new-qt-quick-compiler
- Difference between the qtquickcompiler and the new JIT .qmlc cache? - Stack Overflow, 访问时间为 十一月 20, 2025, https://stackoverflow.com/questions/43845523/difference-between-the-qtquickcompiler-and-the-new-jit-qmlc-cache
- QT QML WebView Cache - Stack Overflow, 访问时间为 十一月 20, 2025, https://stackoverflow.com/questions/41698115/qt-qml-webview-cache
- How to set Local Storage path for Android - Stack Overflow, 访问时间为 十一月 20, 2025, https://stackoverflow.com/questions/55305193/how-to-set-local-storage-path-for-android
昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链
更多推荐

所有评论(0)