
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_windows是path_provider联邦插件体系中的 Windows 平台实现包其 README 明确说明它是一个受官方背书endorsed的联邦实现应用方通常不需要手动添加该包只要依赖path_provider它就会在构建 Windows 目标时自动被引入并注册只有当代码直接import它并使用其 API 时才需要在pubspec.yaml中显式声明依赖。该包的 CHANGELOG.md 从 0.0.12 一直记录到 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.0、flutter: 3.38.0说明 NEXT 段描述的就是工作区中正在推进的下一版约束。如果你从 2.3.0 升级到这个新版本构建工具链必须先达到 Flutter 3.38 以上。三、2.x 代际从 win32 依赖走向纯 FFI3.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出实际用到的 APIWin32 API所在 DLL用途SHGetKnownFolderPathshell32.dll通过 KNOWNFOLDERIDGUID查询系统已知文件夹路径GetTempPathWkernel32.dll获取临时目录GetModuleFileNameWkernel32.dll获取当前可执行文件路径用于提取版本信息GetFileVersionInfoSizeW/GetFileVersionInfoWversion.dll加载 EXE 的 VERSIONINFO 版本资源VerQueryValueWversion.dll从版本资源中按键查询字符串如 CompanyNameGetLastErrorkernel32.dll失败时获取 Win32 错误码同时该文件还定义了 Win32 编程中常用的类型别名与常量HRESULT、LPCWSTR、MAX_PATH 260以及 HRESULT 判负函数FAILED(int hr) hr 0和错误常量E_FAIL、E_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 —— 新增缓存目录 APIAdds getApplicationCachePath() for storing app-specific cache files.—— 新增getApplicationCachePath()用于存放应用专属缓存文件。在 lib/src/path_provider_windows_real.dart 中的实现是override FutureString? 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.0Added support for unicode encoded VERSIONINFO支持 Unicode 编码的 VERSIONINFO 资源适配新 analysis options 的小修复。其中 2.1.0 的 Unicode 版本资源支持在源码中有明确落点path_provider_windows_real.dart 定义了三组visibleForTesting常量——语言代码languageEn 0409en-US以及两种编码encodingCP1252 04e4CP1252与encodingUnicode 04b0Unicode_getStringValue会先按 CP1252 查询失败再回退到 Unicode 编码String? _getStringValue(PointerUint8? 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 CP1252、in Unicode以及Unsupported Encoding三个用例的断言。四、2.0.x 代际null safety 迁移与配套修正2.0.6修复library_private_types_in_public_api、sort_child_properties_last、use_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: PathProviderWindows2.0.1 的已知文件夹定位失败时崩溃修复如今体现在getPath的错误处理分支里见下文第五节E_INVALIDARG/E_FAIL才抛异常其它 HRESULT 失败时返回null而不是崩溃。五、0.0.x 初始版本从原型到可用0.0.44更新 Flutter SDK 约束。0.0.43移除未使用的test依赖更新 example 的 Dart SDK 约束。0.0.42将 example 的windows/目录纳入版本控制。0.0.41在 stub 中添加getPath使分析器不再抱怨重写它的 fake改为export folders.dart而非 import因为它是有意暴露的公共 API。0.0.4将真实实现放入条件导入之后对不支持 FFI 的平台导出 stub。修复了项目中对 path_provider 的传递依赖破坏 web 构建的问题。0.0.3为兼容 stable 渠道补上缺失的pluginClass: none。0.0.2README 更新 endorsement 说明变更 getApplicationSupportPath 的存放位置移除 getLibraryPath。0.0.12path_provider for Windows 的初始实现实现了getTemporaryPath、getApplicationSupportPath、getLibraryPath、getApplicationDocumentsPath和getDownloadsPath。其中 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.41 条目中stub 加 getPath让分析器不抱怨的遗留。六、Windows 已知文件夹 GUID 表WindowsKnownFoldergetPath(String folderID)接受的是 KNOWNFOLDERID 的 GUID 字符串这些常量集中定义在 lib/src/folders.dart 的WindowsKnownFolder类中这也是 0.0.41 中被 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};常用的还有Music、Pictures、Videos、Fonts、Profile、ProgramData、Recent、RecycleBinFolder、InternetCache、Cookies、StartMenu、Startup、System、Windows等三十余个。从源码结构看PathProviderWindows对平台接口的五个标准方法都建立在这张表之上override FutureString? getApplicationSupportPath() _createApplicationSubdirectory(WindowsKnownFolder.RoamingAppData); override FutureString? getApplicationDocumentsPath() getPath(WindowsKnownFolder.Documents); override FutureString? getApplicationCachePath() _createApplicationSubdirectory(WindowsKnownFolder.LocalAppData); override FutureString? getDownloadsPath() getPath(WindowsKnownFolder.Downloads);也就是说支持目录 RoamingAppData 下应用专属子目录缓存目录 LocalAppData 下应用专属子目录文档/下载目录直接返回系统已知文件夹本身而临时目录走单独的GetTempPathW路径见下节。七、底层实现剖析五个目录各自如何得到以下实现细节均出自 lib/src/path_provider_windows_real.dart。7.1 临时目录GetTempPath 兜底创建override FutureString? getTemporaryPath() async { final PointerUtf16 buffer callocUint16(MAX_PATH 1).castUtf16(); 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 doesnt. final directory Directory(path); if (!directory.existsSync()) { await directory.create(recursive: true); } return path; } finally { calloc.free(buffer); } }两个值得注意的实现细节一是GetTempPath返回值通常带尾部反斜杠而SHGetKnownFolderPath不带这里主动剥离以保持各方法返回格式一致二是注释明确指出GetTempPath不保证目录存在因此会create(recursive: true)兜底。失败时抛出的是统一的PlatformExceptioncode 为Win32 Errormessage 为十六进制错误码Error code 0x${errorCode.toRadixString(16)}。7.2 任意已知文件夹getPath 与 HRESULT 分支FutureString? getPath(String folderID) { final PointerPointerUtf16 pathPtrPtr callocPointerUtf16(); final PointerGUID knownFolderID callocGUID()..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 FutureString?.value(); // 其它失败情况返回 null } final String path pathPtrPtr.value.toDartString(); return FutureString.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 资源与回退规则getApplicationSupportPath和getApplicationCachePath返回的不是裸的 RoamingAppData/LocalAppData而是其下的应用专属子目录命名遵循 Windows 惯例company-name\product-name。_getApplicationSpecificSubdirectory()的逻辑用GetModuleFileNameW(0, ...)取当前可执行文件路径用GetFileVersionInfoSizeWGetFileVersionInfoW加载 EXE 的 VERSIONINFO 版本资源用VerQueryValueW依次取CompanyName、ProductName先 CP1252 后 Unicode见第三节回退规则公司名缺失则省略该层级产品名缺失则改用可执行文件名去扩展名两个名字都经过_sanitizedDirectoryName清洗把 Win32 文件命名禁用字符[:/\\|?*]替换为_、去掉尾部空白与句点、并截断到 255 字符Windows 路径分量长度上限清洗后为空则视为缺失。组装时_createApplicationSubdirectory还会保证目录实际存在但若拼接后的路径长度超过MAX_PATH260定义于 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.44更新约束见 0.0.x 条目—初始阶段2.1.32.10—win32 3.x 兼容2.1.43.0—仓库合并后链接更新2.1.73.32.18win32 5.x 兼容2.2.13.72.19pub topics2.3.03.163.2FFI 替换 win32NEXT3.383.10当前工作区约束九、使用建议常规应用直接依赖path_provider即可本包作为 endorsed 联邦实现自动生效无需写入pubspec.yaml见 README。需要任意已知文件夹直接依赖本包PathProviderWindows().getPath(WindowsKnownFolder.Desktop)之类可取到表内任意 GUID 对应目录也可以自行查阅folders.dart中每条属性的文档注释确认语义与典型路径。注意目录保证存在的差异临时目录、支持目录、缓存目录在路径不超过 MAX_PATH 时会确保目录创建文档与下载目录则原样返回系统目录不额外创建子目录。web 与跨平台兼容条件导入 stub0.0.4 引入保证把本包放进跨平台依赖图不会破坏 web 编译stub 上的assert(false)意味着若在 web 上真正实例化会立即失败。升级 2.3.0 及以上确认工具链满足对应 SDK 门槛2.3.0 起为 Flutter 3.16/Dart 3.2NEXT 为 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.dartweb/非 FFI 平台 stublib/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),仅供参考