# flutter跨平台音乐 **Repository Path**: ruirui-study/flutter-music-pure ## Basic Information - **Project Name**: flutter跨平台音乐 - **Description**: flutter开发的跨平台音乐,目前适配了安卓端和windows端,纯净无广告,支持灵动岛、歌词解析等 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-05-22 - **Last Updated**: 2026-05-22 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 胖虎音乐 — Code Wiki > 版本:1.1.3 | 框架:Flutter 3.x + Dart | 平台:Android & Windows --- ## 一、项目概览 胖虎音乐是一款基于 Flutter 开发的**本地音乐播放器**,支持 Android 和 Windows 双平台。核心特性包括本地音乐扫描、播放控制、歌词显示、收藏管理、听歌统计、主题切换、图标更换、在线更新等。 ### 技术栈 | 类别 | 技术 | 说明 | |------|------|------| | 框架 | Flutter 3.x + Dart | 跨平台 UI 框架 | | 音频-Android | just_audio + audio_service | 支持后台播放、灵动岛、通知栏控制 | | 音频-Windows | audioplayers (miniaudio) | 轻量级,约200KB,避免 just_audio_windows 线程安全 Bug | | 状态管理 | Provider + ChangeNotifier | 轻量级响应式状态管理 | | 主题 | Material 3 + ColorScheme.fromSeed | 动态主题色 | | 持久化 | shared_preferences | 轻量级 KV 存储 | | 文件操作 | path_provider | 临时目录/应用目录 | | 系统托盘 | tray_manager + window_manager | Windows 托盘图标和窗口管理 | | 更新 | Gitee Releases API + http | 在线检查更新和下载 | | GBK 解码 | dart:ffi (Windows) + charset_converter (Android) | 原生系统 API 解码 GBK | | 权限 | permission_handler | Android 存储权限/音频权限 | | 快捷方式 | quick_actions | Android 长按图标快捷操作 | | 图标切换 | MethodChannel + activity-alias | Android 原生 PackageManager | --- ## 二、目录结构 ``` ali_plan_glm_music_copy/ ├── android/ # Android 原生工程 │ └── app/src/main/ │ ├── kotlin/com/arui/ali_plan_glm_music/ │ │ ├── MainActivity.kt # 主 Activity,注册 MethodChannel │ │ ├── IconMethodCallHandler.kt # 图标切换原生处理器 │ │ └── MainApplication.kt # Application 类 │ ├── res/ # Android 资源(图标、布局等) │ └── AndroidManifest.xml # 清单文件(含 activity-alias) ├── assets/ │ └── icons/ # 应用图标资源(.ico/.jpg/.png) ├── lib/ # Dart 源码(核心代码) │ ├── main.dart # 应用入口,初始化 AudioService/窗口/Provider │ ├── models/ │ │ └── song_model.dart # SongInfo 数据模型 + PlayMode 枚举 │ ├── providers/ │ │ └── music_player_provider.dart # 全局状态管理(播放/扫描/收藏/统计) │ ├── screens/ │ │ ├── home_screen.dart # 主界面(音乐列表+播放控制+搜索) │ │ ├── favorites_screen.dart # 收藏列表页 │ │ ├── lyrics_screen.dart # 歌词页(自动滚动+手动定位) │ │ ├── settings_screen.dart # 设置页(主题/图标/缓存/统计/更新) │ │ ├── listening_stats_screen.dart # 听歌统计页(今日/本周/本月) │ │ ├── version_manage_screen.dart # 版本管理页(检查更新+下载安装) │ │ └── about_screen.dart # 关于页(WebView 加载掘金主页) │ ├── services/ │ │ ├── audio_handler.dart # 音频处理器(just_audio/WindowsAudioPlayer 适配) │ │ ├── windows_audio_player.dart # Windows 专用播放器(audioplayers) │ │ ├── music_scanner.dart # 本地音乐扫描服务(两阶段策略) │ │ ├── favorites_service.dart # 收藏持久化服务 │ │ ├── listening_stats_service.dart # 听歌统计服务 │ │ ├── theme_service.dart # 主题色配置服务 │ │ ├── app_icon_service.dart # 应用图标切换服务(MethodChannel) │ │ ├── update_service.dart # 在线更新服务(Gitee API) │ │ └── system_tray_manager.dart # Windows 系统托盘管理器 │ ├── utils/ │ │ ├── audio_metadata_extractor.dart # MP3/OGG 元数据提取(纯 Dart) │ │ ├── audio_duration_calculator.dart # 音频时长计算(纯 Dart) │ │ ├── windows_gbk_decoder.dart # Windows GBK 解码器(dart:ffi) │ │ ├── gbk_decoder.dart # GBK 解码回退方案 │ │ ├── permission_helper.dart # 权限请求工具 │ │ ├── platform_quick_actions.dart # Android 快捷方式注册 │ │ ├── app_icon_generator.dart # 运行时图标图片生成器 │ │ └── default_artwork_generator.dart # 默认封面图生成器 │ └── widgets/ │ ├── music_list.dart # 音乐列表组件 │ ├── music_list_item.dart # 音乐列表项(左滑删除+收藏) │ └── player_controls.dart # 底部播放控制组件 ├── windows/ # Windows 原生工程 │ └── runner/ # C++ 层窗口配置 ├── web/ # Web 平台资源(暂未使用) ├── others/ │ └── screenshot/ # 应用截图 ├── pubspec.yaml # Flutter 依赖配置 └── analysis_options.yaml # Dart 静态分析配置 ``` --- ## 三、架构设计 ### 3.1 整体架构图 ``` ┌─────────────────────────────────────────────────────┐ │ UI 层 (Screens/Widgets) │ │ HomeScreen / FavoritesScreen / LyricsScreen / ... │ └──────────────────────┬──────────────────────────────┘ │ Consumer ▼ ┌─────────────────────────────────────────────────────┐ │ 状态管理层 (Provider) │ │ MusicPlayerProvider │ │ ┌─────────┐ ┌──────────┐ ┌──────────────────┐ │ │ │ 播放状态 │ │ 扫描状态 │ │ 收藏/统计/搜索 │ │ │ └────┬────┘ └────┬─────┘ └────────┬─────────┘ │ └───────┼───────────┼────────────────┼───────────────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────┐ │ 服务层 (Services) │ │ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │ │ │ AudioHandler │ │ MusicScanner │ │ Favorites │ │ │ │ (just_audio/ │ │ (两阶段扫描) │ │ Service │ │ │ │ Windows) │ │ │ │ │ │ │ └──────┬───────┘ └──────┬───────┘ └───────────┘ │ │ │ │ │ │ ┌──────┴───────┐ ┌──────┴───────────────────┐ │ │ │WindowsAudio │ │ AudioMetadataExtractor │ │ │ │Player │ │ AudioDurationCalculator │ │ │ │(audioplayers)│ │ WindowsGbkDecoder │ │ │ └──────────────┘ └──────────────────────────┘ │ └─────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────┐ │ 平台层 (Platform) │ │ Android: AudioService + MethodChannel │ │ Windows: audioplayers + tray_manager + dart:ffi │ └─────────────────────────────────────────────────────┘ ``` ### 3.2 数据流 ``` 用户操作 → Screen/Widget → MusicPlayerProvider (ChangeNotifier) → Service 层执行业务逻辑 → Provider.notifyListeners() → UI 自动重建(Consumer/Provider.of) ``` ### 3.3 平台适配策略 | 功能 | Android | Windows | |------|---------|---------| | 音频播放 | just_audio + audio_service | audioplayers (miniaudio) | | 后台播放 | ✅ AudioService 通知栏 | ❌ 不支持 | | 灵动岛/媒体中心 | ✅ MediaItem + PlaybackState | ❌ 不支持 | | 系统托盘 | ❌ | ✅ tray_manager | | 窗口管理 | ❌ | ✅ window_manager | | 图标切换 | ✅ activity-alias (MethodChannel) | ❌ | | GBK 解码 | charset_converter (系统 API) | dart:ffi → MultiByteToWideChar | | 快捷方式 | ✅ quick_actions | ❌ | | 权限请求 | ✅ permission_handler | 直接返回 true | --- ## 四、核心模块详解 ### 4.1 应用入口 — [main.dart](lib/main.dart) **职责**:初始化平台服务、创建 Provider、启动应用 **关键流程**: 1. `WidgetsFlutterBinding.ensureInitialized()` 2. Android 端锁定竖屏方向 3. Android 端初始化 `AudioService`(通知栏/灵动岛支持) 4. Windows 端直接创建 `MyAudioHandler`(不需要 AudioService) 5. 创建 `MusicPlayerProvider` 并注入 `AudioHandler` 6. Windows 端初始化 `SystemTrayManager`(托盘+窗口管理) 7. `runApp()` 启动应用,Provider 包裹根 Widget **主题切换机制**: - `globalChangeTheme` 全局回调,设置页通过此回调通知 `MyApp` 切换主题色 - `ThemeService` 持久化存储主题色 key - 使用 `ColorScheme.fromSeed` 生成完整 Material 3 主题 ### 4.2 数据模型 — [song_model.dart](lib/models/song_model.dart) **SongInfo** — 歌曲信息数据模型 | 字段 | 类型 | 说明 | |------|------|------| | id | int | 歌曲唯一标识(filePath.hashCode) | | title | String | 歌曲标题 | | artist | String | 歌手名 | | duration | int | 时长(毫秒) | | path | String | 文件绝对路径 | | albumId | int? | 专辑 ID(暂未使用) | | artworkPath | String? | 封面图缓存路径 | **PlayMode** — 播放模式枚举 - `sequential`:顺序播放 - `loopSingle`:单曲循环 **序列化**:`encodeList` / `decodeList` 使用 JSON 格式,存储到 SharedPreferences ### 4.3 状态管理 — [music_player_provider.dart](lib/providers/music_player_provider.dart) **MusicPlayerProvider** 是整个应用的核心状态管理类,继承 `ChangeNotifier`。 **管理状态**: - `_songs`:全部歌曲列表 - `_favoriteIds`:收藏歌曲 ID 集合 - `_isScanning` / `_scannedCount`:扫描状态 - `_hasPermission` / `_isPermissionPermanentlyDenied`:权限状态 - `_searchQuery`:搜索关键词 - `_appTitle`:自定义应用名称 - `_statsEnabled`:统计开关 - `_playStartTime` / `_playingSongId`:当前播放统计 **核心方法**: | 方法 | 说明 | |------|------| | `scanMusic()` | 扫描本地音乐,请求权限 → 清缓存 → 扫描 → 通知 | | `playSong(filteredIndex)` | 播放筛选列表中的歌曲,映射到主列表索引 | | `shufflePlay()` | 随机播放,使用时间戳取模选择随机索引 | | `toggleFavorite(songId)` | 切换收藏状态 | | `deleteSong(songId)` | 删除歌曲(从列表+缓存移除,若正在播放则跳下一首) | | `setAppTitle(title)` | 修改应用名称(最多7字) | | `setStatsEnabled(enabled)` | 开关听歌统计 | **播放统计机制**: - 播放开始时记录 `_playStartTime`,启动 30 秒定时器定期保存 - 播放停止/切歌时保存统计 - 只记录播放时长 ≥ 5 秒的记录 - 增量保存:每次保存后更新 `_playStartTime`,避免重复计算 ### 4.4 音频处理 — [audio_handler.dart](lib/services/audio_handler.dart) **MyAudioHandler** 继承 `BaseAudioHandler with SeekHandler`,是音频播放的核心控制器。 **双平台适配**: - Android:使用 `just_audio` 的 `AudioPlayer` - Windows:使用自定义的 `WindowsAudioPlayer`(基于 audioplayers) **关键功能**: - `setPlaylist()`:设置播放列表并开始播放 - `skipToNext()` / `skipToPrevious()`:切歌(循环列表) - `togglePlayMode()`:切换播放模式 - `refreshDefaultArtwork()`:图标切换后更新通知栏封面 - `_updateFavoriteState()`:切歌时更新收藏状态 **封面图策略**: 1. 有 `artworkPath` → 使用歌曲封面 2. 无封面 → 使用当前 APP 图标作为默认封面 3. 图标切换时自动更新默认封面 **播放完成处理**: - 单曲循环 → `seek(Duration.zero)` + `play()` - 顺序播放 → `skipToNext()` ### 4.5 Windows 播放器 — [windows_audio_player.dart](lib/services/windows_audio_player.dart) **为什么不用 just_audio_windows?** - just_audio_windows 存在线程安全 Bug - media_kit 体积过大(libmpv 约 15MB) - audioplayers 使用 miniaudio(约 200KB),轻量且稳定 **事件监听**: - `onPositionChanged`:更新播放位置 - `onDurationChanged`:更新总时长 - `onPlayerStateChanged`:更新播放/暂停状态 - `onPlayerComplete`:播放完成,根据模式切歌或循环 ### 4.6 音乐扫描 — [music_scanner.dart](lib/services/music_scanner.dart) **两阶段扫描策略**: ``` 阶段1:快速扫描(同步返回歌曲列表) ├─ 发现音频文件(递归遍历目录,深度≤5) ├─ 提取基本信息(标题、歌手、时长) └─ 跳过封面提取 阶段2:后台封面提取(不阻塞 UI) ├─ 分批处理(每批3首,避免内存峰值) ├─ 提取封面写入临时目录 └─ 完成后更新缓存 ``` **扫描目录**: - Android:`/storage/emulated/0` 下的 Music/Download/Recordings 等目录 - Windows:用户目录下的 Music/Downloads/Desktop/Documents + 各盘符的 Music/Downloads **目录过滤**:跳过 `android/cache/temp/.thumbnails/windows/program files` 等系统目录 **缓存机制**: - 使用 SharedPreferences 存储扫描结果(JSON 格式) - 加载缓存时检查文件是否存在,不存在则标记 `hasPruned` - 有文件缺失时自动触发重新扫描 **支持的音频格式**:`.mp3`、`.ogg` ### 4.7 元数据提取 — [audio_metadata_extractor.dart](lib/utils/audio_metadata_extractor.dart) **纯 Dart 实现**,不依赖原生库,支持 MP3 和 OGG 格式。 **MP3 解析**: 1. ID3v2 标签解析(v2.3/v2.4) - TIT2:标题 - TPE1:歌手 - APIC:封面图 2. ID3v1 标签解析(回退方案) 3. 暴力搜索 APIC 帧(处理非规范 MP3 文件) **OGG 解析**: - 解析 Vorbis Comments(title/artist) - 解析 METADATA_BLOCK_PICTURE(Base64 编码的封面) **GBK 中文乱码修复**: - ID3v2 encoding=0 时,很多中文 MP3 实际使用 GBK 编码 - 解码策略:UTF-8 严格模式 → 系统原生 GBK → Latin1 回退 - Windows:dart:ffi 调用 `MultiByteToWideChar` - Android:charset_converter 调用系统 GBK 解码器 **封面提取双重策略**: 1. 按 APIC 帧规范解析 2. 失败则搜索图片文件头魔数(JPEG/PNG/GIF/BMP/WebP) ### 4.8 时长计算 — [audio_duration_calculator.dart](lib/utils/audio_duration_calculator.dart) **纯 Dart 实现**,不依赖 just_audio。 **MP3 时长计算**: 1. 优先读取 ID3v2 TLEN 标签(精确时长) 2. 回退到帧扫描:扫描前 100 帧获取平均比特率,用文件大小估算 **OGG 时长计算**: - 解析 OGG 页面头获取采样率 - 读取最后一页的 granule position - 时长 = granule_position / sample_rate ### 4.9 系统托盘 — [system_tray_manager.dart](lib/services/system_tray_manager.dart) **单例模式**,实现 `WindowListener` + `TrayListener`。 **功能**: - 关闭按钮 → 隐藏窗口(最小化到托盘) - 托盘左键 → 显示窗口 - 托盘右键 → 弹出菜单(上一首/播放暂停/下一首/随机播放/退出) - 播放状态变化 → 自动更新菜单文本 ### 4.10 在线更新 — [update_service.dart](lib/services/update_service.dart) **基于 Gitee Releases API**,纯静态方式分发更新。 **更新流程**: 1. 获取最新 Release 信息(`/repos/{owner}/{repo}/releases/latest`) 2. 版本号比较(语义化版本三段式) 3. 匹配平台安装包(.apk / .exe) 4. 下载安装包到临时目录 5. Android 端调用 `OpenFilex.open()` 安装 APK **下载管理**: - 支持下载进度回调 - 下载前清理旧安装包 - Gitee API 下载链接需要解析 302 重定向 ### 4.11 图标切换 — [app_icon_service.dart](lib/services/app_icon_service.dart) **Android 端**:通过 MethodChannel 与原生 `IconMethodCallHandler` 通信 - 使用 `PackageManager.setComponentEnabledSetting()` 切换 activity-alias - 4 种预设图标(A/B/C/D),对应 4 个 activity-alias **Windows 端**:不支持图标切换 ### 4.12 歌词页面 — [lyrics_screen.dart](lib/screens/lyrics_screen.dart) **功能**: - 自动加载同名 `.lrc` 文件 - 实时滚动高亮当前歌词行 - 手动滑动时显示"播放"按钮和"回到当前"按钮 - 点击播放按钮从选中行对应时间开始播放 **LRC 解析**:支持 `[mm:ss.xx]` 和 `[mm:ss.xxx]` 两种时间标签格式 **滚动控制**: - 自动滚动:使用 `animateTo` 平滑滚动到当前行 - 手动滚动:通过 `NotificationListener` 检测 - `_isAutoScrolling` 标志位区分自动滚动和手动滚动 --- ## 五、Bug 与潜在问题分析 ### 5.1 🔴 严重问题 #### 1. Gitee Access Token 硬编码泄露 - **文件**:[update_service.dart:101](lib/services/update_service.dart#L101) - **问题**:`_accessToken = '2a9d9647f30f460cb7d53d857ddb8373'` 直接硬编码在源码中 - **风险**:Token 泄露可能导致 Gitee API 被滥用(修改 Release、上传恶意文件等) - **建议**:通过环境变量注入,或使用不需要 Token 的公开 API #### 2. WindowsGbkDecoder 内存访问后使用已释放指针 - **文件**:[windows_gbk_decoder.dart:80-95](lib/utils/windows_gbk_decoder.dart#L80) - **问题**:`malloc.free(outputPtr)` 在第 87 行执行后,第 91-93 行仍在通过 `outputList`(即 `outputPtr.asTypedList(result)`)读取已释放的内存 - **风险**:读取已释放内存可能导致崩溃或数据损坏 - **建议**:在 `free` 之前先拷贝数据到 Dart 列表 ### 5.2 🟡 中等问题 #### 3. MusicListItem 收藏状态不响应式更新 - **文件**:[music_list_item.dart:32](lib/widgets/music_list_item.dart#L32) - **问题**:使用 `Provider.of(context, listen: false)` 获取收藏状态,不会监听变化 - **影响**:点击收藏后,其他列表项的爱心图标不会立即更新 - **建议**:改为 `listen: true` 或使用 `Consumer` 包裹 #### 4. _updateFavoriteState 方法无实际效果 - **文件**:[audio_handler.dart:349-355](lib/services/audio_handler.dart#L349) - **问题**:`_updateFavoriteState()` 调用 `_favoritesService.isFavorite()` 但没有使用返回值 - **影响**:切歌时没有真正更新收藏状态到系统媒体控制 - **建议**:使用返回值更新 MediaItem 的收藏状态 #### 5. PlayerControls 进度更新使用递归 Future.delayed - **文件**:[player_controls.dart:26-33](lib/widgets/player_controls.dart#L26) - **问题**:使用递归 `Future.delayed` 每秒刷新进度,可能导致内存泄漏(Widget 销毁后定时器仍在运行) - **影响**:虽然检查了 `mounted`,但在高频场景下不够可靠 - **建议**:使用 `Timer.periodic` 并在 `dispose` 中取消 #### 6. 歌词页 Windows 端使用轮询而非 Stream - **文件**:[lyrics_screen.dart:61-75](lib/screens/lyrics_screen.dart#L61) - **问题**:Windows 端使用 `Stream.periodic` 轮询位置(200ms)、播放状态(500ms)、时长(1s) - **影响**:不必要的 CPU 开销,不如直接监听 audioplayers 的 Stream - **建议**:直接使用 `windowsPlayer._player` 的 Stream(需暴露或添加 getter) #### 7. SharedPreferences 频繁实例化 - **问题**:多个 Service 每次操作都调用 `SharedPreferences.getInstance()` - **影响**:虽然 SharedPreferences 内部有缓存,但频繁调用仍有微小的性能开销 - **建议**:在应用启动时获取一次实例并全局传递 #### 8. 扫描仅支持 MP3 和 OGG - **文件**:[music_scanner.dart:24](lib/services/music_scanner.dart#L24) - **问题**:`_supportedExtensions = ['.mp3', '.ogg']`,README 中声称支持 FLAC/WAV/M4A/AAC/WMA - **影响**:README 与实际代码不一致,用户期望落空 - **建议**:扩展支持格式或修正 README ### 5.3 🟢 轻微问题 #### 9. 随机播放使用时间戳取模 - **文件**:[music_player_provider.dart:331](lib/providers/music_player_provider.dart#L331) - **问题**:`DateTime.now().millisecondsSinceEpoch % _songs.length` 不是真正的随机 - **影响**:短时间内多次调用可能得到相同结果 - **建议**:使用 `dart:math` 的 `Random` #### 10. 全局变量 globalChangeTheme - **文件**:[main.dart:17](lib/main.dart#L17) - **问题**:使用全局函数变量传递主题切换回调,不够优雅 - **建议**:通过 Provider 或 InheritedWidget 传递 #### 11. AboutScreen 硬编码外部 URL - **文件**:[about_screen.dart:29](lib/screens/about_screen.dart#L29) - **问题**:WebView 直接加载掘金用户主页,无离线回退 - **建议**:添加加载失败提示或本地缓存 #### 12. 删除歌曲无确认弹窗 - **文件**:[music_list_item.dart:65](lib/widgets/music_list_item.dart#L65) - **问题**:左滑删除直接执行,无二次确认 - **影响**:用户可能误删歌曲 - **建议**:添加确认对话框或撤销功能 #### 13. MainApplication 使用已废弃的 FlutterApplication - **文件**:[MainApplication.kt](android/app/src/main/kotlin/com/arui/ali_plan_glm_music/MainApplication.kt) - **问题**:`io.flutter.app.FlutterApplication` 已被标记为废弃 - **建议**:改为 `io.flutter.app.FlutterApplication` → 使用 `FlutterInjector` 或直接移除 --- ## 六、依赖关系图 ``` main.dart ├── MusicPlayerProvider ──┬── MyAudioHandler ────┬── just_audio (Android) │ │ ├── WindowsAudioPlayer ── audioplayers (Windows) │ │ ├── AppIconService ── MethodChannel │ │ └── FavoritesService │ ├── MusicScanner ──────┬── AudioMetadataExtractor │ │ ├── AudioDurationCalculator │ │ └── WindowsGbkDecoder (dart:ffi) │ ├── FavoritesService │ └── ListeningStatsService ├── SystemTrayManager (Windows only) │ ├── tray_manager │ └── window_manager └── MyApp ├── ThemeService └── setupPlatformQuickActions (Android only) ``` --- ## 七、数据持久化一览 | Key | 存储位置 | 类型 | 说明 | |-----|---------|------|------| | `music_scan_cache` | SharedPreferences | String (JSON) | 歌曲列表缓存 | | `music_scan_time` | SharedPreferences | int | 扫描时间戳 | | `favorite_song_ids` | SharedPreferences | StringList | 收藏歌曲 ID | | `play_mode` | SharedPreferences | String | 播放模式 | | `theme_color_key` | SharedPreferences | String | 主题色 key | | `selected_app_icon` | SharedPreferences | String | 图标 key | | `app_title` | SharedPreferences | String | 自定义应用名 | | `listening_stats_records` | SharedPreferences | String (JSON) | 听歌记录 | | `listening_stats_enabled` | SharedPreferences | bool | 统计开关 | | `artwork/` | 临时目录 | 文件 | 封面图缓存 | | `default_artwork.png` | 临时目录 | 文件 | 默认封面 | --- ## 八、构建与打包 ### Android ```bash flutter build apk --release ``` 输出:`build/app/outputs/flutter-apk/app-release.apk` ### Windows ```bash flutter build windows --release ``` 输出:`build/windows/x64/runner/Release/` ### 环境要求 - Flutter SDK: ^3.9.2 - Dart SDK: ^3.9.2 - Android: minSdkVersion 适配 Android 12+ - Windows: Windows 10/11