HarmonyOS NEXT 实战:Storage 存储空间分析的设计与实现

前言

存储空间分析是文件管理应用的高级能力,帮助用户直观了解设备存储占用情况并释放空间。HarmonyExplorer 基于 HarmonyOS NEXT 的 Storage 统计能力,实现了总容量统计、分类大小统计、大文件扫描与缓存清理的完整方案。存储分析的核心难点在于扫描性能与数据可视化的平衡,既要快速统计大量文件,又要以直观的图表呈现。本文将完整拆解 StorageUtil 封装、StorageChart 组件、扫描与清理的落地实践。

提示:本文代码基于 HarmonyOS NEXT(API 12+)ArkTS 严格模式编写,禁用 any 类型与隐式断言,所有存储数据均使用命名接口显式声明。

一、存储空间分析功能设计

1.1 需求分析

通过对用户使用场景的调研,HarmonyExplorer 的存储分析模块需要覆盖以下能力点:

  1. 获取设备总容量、已用空间与可用空间
  2. 按文件类型分类统计占用大小(图片、视频、音频、文档等)
  3. 以图表与进度条直观展示存储占比
  4. 扫描大文件并支持快速定位清理
  5. 一键清理应用缓存,释放存储空间

1.2 架构分层

存储分析功能遵循 UI → ViewModel → Repository → Service → KitManager → Kits 的分层架构,职责清晰。

  • UI 层:StoragePage 展示图表与详情列表
  • Repository 层:StorageRepository 聚合统计数据
  • KitManager 层:封装 Storage Statistics Kit
  • Utils 层:StorageUtil 提供纯函数式工具方法

提示:存储统计涉及大量文件遍历,建议在子线程或异步任务中执行,避免阻塞 UI 主线程导致卡顿。

二、Storage Kit 获取存储信息

2.1 API 概览

HarmonyOS NEXT 通过 storageStatistics 模块提供存储统计能力,核心 API 包括获取总容量、剩余空间与目录统计。

API 名称 作用 返回数据
getTotalSizeOfVolume 获取总容量 字节数
getFreeSizeOfVolume 获取可用空间 字节数
getCurrentBundleStats 获取应用占用 BundleStats
getUserStorageStats 用户存储统计 分类大小

2.2 获取存储信息

通过 storageStatistics 获取设备存储基础信息,封装为统一的 StorageInfo 数据模型。

// model/StorageModel.ets
export interface StorageInfo {
  totalSize: number
  freeSize: number
  usedSize: number
  usedPercent: number
}

export interface CategorySize {
  category: string
  size: number
  percent: number
}
// manager/StorageKitManager.ets
import { storageStatistics } from '@kit.CoreFileKit'
import { StorageInfo } from '../model/StorageModel'

export class StorageKitManager {
  static async getStorageInfo(): Promise<StorageInfo> {
    const total: number = await storageStatistics.getTotalSizeOfVolume()
    const free: number = await storageStatistics.getFreeSizeOfVolume()
    const used: number = total - free
    const percent: number = total > 0 ? Math.floor((used / total) * 100) : 0
    return { totalSize: total, freeSize: free, usedSize: used, usedPercent: percent }
  }
}

三、存储空间统计

3.1 总量统计

总量统计是存储分析的基础,HarmonyExplorer 在 StoragePage 进入时即触发统计,结果绑定到 ViewModel。

3.2 统计实现

StorageViewModel 持有存储状态,通过 StorageUtil 获取数据并更新 UI 状态。

// viewmodel/StorageViewModel.ets
import { StorageUtil } from '../utils/StorageUtil'
import { StorageInfo, CategorySize } from '../model/StorageModel'

@Observed
export class StorageViewModel {
  storageInfo: StorageInfo = { totalSize: 0, freeSize: 0, usedSize: 0, usedPercent: 0 }
  categoryList: CategorySize[] = []
  isLoading: boolean = false

  async loadStorageData(): Promise<void> {
    this.isLoading = true
    this.storageInfo = await StorageUtil.getStorageInfo()
    this.categoryList = await StorageUtil.getCategorySizes()
    this.isLoading = false
  }
}

四、文件分类大小统计

4.1 分类策略

文件按业务类型分为五大类,每类对应不同的扩展名集合,统计时遍历文件目录累计大小。

分类 包含类型 统计来源
图片 png/jpg/gif/webp 图片目录
视频 mp4/mov/avi 视频目录
音频 mp3/aac/flac 音频目录
文档 pdf/doc/txt 文档目录
其他 其余类型 沙箱目录

4.2 统计实现

StorageUtil 遍历各分类目录,累计文件大小,返回分类统计列表。

// utils/StorageUtil.ets
import { fileIo } from '@kit.CoreFileKit'
import { StorageKitManager } from '../manager/StorageKitManager'
import { StorageInfo, CategorySize } from '../model/StorageModel'

export class StorageUtil {
  static readonly CATEGORY_MAP: Record<string, string[]> = {
    '图片': ['png', 'jpg', 'gif', 'webp'],
    '视频': ['mp4', 'mov', 'avi'],
    '音频': ['mp3', 'aac', 'flac'],
    '文档': ['pdf', 'doc', 'txt']
  }

  static async getCategorySizes(): Promise<CategorySize[]> {
    const storageInfo: StorageInfo = await StorageKitManager.getStorageInfo()
    const totalUsed: number = storageInfo.usedSize
    const result: CategorySize[] = []
    const categories: string[] = Object.keys(StorageUtil.CATEGORY_MAP)
    for (const category of categories) {
      const size: number = await StorageUtil.calcCategorySize(category)
      const percent: number = totalUsed > 0 ? Math.floor((size / totalUsed) * 100) : 0
      result.push({ category: category, size: size, percent: percent })
    }
    return result
  }

  static async calcCategorySize(category: string): Promise<number> {
    const exts: string[] = StorageUtil.CATEGORY_MAP[category]
    if (exts === undefined) {
      return 0
    }
    let total: number = 0
    for (const ext of exts) {
      total = total + await StorageUtil.scanByExt(ext)
    }
    return total
  }
}

五、StorageChart 图表组件

5.1 组件实现

StorageChart 以环形图展示各分类占用比例,直观的可视化是存储分析的核心价值,让用户一眼看清空间分布。

// components/StorageChart.ets
@Component
export struct StorageChart {
  @Prop categories: CategorySize[]
  private colors: string[] = ['#007DFF', '#FF6B6B', '#4ECDC4', '#FFE66D', '#95A5A6']

  build() {
    Column({ space: 12 }) {
      Text('存储占用分布').fontSize(16).fontWeight(FontWeight.Medium)
      Stack() {
        ForEach(this.categories, (item: CategorySize, index: number) => {
          Progress({ value: item.percent, total: 100, type: ProgressType.Ring })
            .width(120).height(120)
            .color(this.colors[index % this.colors.length])
        }, (item: CategorySize) => item.category)
      }
      ForEach(this.categories, (item: CategorySize, index: number) => {
        Row({ space: 8 }) {
          Circle({ width: 10, height: 10 }).fill(this.colors[index % this.colors.length])
          Text(item.category + ' ' + item.percent.toString() + '%').fontSize(12)
        }
      }, (item: CategorySize) => item.category)
    }.width('100%').padding(16)
  }
}

六、ProgressBar 进度条展示

6.1 进度展示

除环形图外,HarmonyExplorer 还使用线性 ProgressBar 展示总存储使用率,配合数字提示形成双重反馈。

// components/StorageOverview.ets
@Component
export struct StorageOverview {
  @ObjectLink viewModel: StorageViewModel

  build() {
    Column({ space: 12 }) {
      Row({ space: 8 }) {
        Text('已用 ' + StorageUtil.formatSize(this.viewModel.storageInfo.usedSize))
          .fontSize(14).layoutWeight(1)
        Text('总共 ' + StorageUtil.formatSize(this.viewModel.storageInfo.totalSize))
          .fontSize(14).fontColor('#999999')
      }
      Progress({ value: this.viewModel.storageInfo.usedPercent, total: 100, type: ProgressType.Linear })
        .width('100%').color('#007DFF')
      Text('使用率 ' + this.viewModel.storageInfo.usedPercent.toString() + '%')
        .fontSize(12).fontColor('#666666')
    }.width('100%').padding(16)
  }
}

StorageUtil 的格式化方法将字节数转换为易读的单位:

// utils/StorageUtil.ets
export class StorageUtil {
  static formatSize(bytes: number): string {
    if (bytes < 1024) {
      return bytes.toString() + ' B'
    }
    if (bytes < 1024 * 1024) {
      return (bytes / 1024).toFixed(1) + ' KB'
    }
    if (bytes < 1024 * 1024 * 1024) {
      return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
    }
    return (bytes / (1024 * 1024 * 1024)).toFixed(2) + ' GB'
  }
}

七、存储详情列表

7.1 列表实现

存储详情列表展示各分类的具体占用,每项包含分类名、大小与占比,点击可进入分类文件列表。

// components/StorageDetailList.ets
@Component
export struct StorageDetailList {
  @Prop list: CategorySize[]
  onItemClick: (category: string) => void = () => {}

  build() {
    List({ space: 8 }) {
      ForEach(this.list, (item: CategorySize) => {
        ListItem() {
          Row({ space: 12 }) {
            Text(item.category).fontSize(14).layoutWeight(1)
            Text(StorageUtil.formatSize(item.size)).fontSize(14).fontColor('#666666')
            Text(item.percent.toString() + '%').fontSize(12).fontColor('#999999')
          }.width('100%').padding(12).backgroundColor('#FFFFFF').borderRadius(12)
        }.onClick(() => this.onItemClick(item.category))
      }, (item: CategorySize) => item.category)
    }.width('100%').layoutWeight(1)
  }
}

八、大文件扫描

8.1 扫描实现

大文件扫描按文件大小阈值筛选,帮助用户快速定位占用空间最大的文件。大文件扫描是释放存储空间最直接有效的手段

// utils/StorageUtil.ets
import { FileInfo } from '../model/FileInfo'

export class StorageUtil {
  static async scanLargeFiles(dirPath: string, threshold: number): Promise<FileInfo[]> {
    const result: FileInfo[] = []
    if (!fileIo.accessSync(dirPath)) {
      return result
    }
    const names: string[] = fileIo.listFileSync(dirPath)
    for (const name of names) {
      const fullPath: string = dirPath + '/' + name
      const stat: fileIo.Stat = fileIo.statSync(fullPath)
      if (stat.isDirectory()) {
        const sub: FileInfo[] = await StorageUtil.scanLargeFiles(fullPath, threshold)
        for (const f of sub) {
          result.push(f)
        }
      } else if (stat.size >= threshold) {
        result.push({
          id: fullPath,
          name: name,
          path: fullPath,
          size: stat.size,
          type: StorageUtil.getExt(name),
          modifyTime: stat.mtime,
          createTime: stat.mtime,
          favorite: false
        })
      }
    }
    return result
  }

  static getExt(name: string): string {
    const dotIndex: number = name.lastIndexOf('.')
    return dotIndex > 0 ? name.substring(dotIndex + 1) : ''
  }
}

九、缓存清理功能

9.1 清理实现

缓存清理针对应用临时目录与缓存目录,一键清空非必要文件,释放存储空间。清理前先统计可清理大小,确认后执行。完整清理流程如下:

  1. 调用 calcCacheSize 遍历缓存目录并统计可清理的文件总大小
  2. 弹出 ConfirmDialog 展示可释放空间并等待用户确认清理操作
  3. 用户确认后调用 cleanCache 执行清理并刷新存储统计数据
// utils/StorageUtil.ets
export class StorageUtil {
  static async cleanCache(cacheDir: string): Promise<number> {
    let cleaned: number = 0
    if (!fileIo.accessSync(cacheDir)) {
      return 0
    }
    const names: string[] = fileIo.listFileSync(cacheDir)
    for (const name of names) {
      const fullPath: string = cacheDir + '/' + name
      const stat: fileIo.Stat = fileIo.statSync(fullPath)
      if (stat.isDirectory()) {
        fileIo.rmdirSync(fullPath)
      } else {
        cleaned = cleaned + stat.size
        fileIo.unlinkSync(fullPath)
      }
    }
    return cleaned
  }

  static async calcCacheSize(cacheDir: string): Promise<number> {
    let total: number = 0
    if (!fileIo.accessSync(cacheDir)) {
      return 0
    }
    const names: string[] = fileIo.listFileSync(cacheDir)
    for (const name of names) {
      const stat: fileIo.Stat = fileIo.statSync(cacheDir + '/' + name)
      total = total + stat.size
    }
    return total
  }
}

提示:缓存清理要避免误删用户数据,建议只清理明确的临时目录,并在清理前弹出 ConfirmDialog 二次确认。

十、StorageUtil 工具类封装

10.1 完整封装

StorageUtil 整合存储信息获取、分类统计、大文件扫描与缓存清理,对外提供统一入口。

// utils/StorageUtil.ets
import { fileIo } from '@kit.CoreFileKit'
import { StorageKitManager } from '../manager/StorageKitManager'
import { StorageInfo, CategorySize, FileInfo } from '../model/StorageModel'

export class StorageUtil {
  static async getStorageInfo(): Promise<StorageInfo> {
    return await StorageKitManager.getStorageInfo()
  }

  static async getLargeFiles(dirPath: string): Promise<FileInfo[]> {
    const threshold: number = 100 * 1024 * 1024
    return await StorageUtil.scanLargeFiles(dirPath, threshold)
  }
}

各存储操作的能力与阈值如下表,便于运维与扩展时统一调整:

操作 阈值/范围 触发方式
总量统计 全设备 进入页面自动
分类统计 五大类 进入页面自动
大文件扫描 ≥100MB 用户手动触发
缓存清理 缓存目录 用户确认后执行

在这里插入图片描述

总结

本文完整实现了 HarmonyExplorer 的存储空间分析模块,涵盖 Storage Kit 调用、总量与分类统计、StorageChart 图表、大文件扫描与缓存清理。分层架构让存储逻辑清晰可测试,图表与进度条的双重可视化大幅提升了数据可读性。大文件扫描与缓存清理也为用户释放空间提供了实用工具。希望这套方案能帮助你在鸿蒙项目中落地存储分析能力。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!

相关资源

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐