本文用 disk_space_2 1.0.13 在 Flutter
鸿蒙应用中读取总容量、可用容量和指定目录的可用容量,并把查询结果用于下载前空间检查。示例依赖锁定到真机受测提交,返回单位、异常处理和路径边界都按实际
API 说明。

三方库仓库: https://atomgit.com/oh-flutter/disk_space_2

本文锁定版本: 0cb25f91bdda96fddbdfd465ea189678e2cf959b

完整 Demo: disk_space_2/example(受测提交)

一、最终真机效果

在这里插入图片描述

图 1:CHZ-AL00 / HarmonyOS 7.0.0.105 上读取总量、空闲量和应用沙箱目录空闲量。

在这里插入图片描述

在这里插入图片描述

本次样本中,总容量为 103460 MiB,可用容量为 67477.16015625 MiB。默认目录和测试用沙箱目录位于同一文件系统,因此两次可用容量相同;这不是硬编码值,设备写入数据后会变化。

使用场景真机结果
查询总容量成功,返回 double,单位 MiB
查询默认可用容量成功
查询应用沙箱目录成功
查询不存在目录明确失败,没有回退到默认目录
自动化与构建13 项 Dart/Widget 测试、静态分析和 HAP 构建通过

二、接入前先确认三个语义

第一,三个容量接口返回的都是 MiB,即 1 MiB = 1024 * 1024 bytes,不是 bytes,也不是以 1000 为进位的 MB。若页面要显示 GiB,应在展示层继续除以 1024。

第二,查询结果描述的是目标路径所在文件系统。它适合回答“这个应用目录还能不能放下一个文件”,不等于设备所有物理介质的总和,也不能作为固定硬件规格保存。

第三,指定路径必须是应用能够访问的真实本地目录。不要传文档 URI、普通文件、其他应用私有目录或不存在的路径。错误不能按 0 处理,因为 0 也可能表示文件系统确实已经没有可用空间。

三、环境与依赖

组件实测版本
Flutter OH3.41.10-ohos-1.0.1
Dart3.11.5
DevEco Studio26.0.0 Release
HarmonyOS SDKAPI 26,示例兼容 API 18
测试设备CHZ-AL00 / HarmonyOS 7.0.0.105
disk_space_21.0.13 / 上述受测提交

版本号更大的 3.44.9+ohos-0.0.1-canary1 是预览版,不是本文的实测环境。当前 OHOS 适配尚未发布稳定 TAG,因此业务工程应锁定完整 SHA:

dependencies:
  disk_space_2:
    git:
      url: https://atomgit.com/oh-flutter/disk_space_2.git
      ref: 0cb25f91bdda96fddbdfd465ea189678e2cf959b
flutter pub get
flutter pub deps

随后在 pubspec.lock 中确认 resolved-ref 与受测 SHA 一致。容量查询不需要新增鸿蒙权限;示例中的调试网络权限也不是该库的功能要求。

在这里插入图片描述

图 2:AtomGit 仓库、OHOS 适配分支和当前提交核对。

四、核心 API 用法

import 'dart:io';

import 'package:disk_space_2/disk_space_2.dart';

Future<Map<String, double?>> loadDiskSpace() async {
  final directory = Directory.systemTemp;
  if (!directory.existsSync()) {
    throw StateError('应用临时目录不存在');
  }

  return <String, double?>{
    'totalMiB': await DiskSpace.getTotalDiskSpace,
    'freeMiB': await DiskSpace.getFreeDiskSpace,
    'directoryFreeMiB':
        await DiskSpace.getFreeDiskSpaceForPath(directory.path),
  };
}

getTotalDiskSpacegetFreeDiskSpace 是静态异步 getter;指定目录使用 getFreeDiskSpaceForPath(path)。返回类型可空,所以业务既要处理平台异常,也要处理 null。如果下载包还需要解压,阈值不要只等于压缩包大小,应叠加解压空间和安全余量。

bool hasEnoughSpace(double? freeMiB, int downloadBytes) {
  if (freeMiB == null) return false;
  final requiredMiB = downloadBytes / (1024 * 1024);
  const reserveMiB = 256.0;
  return freeMiB >= requiredMiB + reserveMiB;
}

五、可直接放进页面的查询流程

import 'dart:io';

import 'package:disk_space_2/disk_space_2.dart';
import 'package:flutter/material.dart';

class DiskSpacePage extends StatefulWidget {
  const DiskSpacePage({super.key});

  
  State<DiskSpacePage> createState() => _DiskSpacePageState();
}

class _DiskSpacePageState extends State<DiskSpacePage> {
  double? _total;
  double? _free;
  double? _pathFree;
  Object? _error;
  bool _loading = false;

  
  void initState() {
    super.initState();
    _refresh();
  }

  Future<void> _refresh() async {
    if (_loading) return;
    setState(() {
      _loading = true;
      _error = null;
    });
    try {
      final values = await Future.wait<double?>([
        DiskSpace.getTotalDiskSpace,
        DiskSpace.getFreeDiskSpace,
        DiskSpace.getFreeDiskSpaceForPath(Directory.systemTemp.path),
      ]);
      if (!mounted) return;
      setState(() {
        _total = values[0];
        _free = values[1];
        _pathFree = values[2];
      });
    } catch (error) {
      if (mounted) setState(() => _error = error);
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  }

  String _format(double? value) =>
      value == null ? '不可用' : '${(value / 1024).toStringAsFixed(2)} GiB';

  
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: const Text('存储空间'),
          actions: [
            IconButton(
              tooltip: '刷新',
              onPressed: _loading ? null : _refresh,
              icon: const Icon(Icons.refresh),
            ),
          ],
        ),
        body: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            ListTile(title: const Text('总容量'), subtitle: Text(_format(_total))),
            ListTile(title: const Text('可用容量'), subtitle: Text(_format(_free))),
            ListTile(
              title: const Text('临时目录可用容量'),
              subtitle: Text(_format(_pathFree)),
            ),
            if (_loading) const LinearProgressIndicator(),
            if (_error != null) Text('查询失败:$_error'),
          ],
        ),
      );
}

页面在 Future 完成后检查 mounted,并在请求期间禁用刷新,避免旧结果覆盖新状态。真实下载任务还应在开始写文件前再查一次,因为用户可能在页面展示后继续占用空间。

在这里插入图片描述

图 3:OHOS 端使用应用目录、statvfs 查询和 MiB 换算。

六、测试、构建与真机核对

flutter analyze
flutter test
cd example
flutter test
flutter build hap --debug --no-codesign

在这里插入图片描述

图 4:13 项 Dart/Widget 测试和静态检查结果。

在这里插入图片描述

图 5:HAP 构建信息及真机宿主锁定的远程提交。

在这里插入图片描述

图 6:容量读取、沙箱目录成功和不存在目录失败的真实记录。

真机数据只代表测试时刻。自动化、HAP 构建和安装也不能代替容量 API 的实际调用,因此发布时应同时保留图 1 和图 6。

七、常见问题

Q1:为什么拿到的是几万而不是几百 GB

返回单位是 MiB。显示 GiB 时除以 1024,不要再次按 bytes 除以 1024 * 1024 * 1024

Q2:目录查询为什么抛异常

确认路径存在、是目录、属于应用可访问范围且不是 URI。失败时提示用户清理空间或重试,不要改查默认目录后假装目标目录可写。

Q3:free 与下一秒读取的结果不同正常吗

正常。系统缓存、日志和其他进程都可能改变可用容量。业务测试应比较范围和阈值,不应断言一个固定小数。

八、总结

disk_space_2 适合在下载、解压和缓存前查询目标文件系统。可靠接入的关键是锁定受测 SHA、牢记返回单位为 MiB、只查询应用可访问目录,并把空值和异常与“空间为 0”分开处理。本文已在 API 26 真机验证三个查询及非法路径边界。

九、参考链接

欢迎加入CPF-Flutter 鸿蒙社区:https://atomgit.com/CPF-Flutter

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐