HarmonyOS原生应用占用空间管理
使用接口
接口 | 接口能力 |
storageStatistics.getCurrentBundleStats | 获取当前应用的存储空间大小 (包含缓存文件,安装文件等) |
statvfs.getFreeSize | 查询设备剩余可用空间 |
statvfs.getTotalSize | 获取设备总空间 (排除系统占用空间) |
场景一:计算应用缓存大小,并进行清理
1.可以通过storageStatistics.getCurrentBundleStats接口获取缓存大小。
应用的缓存文件因为级别和加密类型不同,会保存在以下目录中。例如:app级别,加密类型为el1的缓存会保存到/data/storage/el1/base/cache目录下。
/data/storage/el1/base/cache
/data/storage/el1/base/haps/entry/cache
/data/storage/el2/base/cache
/data/storage/el2/base/haps/entry/cache
这四个目录可以通过以下接口获取,其中el1与el2因为分区不同,需要切换加密等级才能获取到:
let moduleContext: common.Context;
moduleContext = this.context.createModuleContext('entry');
console.log('moduleContext + el2: '+moduleContext.cacheDir);
console.log('UIAbilityContext + el2: '+this.context.cacheDir);
moduleContext.area = contextConstant.AreaMode.EL1;
console.log('moduleContext + el1: '+moduleContext.cacheDir);
this.context.area = contextConstant.AreaMode.EL1;
console.log('UIAbilityContext + el1: '+this.context.cacheDir);
storageStatistics.getCurrentBundleStats((err: BusinessError, bundleStats: storageStatistics.BundleStats) => {
if (err) {
console.error(`Invoke getCurrentBundleStats failed, code is ${err.code}, message is ${err.message}`);
} else {
console.info(`Invoke getCurrentBundleStats succeeded, cacheSize is ${bundleStats.cacheSize}`);
}
});
2.清除缓存,缓存会保存在上述四个文件夹中,可以递归删除
webview缓存(/data/storage/el2/base/cache/web/Cache)包含于在上述文件夹中,也可以调用WebviewController.removeCache单独清理web缓存。
//通过Context.cacheDir获取所有缓存路径
let cacheDir : string[] =[];
let moduleContext: common.Context;
moduleContext = getContext().createModuleContext('entry');
cacheDir.push(moduleContext.cacheDir);
cacheDir.push(getContext().cacheDir);
moduleContext.area = contextConstant.AreaMode.EL1;
getContext().area = contextConstant.AreaMode.EL1;
cacheDir.push(moduleContext.cacheDir);
cacheDir.push(getContext().cacheDir);
for (let i = 0; i < cacheDir.length; i++) {
let cache = cacheDir[i];
let exist = fs.accessSync(cache);
if (exist) {
try {
fs.rmdirSync(cache);
} catch (err) {
console.log(err);
}
}
}
场景二:对文件夹中图片进行展示,并选择性清理(这里只对图片做了筛选,同理,还可以加上视频等)
1.制定图片过滤规则,过滤出对应格式(例如:jpg,png)的图片。
let listFileOption: ListFileOptions = {
recursion: true,//列出子目录
listNum: 0,// 列出文件名数量。可选,当设置0时,列出所有文件,默认为0。
filter: {
suffix: ['.jpg','.png'],//文件后缀
}
}
2.将文件夹路径与listFile获取到的路径拼接起来,得到图片完整沙箱路径。
let path: string[] = [];
let fileNames = fs.listFileSync(dirPath, listFileOption)
for (let i = 0; i < fileNames.length; i++) {
let filePath = dirPath + fileNames[i]
console.log(filePath);
path[i] = filePath
}
3.根据沙箱路径将图片转换为pixelmap。
function Picture(mediaPath: string): image.PixelMap {
let options: image.SourceOptions = {
sourceSize: {
width: 100,
height: 100
},
sourceDensity: 0
}
let decodingOptions: image.DecodingOptions = {}
//获取文件
let file = fs.openSync(mediaPath, fs.OpenMode.READ_ONLY);
fs.copyFileSync(file.fd, getContext().filesDir + '/' + file.name + '1');
//通过传入文件描述符来创建图片源实例
let imageSource: image.ImageSource = image.createImageSource(file.fd, options);
let imageInfo: image.ImageInfo = imageSource.getImageInfoSync();
//这里只取图片中间一部分
if (imageInfo.size.height > imageInfo.size.width) {
let a = (imageInfo.size.height - imageInfo.size.width) / 2
decodingOptions.desiredRegion = {
size: {
width: imageInfo.size.width,
height: imageInfo.size.width
},
x: 0,
y: a
}
} else {
let a = (imageInfo.size.width - imageInfo.size.height) / 2
decodingOptions.desiredRegion = {
size: {
width: imageInfo.size.height,
height: imageInfo.size.height
},
x: a,
y: 0
}
}
let pixelMap: image.PixelMap = imageSource.createPixelMapSync(decodingOptions);
return pixelMap
}
4.将图片展示出来,并删除指定图片,释放空间。
(1)单张图片删除。
定义长按手势,可以通过长按图片拉起菜单,选择删除指定图片。
@Builder
MenuBuilder(path: string) {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
Text('删除')
.fontSize(20)
.width(100)
.height(50)
.textAlign(TextAlign.Center)
.onClick(() => {
try {
fs.unlinkSync(path);
let i = this.mediaPath.indexOf(path);
this.mediaPath.splice(i, 1);
} catch (err) {
console.log(err);
}
})
}.width(100)
}
使用Grid将图片排列展示,并通过bindContextMenu给image组件绑定菜单,长按拉起删除选择框,并实时展示删除后剩余图片。
Grid() {
ForEach(this.mediaPath, (item: string) => {
GridItem() {
Image(picture(item))
.bindContextMenu(this.MenuBuilder(item), ResponseType.LongPress)
.objectFit(ImageFit.Fill)
.autoResize(false)
}
})
}
(2)一键删除所有图片。
点击一键删除后,会拉起一个弹窗确认,这里弹窗使用的promptAction.openCustomDialog。
Button('一键删除')
.onClick(() => {
let uiContext = this.getUIContext();
let promptAction = uiContext.getPromptAction();
let contentNode = new ComponentContent(uiContext, wrapBuilder(BuildText), this.mediaPath);
this.content = contentNode;
try {
promptAction.openCustomDialog(contentNode
, {
showInSubWindow: false,
offset: { dx: 5, dy: 5 },
onWillDisappear: () => {
this.mediaPath = FindAll(getContext().filesDir)
}
}
)
} catch (error) {
let message = (error as BusinessError).message;
let code = (error as BusinessError).code;
console.error(`OpenCustomDialog args error code is ${code}, message is ${message}`);
}
})
当前弹窗组件返回的reason中,暂不支持按钮关闭,故需自行实现。
自定义一个变量,使用@Watch去监测,一旦变量发生改变则触发回调关闭弹窗。
//自定义回调,当dialogState发生变化,关闭弹窗
@LocalStorageLink('CustomDialogInfo') @Watch('onChange') dialogState: number = 0;
......
// 自定义回调,关闭弹窗
onChange() {
try {
this.uiContext.getPromptAction().closeCustomDialog(this.content);
} catch (error) {
let message = (error as BusinessError).message;
let code = (error as BusinessError).code;
console.error(`OpenCustomDialog args error code is ${code}, message is ${message}`);
}
}
......
@Builder
function BuildText(params: string[]) {
Column({ space: 10 }) {
Text('请确认是否删除')
.fontSize(30)
.textAlign(TextAlign.Center)
.fontWeight(FontWeight.Bold)
.padding(8)
Text('注意:一旦确认删除后,文件将不可恢复')
.fontSize(20)
.textAlign(TextAlign.Center)
.padding({ left: 14, right: 14 })
Flex({ justifyContent: FlexAlign.SpaceAround }) {
Button('取消').onClick(() => {
//改变dialogState,触发onChange回调,关闭弹窗
let dialogInfo: SubscribedAbstractProperty<number> = storage.link('CustomDialogInfo');
let dialogState = dialogInfo.get();
console.log('取消', dialogInfo.get());
dialogInfo.set(++dialogState);
}).backgroundColor(0xffffff).fontColor(Color.Black)
Button('确认')
.onClick(() => {
if (params!) {
for (let i = 0; i < params.length; i++) {
const filePath = params[i];
try {
fs.unlinkSync(filePath);
} catch (err) {
console.log(err);
}
}
}
//改变dialogState,触发onChange回调,关闭弹窗
let dialogInfo: SubscribedAbstractProperty<number> = storage.link('CustomDialogInfo');
let dialogState = dialogInfo.get();
dialogInfo.set(++dialogState);
}).backgroundColor(0xffffff).fontColor(Color.Black)
}.margin({ bottom: 10 })
}.backgroundColor('#FFF0F0F0')
}
常见问题
Q:为什么通过查询到设备总空间和设置中显示的不同?
A:statvfs.getTotalSizeSync获取到的是data分区大小,三方应用仅能查询到自己能够使用的空间大小,暂无查询手机总内存大小的接口。
Q:能够查询外卡空间大小吗?
A:暂不支持。
Q:为什么有些中文文件名的文件搜不到?
A:当前HarmonyOS文件系统仅支持utf-8格式,gbk格式的中文名的文件通过其它方式存入文件系统中,名称会乱码。
Q:为什么删除文件夹会报错:Directory not empty。
A:fs.rmdir是递归删除,会删除该文件夹以及子文件夹中的所有文件,但是当其子目录中有高权限的文件时,调用的接口无法删除此文件,导致无法删除此文件夹,报错:Directory not empty。