【OpenHarmony/HarmonyOs 】CheckMe 存储分析与容量校准实践:statvfs 多路径探测和 Preferences 持久化
【OpenHarmony/HarmonyOs 】CheckMe 存储分析与容量校准实践:statvfs 多路径探测和 Preferences 持久化
本文基于
CheckMe项目的存储分析模块展开,聚焦存储容量读取、分区统计、多路径探测和用户校准,适合用于讲解 HarmonyOS 第三方应用如何在权限边界内做可信存储展示。
存储信息看起来简单:总容量、已使用、可用空间、使用率。
但在 HarmonyOS 第三方应用里,存储读取并不等于“直接拿到整机物理容量”。很多时候,应用只能读取到某个路径或分区的统计信息,这就会出现一个问题:
系统设置里显示 256GB,但应用 API 读出来的可能是某个分区容量。
CheckMe 为了解决这个问题,设计了两层方案:
- 使用
statvfs做多路径探测 - 允许用户手动校准整机容量,并持久化保存
这篇文章就专门拆解这个模块。💾
一、为什么存储容量读取需要工程化
存储分析页面需要展示:
- 总容量
- 已使用
- 可用空间
- 使用率
- 数据来源说明
页面代码中有明确注释:
/** StorageInfo.totalStorage / usedStorage / availableStorage 已为 GB 数值,非字节 */
private formatStorageGb(gb: number | undefined): string {
if (gb === undefined || gb < 0) {
return '--';
}
return gb.toFixed(2) + ' GB';
}
这里强调了一个点:服务层已经把字节转换成 GB,页面只负责格式化展示。
这就是工程化分层:
StorageInfoReader:读取底层字节数据
DeviceInfoService:转换成业务模型
StorageAnalysis:负责页面展示
二、页面展示:不要隐藏数据来源
StorageAnalysis.ets 展示分区容量:
this.InfoRow('总容量', this.formatStorageGb(this.storage.totalStorage))
this.InfoRow('已使用', this.formatStorageGb(this.storage.usedStorage))
this.InfoRow('可用', this.formatStorageGb(this.storage.availableStorage))
this.InfoRow('使用率', this.formatPercent(this.storage.usagePercent))
同时,如果有数据来源说明,也会展示出来:
if (this.storage.storageSourceLabel !== undefined &&
this.storage.storageSourceLabel !== '') {
Text('数据来源:' + this.storage.storageSourceLabel)
.fontSize(12)
.fontColor($r('app.color.text_tertiary'))
.width('100%')
}
这非常重要。
存储数据可能来自:
- 系统分区统计
- 应用沙箱路径
- 用户手动校准
- 多路径探测结果
如果不说明来源,用户看到和系统设置不一致时,会以为应用有 bug。
三、statvfs 多路径探测
底层读取在 StorageInfoReader.ts 中:
import statvfs from '@ohos.file.statvfs';
private static readonly STATVFS_CANDIDATE_PATHS: string[] = [
'/data/storage/el2/base',
'/data/storage/el1/base',
'/data',
'/storage/emulated/0',
'/data/storage'
];
项目不会只读一个路径,而是尝试多个候选路径。
原因是:不同路径可能落在不同分区或沙箱视角下。如果只读一个路径,结果可能偏小。
四、选择 total 最大的合法结果
多路径探测逻辑如下:
public async getDataPartitionStat(): Promise<{ total: number; free: number }> {
let bestTotal: number = 0;
let bestFree: number = 0;
const paths: string[] = StorageInfoReader.STATVFS_CANDIDATE_PATHS;
for (let i: number = 0; i < paths.length; i++) {
const p: string = paths[i];
try {
const total: number = await statvfs.getTotalSize(p);
const free: number = await statvfs.getFreeSize(p);
if (total > 0 && free >= 0 && free <= total) {
if (total > bestTotal) {
bestTotal = total;
bestFree = free;
}
}
} catch (_e) {
// ignore
}
}
return { total: bestTotal, free: bestFree };
}
这段代码有几个稳健点:
- 单个路径失败不影响整体
total必须大于 0free不能小于 0free不能大于total- 在合法结果中取
total最大的一组
这比直接读 /data 更稳。
五、为什么还需要用户校准
即使做了多路径探测,第三方应用读到的也可能仍然不是系统设置里的整机容量。
所以项目提供了容量校准能力。
StorageCalibrationStore 使用 Preferences 保存用户输入的整机容量:
const PREF_STORE_NAME = 'checkme_user_prefs';
const KEY_STORAGE_CALIBRATION_GB = 'storageTotalCalibrationGB';
保存逻辑:
static async saveCalibrationGb(gb: number): Promise<boolean> {
const ctx: common.UIAbilityContext | null =
StorageCalibrationStore.contextHolder;
if (ctx === null) {
return false;
}
try {
const pref = await dataPreferences.getPreferences(ctx, PREF_STORE_NAME);
await pref.put(KEY_STORAGE_CALIBRATION_GB, gb);
await pref.flush();
return true;
} catch (e) {
console.error('StorageCalibrationStore.saveCalibrationGb failed: ' + JSON.stringify(e));
return false;
}
}
Preferences 适合保存这种轻量配置:
- 数字小
- 读取频繁
- 不需要复杂数据库
- 和用户设置强相关
六、读取校准值
读取逻辑:
static async getCalibrationGb(): Promise<number | undefined> {
const ctx: common.UIAbilityContext | null =
StorageCalibrationStore.contextHolder;
if (ctx === null) {
return undefined;
}
try {
const pref = await dataPreferences.getPreferences(ctx, PREF_STORE_NAME);
const raw: dataPreferences.ValueType =
await pref.get(KEY_STORAGE_CALIBRATION_GB, 0);
if (typeof raw !== 'number') {
return undefined;
}
if (raw <= 0) {
return undefined;
}
return raw;
} catch (_e) {
return undefined;
}
}
这里做了两层校验:
- 类型必须是 number
- 数值必须大于 0
这可以避免无效配置污染存储展示。
七、校准后的容量如何计算
存储读取时,如果用户提供了校准值,会按当前分区使用比例映射到校准总量:
if (paramTotalGB !== undefined && paramTotalGB > 0) {
const calibratedBytes: number =
Math.round(paramTotalGB * 1024 * 1024 * 1024);
if (partitionTotal > 0) {
const usageRatio: number = partitionUsed / partitionTotal;
const usedScaled: number = Math.round(calibratedBytes * usageRatio);
const freeScaled: number = Math.max(0, calibratedBytes - usedScaled);
return {
total: calibratedBytes,
free: freeScaled
};
}
}
这个设计很有意思。
它不是简单把总容量改成用户输入值,然后直接套用原始 free。因为原始 free 可能来自分区视角,和整机容量不一致。
项目采用的是:
分区真实使用比例 -> 映射到用户校准总量
这样展示结果更接近系统设置中的整机容量。
八、存储分析页面的用户提示
页面底部有说明:
Text('说明:第三方应用仅能读取分区统计,与「设置」中整机存储可能略有差异。')
.fontSize(12)
.fontColor($r('app.color.text_tertiary'))
.width('100%')
这句非常重要。
开发设备工具类应用时,不要假装自己能拿到所有系统级数据。第三方应用有权限边界,某些数据只能近似或通过用户校准。
把限制告诉用户,反而能提升可信度。
九、推荐扩展方向
如果继续完善存储分析,我建议加这些能力:
- 存储来源标签更明确
- 展示是否已校准
- 提供重新校准入口
- 显示校准时间
- 支持恢复默认分区统计
- 对异常输入做范围校验
比如:
interface StorageCalibrationInfo {
calibratedTotalGb: number;
calibratedAt: number;
source: 'user_input' | 'statvfs';
}
这样用户能知道当前容量展示到底来自系统探测还是手动校准。
十、文章小结
CheckMe 的存储分析模块解决了一个很实际的问题:第三方应用读取存储容量时,系统 API 结果和用户认知可能不完全一致。
项目采用了两步方案:
- 先用
statvfs多路径探测,取最合理的分区统计 - 再允许用户手动校准整机容量,并通过 Preferences 持久化
最终效果是:
- 数据更稳定
- 来源更透明
- 用户可校准
- 展示更接近系统设置
- 不突破第三方应用权限边界
最后一句总结:设备工具类应用最难的不是显示数字,而是告诉用户这个数字从哪里来、为什么可信、什么时候需要校准。 📊
参考资料
- 华为开发者文档:statvfs API
- 华为开发者文档:Preferences Data Persistence
- 华为开发者文档:Application File Access

更多推荐



所有评论(0)