path_provider_windows 版本演进与 Windows 已知文件夹获取实现详解
2026/9/18 13:25:57 网站建设 项目流程

path_provider_windows 版本演进与 Windows 已知文件夹获取实现详解

【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages

本文以path_provider_windows的 CHANGELOG.md 为主线,完整梳理该包从 0.0.1 到 2.3.0(含未发布的 NEXT)的版本演进、每一轮 SDK 最低版本要求与关键依赖变更的原因,并结合仓库中的实际源码(FFI 封装、GUID 常量表、条件导入 stub、测试用例),讲解 Windows 平台应用目录(临时目录、支持目录、文档目录、下载目录、缓存目录)究竟是如何从 Win32 API 中解析出来的。读完后你既能准确理解每个版本号的变更含义,也能掌握在 Windows 端获取应用专属存储目录的完整技术链路。

一、这个包是什么,CHANGELOG 记录了什么

path_provider_windowspath_provider联邦插件体系中的 Windows 平台实现包,其 README 明确说明它是一个受官方背书(endorsed)的联邦实现:应用方通常不需要手动添加该包,只要依赖path_provider,它就会在构建 Windows 目标时自动被引入并注册;只有当代码直接import它并使用其 API 时,才需要在pubspec.yaml中显式声明依赖。

该包的 CHANGELOG.md 从 0.0.1+2 一直记录到 2.3.0,并保留了顶部的NEXT段,完整覆盖了以下技术节点:初始实现、null safety 迁移、对package:win32多代版本的兼容性适配、以直接 FFI 调用替换win32依赖、新增缓存目录 API、pub 元数据(topics)补充,以及 Flutter/Dart 最低 SDK 约束的历次上调。下面按版本代际完整解读。

二、NEXT:下一个未发布版本

CHANGELOG 顶部的NEXT段记录了尚未发布的变更:

  • Updates minimum supported SDK version to Flutter 3.38/Dart 3.10.—— 将最低支持的 SDK 版本提升至 Flutter 3.38 / Dart 3.10。

这一点与当前 pubspec.yaml 中的环境约束完全一致(sdk: ^3.10.0flutter: ">=3.38.0"),说明 NEXT 段描述的就是工作区中正在推进的下一版约束。如果你从 2.3.0 升级到这个新版本,构建工具链必须先达到 Flutter 3.38 以上。

三、2.x 代际:从 win32 依赖走向纯 FFI

3.1 2.3.0 —— 用直接 FFI 替换 win32 依赖

  • Replaceswin32dependency with direct FFI usage.—— 不再依赖第三方package:win32,改为直接用dart:ffi封装所需的 Win32 API。
  • Updates minimum supported SDK version to Flutter 3.16/Dart 3.2.—— 最低 SDK 提升至 Flutter 3.16 / Dart 3.2。

这是该包架构上最重要的一次变更。仓库源码印证了这一事实:lib/src/win32_wrappers.dart 中没有任何对package:win32的导入,而是自行定义了三组DynamicLibrary.open与函数绑定:

final DynamicLibrary _dllKernel32 = DynamicLibrary.open('kernel32.dll'); final DynamicLibrary _dllVersion = DynamicLibrary.open('version.dll'); final DynamicLibrary _dllShell32 = DynamicLibrary.open('shell32.dll');

并逐个lookupFunction出实际用到的 API:

Win32 API所在 DLL用途
SHGetKnownFolderPathshell32.dll通过 KNOWNFOLDERID(GUID)查询系统已知文件夹路径
GetTempPathWkernel32.dll获取临时目录
GetModuleFileNameWkernel32.dll获取当前可执行文件路径(用于提取版本信息)
GetFileVersionInfoSizeW/GetFileVersionInfoWversion.dll加载 EXE 的 VERSIONINFO 版本资源
VerQueryValueWversion.dll从版本资源中按键查询字符串(如 CompanyName)
GetLastErrorkernel32.dll失败时获取 Win32 错误码

同时该文件还定义了 Win32 编程中常用的类型别名与常量:HRESULTLPCWSTRMAX_PATH = 260,以及 HRESULT 判负函数FAILED(int hr) => hr < 0和错误常量E_FAILE_INVALIDARG。去掉win32依赖的好处是:本包只需链接它真正用到的 6 个 API,避免了为整个包引入一个覆盖数百个 API 的重型依赖,也消除了后续因win32大版本升级导致的兼容性问题(正如 2.1.6/2.1.7 那样反复打补丁)。

3.2 2.2.1 —— pub topics 与 SDK 约束

  • Adds pub topics to package metadata.—— 在 pub 元数据中添加主题标签。当前 pubspec.yaml 中可以看到topics: [files, path-provider, paths],用于 pub 站点的检索分类。
  • Updates minimum supported SDK version to Flutter 3.7/Dart 2.19.—— 最低 SDK 提升至 Flutter 3.7 / Dart 2.19。

3.3 2.2.0 —— 新增缓存目录 API

  • Adds getApplicationCachePath() for storing app-specific cache files.—— 新增getApplicationCachePath(),用于存放应用专属缓存文件。

在 lib/src/path_provider_windows_real.dart 中的实现是:

@override Future<String?> getApplicationCachePath() => _createApplicationSubdirectory(WindowsKnownFolder.LocalAppData);

即缓存目录建立在LocalAppData(不随用户漫游的本地应用数据目录,典型路径C:\Users\<user>\AppData\Local)下的应用专属子目录中,这与 RoamingAppData 上的支持目录形成"本地 vs 漫游"的互补分工。

3.4 2.1.x —— 与 win32 各代版本的兼容史

这一段是"依赖package:win32时代"的兼容记录,读起来就是一部 win32 包版本跟进史:

  • 2.1.7:添加对win325.x 的兼容;最低 SDK 提升至 Flutter 3.3 / Dart 2.18。
  • 2.1.6:添加对win324.x 的兼容。
  • 2.1.5:澄清 README 中关于 endorsement(官方背书)的说明;对齐 Dart 与 Flutter 的 SDK 约束。
  • 2.1.4:更新仓库由 flutter/plugins 合并入 flutter/packages 后的链接;最低 Flutter 版本提升至 3.0。
  • 2.1.3:最低 Flutter 版本提升至 2.10;添加对package:win323.x 的兼容。
  • 2.1.2:修复avoid_redundant_argument_valueslint 警告与少量笔误。
  • 2.1.1:将package:win32依赖版本提升至 2.1.0。
  • 2.1.0:升级package:ffi依赖到 2.0.0;Added support for unicode encoded VERSIONINFO(支持 Unicode 编码的 VERSIONINFO 资源);适配新 analysis options 的小修复。

其中 2.1.0 的 Unicode 版本资源支持在源码中有明确落点:path_provider_windows_real.dart 定义了三组@visibleForTesting常量——语言代码languageEn = '0409'(en-US),以及两种编码encodingCP1252 = '04e4'(CP1252)与encodingUnicode = '04b0'(Unicode);_getStringValue先按 CP1252 查询,失败再回退到 Unicode 编码

String? _getStringValue(Pointer<Uint8>? infoBuffer, String key) => versionInfoQuerier.getStringValue(infoBuffer, key, language: languageEn, encoding: encodingCP1252) ?? versionInfoQuerier.getStringValue(infoBuffer, key, language: languageEn, encoding: encodingUnicode);

这个"双编码回退"策略正是 2.1.0 变更点的实现形态,对应测试文件 test/path_provider_windows_test.dart 中getApplicationSupportPath with full version info in CP1252in Unicode以及Unsupported Encoding三个用例的断言。

四、2.0.x 代际:null safety 迁移与配套修正

  • 2.0.6:修复library_private_types_in_public_apisort_child_properties_lastuse_key_in_widget_constructors三个 lint 警告。
  • 2.0.5:移除对meta的依赖。
  • 2.0.4:从 pubspec 中移除已废弃的pluginClass: none
  • 2.0.3:更新 README 中的安装说明。
  • 2.0.2:在 pubspec.yaml 中添加implements声明;在 Dart 主类中添加registerWith()方法。
  • 2.0.1:修复当某个已知文件夹无法定位时的崩溃问题。
  • 2.0.0Migrate to null safety(迁移到 null safety)

其中 2.0.2 的implements声明对应现在 pubspec.yaml 中的插件声明结构,也是"endorsed 联邦插件"能被path_provider自动发现的关键:

flutter: plugin: implements: path_provider platforms: windows: dartPluginClass: PathProviderWindows

2.0.1 的"已知文件夹定位失败时崩溃"修复,如今体现在getPath的错误处理分支里(见下文第五节):E_INVALIDARG/E_FAIL才抛异常,其它 HRESULT 失败时返回null而不是崩溃。

五、0.0.x 初始版本:从原型到可用

  • 0.0.4+4:更新 Flutter SDK 约束。
  • 0.0.4+3:移除未使用的test依赖;更新 example 的 Dart SDK 约束。
  • 0.0.4+2:将 example 的windows/目录纳入版本控制。
  • 0.0.4+1:在 stub 中添加getPath,使分析器不再抱怨重写它的 fake;改为export 'folders.dart'(而非 import),因为它是有意暴露的公共 API。
  • 0.0.4将真实实现放入条件导入之后,对不支持 FFI 的平台导出 stub。修复了项目中对 path_provider 的传递依赖破坏 web 构建的问题。
  • 0.0.3:为兼容 stable 渠道补上缺失的pluginClass: none
  • 0.0.2:README 更新 endorsement 说明;变更 getApplicationSupportPath 的存放位置;移除 getLibraryPath。
  • 0.0.1+2:path_provider for Windows 的初始实现,实现了getTemporaryPathgetApplicationSupportPathgetLibraryPathgetApplicationDocumentsPathgetDownloadsPath

其中 0.0.4 的条件导入设计是理解本包工程结构的关键,其入口文件 lib/path_provider_windows.dart 只有两行核心导出:

export 'src/folders_stub.dart' if (dart.library.ffi) 'src/folders.dart'; export 'src/path_provider_windows_stub.dart' if (dart.library.ffi) 'src/path_provider_windows_real.dart';

原理是:path_provider需要在代码层面手动注册各平台实现,因此任何传递依赖path_provider的包都会同时(在 pubspec 和代码层面)依赖到本包。若不做条件导入,web 目标在编译时会走到使用dart:ffi的真实实现而报错。为此仓库提供了 lib/src/path_provider_windows_stub.dart,其构造函数带assert(false),注释明确写着"仅用于满足编译期依赖,绝不应被真正创建",并保留了一个空实现的getPath方法——这正是 0.0.4+1 条目中"stub 加 getPath,让分析器不抱怨"的遗留。

六、Windows 已知文件夹 GUID 表:WindowsKnownFolder

getPath(String folderID)接受的是 KNOWNFOLDERID 的 GUID 字符串,这些常量集中定义在 lib/src/folders.dart 的WindowsKnownFolder类中(这也是 0.0.4+1 中被 export 为公共 API 的文件)。该表覆盖了 Windows 文档中的绝大多数常用已知文件夹,每个属性都附带了官方语义说明,例如:

/// The file system directory that serves as a data repository for local /// (nonroaming) applications. A typical path is /// C:\Documents and Settings\username\Local Settings\Application Data. static String get LocalAppData => '{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}'; /// ...application-specific data... static String get RoamingAppData => '{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}'; static String get Documents => '{FDD39AD0-238F-46AF-ADB4-6C85480369C7}'; static String get Downloads => '{374DE290-123F-4565-9164-39C4925E467B}'; static String get Desktop => '{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}'; static String get ProgramFiles => '{905e63b6-c1bf-494e-b29c-65b732d3d21a}';

常用的还有MusicPicturesVideosFontsProfileProgramDataRecentRecycleBinFolderInternetCacheCookiesStartMenuStartupSystemWindows等三十余个。从源码结构看,PathProviderWindows对平台接口的五个标准方法都建立在这张表之上:

@override Future<String?> getApplicationSupportPath() => _createApplicationSubdirectory(WindowsKnownFolder.RoamingAppData); @override Future<String?> getApplicationDocumentsPath() => getPath(WindowsKnownFolder.Documents); @override Future<String?> getApplicationCachePath() => _createApplicationSubdirectory(WindowsKnownFolder.LocalAppData); @override Future<String?> getDownloadsPath() => getPath(WindowsKnownFolder.Downloads);

也就是说:支持目录 = RoamingAppData 下应用专属子目录,缓存目录 = LocalAppData 下应用专属子目录,文档/下载目录直接返回系统已知文件夹本身,而临时目录走单独的GetTempPathW路径(见下节)。

七、底层实现剖析:五个目录各自如何得到

以下实现细节均出自 lib/src/path_provider_windows_real.dart。

7.1 临时目录:GetTempPath + 兜底创建

@override Future<String?> getTemporaryPath() async { final Pointer<Utf16> buffer = calloc<Uint16>(MAX_PATH + 1).cast<Utf16>(); String path; try { final int length = GetTempPath(MAX_PATH, buffer); if (length == 0) { final int error = GetLastError(); throw _createWin32Exception(error); } else { path = buffer.toDartString(); // GetTempPath adds a trailing backslash, but SHGetKnownFolderPath does // not. Strip off trailing backslash for consistency with other methods. if (path.endsWith(r'\')) { path = path.substring(0, path.length - 1); } } // Ensure that the directory exists, since GetTempPath doesn't. final directory = Directory(path); if (!directory.existsSync()) { await directory.create(recursive: true); } return path; } finally { calloc.free(buffer); } }

两个值得注意的实现细节:一是GetTempPath返回值通常带尾部反斜杠,而SHGetKnownFolderPath不带,这里主动剥离以保持各方法返回格式一致;二是注释明确指出GetTempPath不保证目录存在,因此会create(recursive: true)兜底。失败时抛出的是统一的PlatformException,code 为'Win32 Error',message 为十六进制错误码('Error code 0x${errorCode.toRadixString(16)}')。

7.2 任意已知文件夹:getPath 与 HRESULT 分支

Future<String?> getPath(String folderID) { final Pointer<Pointer<Utf16>> pathPtrPtr = calloc<Pointer<Utf16>>(); final Pointer<GUID> knownFolderID = calloc<GUID>()..ref.parse(folderID); try { final int hr = SHGetKnownFolderPath(knownFolderID, KF_FLAG_DEFAULT, NULL, pathPtrPtr); if (FAILED(hr)) { if (hr == E_INVALIDARG || hr == E_FAIL) { throw _createWin32Exception(hr); } return Future<String?>.value(); // 其它失败情况返回 null } final String path = pathPtrPtr.value.toDartString(); return Future<String>.value(path); } finally { calloc.free(pathPtrPtr); calloc.free(knownFolderID); } }

调用链是:GUID 字符串经guid.dart中的parse解析为GUID结构体,再以KF_FLAG_DEFAULT标志调用SHGetKnownFolderPath。错误策略区分三类:E_INVALIDARG(参数非法,如 GUID 格式错误)和E_FAIL抛异常,其它失败返回null——这正是 2.0.1 修复"已知文件夹定位失败时崩溃"后的稳健形态。

7.3 应用专属子目录:VERSIONINFO 资源与回退规则

getApplicationSupportPathgetApplicationCachePath返回的不是裸的 RoamingAppData/LocalAppData,而是其下的应用专属子目录,命名遵循 Windows 惯例company-name\product-name_getApplicationSpecificSubdirectory()的逻辑:

  1. GetModuleFileNameW(0, ...)取当前可执行文件路径;
  2. GetFileVersionInfoSizeW+GetFileVersionInfoW加载 EXE 的 VERSIONINFO 版本资源;
  3. VerQueryValueW依次取CompanyNameProductName(先 CP1252 后 Unicode,见第三节);
  4. 回退规则:公司名缺失则省略该层级;产品名缺失则改用可执行文件名(去扩展名)
  5. 两个名字都经过_sanitizedDirectoryName清洗:把 Win32 文件命名禁用字符[<>:"/\\|?*]替换为_、去掉尾部空白与句点、并截断到 255 字符(Windows 路径分量长度上限),清洗后为空则视为缺失。

组装时_createApplicationSubdirectory还会保证目录实际存在,但若拼接后的路径长度超过MAX_PATH(260,定义于 win32_wrappers.dart)则跳过创建,把处理交给调用方——注释里提示可用"短路径"等方案自行应对。

7.4 可测试性设计:VersionInfoQuerier 注入

VersionInfoQuerier类(@visibleForTesting)单独封装了VerQueryValue调用,注释说明其目的:"允许在测试中注入替代元数据,而无需构建多个自定义测试二进制"。PathProviderWindows暴露了@visibleForTesting VersionInfoQuerier versionInfoQuerier字段供替换。test/path_provider_windows_test.dart 正是利用这一点用FakeVersionInfoQuerier验证了各种场景:无版本信息时路径以可执行名flutter_tester结尾、CP1252/Unicode 编码下得到AppData\Roaming\A Company\Amazing App且目录真实存在、不支持的编码回退到可执行名、缺少公司名时只保留产品名、含非法字符的名字被正确清洗等(这些用例大多带skip: !Platform.isWindows,需在实际 Windows 环境运行)。

八、版本升级路径速查

把 CHANGELOG 中历次 SDK 约束变更纵向排列,可以清楚看到升级门槛的演进:

版本Flutter 最低要求Dart 最低要求关键点
0.0.4+4(更新约束,见 0.0.x 条目)初始阶段
2.1.32.10win32 3.x 兼容
2.1.43.0仓库合并后链接更新
2.1.73.32.18win32 5.x 兼容
2.2.13.72.19pub topics
2.3.03.163.2FFI 替换 win32
NEXT3.383.10当前工作区约束

九、使用建议

  1. 常规应用:直接依赖path_provider即可,本包作为 endorsed 联邦实现自动生效,无需写入pubspec.yaml(见 README)。
  2. 需要任意已知文件夹:直接依赖本包,PathProviderWindows().getPath(WindowsKnownFolder.Desktop)之类可取到表内任意 GUID 对应目录;也可以自行查阅folders.dart中每条属性的文档注释确认语义与典型路径。
  3. 注意目录"保证存在"的差异:临时目录、支持目录、缓存目录在路径不超过 MAX_PATH 时会确保目录创建;文档与下载目录则原样返回系统目录,不额外创建子目录。
  4. web 与跨平台兼容:条件导入 stub(0.0.4 引入)保证把本包放进跨平台依赖图不会破坏 web 编译;stub 上的assert(false)意味着若在 web 上真正实例化会立即失败。
  5. 升级 2.3.0 及以上:确认工具链满足对应 SDK 门槛(2.3.0 起为 Flutter 3.16/Dart 3.2;NEXT 为 Flutter 3.38/Dart 3.10)。由于 2.3.0 已不再依赖win32,历史上为 win32 各代版本做的兼容适配(2.1.1–2.1.7)对新版不再适用。

十、相关文件索引

  • 变更历史(本文主线):CHANGELOG.md
  • 包描述与 endorsed 使用说明:README.md、pubspec.yaml
  • 条件导入入口:lib/path_provider_windows.dart
  • 真实实现(FFI 调用、VERSIONINFO 解析、目录创建):lib/src/path_provider_windows_real.dart
  • 已知文件夹 GUID 常量表:lib/src/folders.dart
  • 原生类型与 Win32 函数绑定:lib/src/win32_wrappers.dart
  • web/非 FFI 平台 stub:lib/src/path_provider_windows_stub.dart
  • 单元测试:test/path_provider_windows_test.dart、test/guid_test.dart
  • 平台集成测试:example/integration_test/path_provider_test.dart

【免费下载链接】packagesA collection of useful packages maintained by the Flutter team项目地址: https://gitcode.com/GitHub_Trending/pac/packages

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询