HarmonyOS优化应用文件上传下载慢问题性能优化二
常见场景和解决方案
场景1:低带宽网络上传琐碎文件场景
在网络连接较差,低带宽的网络环境中,HTTP连接的建立耗时可能会大幅提升。这时候进行数据压缩可以加快页面加载速度,并减少HTTP请求数量和移动数据流量。
场景2:处理大量资源的场景
如应用商店、网盘应用等,这类应用通常拥有大体积的文件资源。当用户从暂停或者断网中重新恢复时,如果从头开始上传下载则会额外耗费大量的时间。此时可以采用断点续传方法进行上传下载。
(一)数据压缩
数据压缩是指在应用中对数据进行压缩,以减少存储空间和数据传输量、节省带宽,提高加载速度。数据压缩通常在网络传输和存储方面发挥着重要作用,特别是在处理大量数据或需要频繁传输数据的场景下。
在应用开发中,常见的数据压缩技术分类如下:
有损压缩:仅限图片视频音频等文件适用。通过减少图片视频文件的分辨率,降低音频的音质等手段,以减少文件的大小,来实现减少加载时间和带宽消耗。
无损压缩:对一些零碎文件可以使用 zlib(Zip模块)来进行打包压缩,减少上传请求次数;对一些大文件可以利用缓存技术,服务器将曾经上传过的大文件MD5码缓存起来,本地在上传前预生成MD5码并传输到服务器进行比对,如果相同则说明服务器存在该文件,可以跳过该文件上传,从而省略重复传输时间。
以批量上传照片(分辨率为480*640,24位,平均大小50~120KB)为例,在设备上测试的结果如下表所示:
图2 上传数量和耗时对比图表
由于上传耗时受到网络状态影响,偏差较大,结果取的几次测量结果的最小值。但是仍然可以从数据中看出,优化前的耗时基本为线性增长,压缩优化后的耗时在上传文件数量较低时并不明显,还会因为多余的压缩处理影响耗时。不过随着上传的照片数量增多,优化后的耗时和优化之前的耗时差距越来越明显,优化效果越好。
数据压缩的相关示例代码如下:
导入相关模块:
import { common } from '@kit.AbilityKit';
import { fileIo } from '@kit.CoreFileKit'
import { zlib } from '@kit.BasicServicesKit';
创建压缩上传相关类:
class ZipUpload {// 创建任务前存放的uriprivate waitList: Array<string> = [];// 需要上传的文件uriprivate fileUris: Array<string> = [];...
}
建立用于接收图库图片的临时文件夹,并将整个临时文件夹打包添加到待上传list内:
// 文件压缩处理
async zipUploadFiles(fileUris: Array<string>): Promise<void> {this.context = getContext(this) as common.UIAbilityContext;let cacheDir = this.context.cacheDir;let tempDir = fileIo.mkdtempSync(`${cacheDir}/XXXXXX`);// 将图库图片获取的uri放入fileUris中,遍历复制到临时文件夹for (let i = 0; i < fileUris.length; i++) {let fileName = fileUris[i].split('/').pop();let resourceFile: fileIo.File = fileIo.openSync(fileUris[i], fileIo.OpenMode.READ_ONLY);fileIo.copyFileSync(resourceFile.fd, `${tempDir}/${fileName}`, 0);fileIo.closeSync(resourceFile);}// 文件压缩,将之前生成的临时文件夹内打包到test.zip内let options: zlib.Options = {level: zlib.CompressLevel.COMPRESS_LEVEL_DEFAULT_COMPRESSION,memLevel: zlib.MemLevel.MEM_LEVEL_DEFAULT,strategy: zlib.CompressStrategy.COMPRESS_STRATEGY_DEFAULT_STRATEGY};let data = await zlib.compressFile(tempDir, `${cacheDir}/test.zip`, options);// 删除临时文件夹fileIo.rmdirSync(tempDir);// 将生成的zip包放到传输队列this.waitList.push(`${cacheDir}/test.zip`);
}
(二)断点续传
断点续传功能的实现,不管是应用端还是服务器端都需要用到合理的技术来互相协同。在实际开发中,开发者无需亲自实现断点续传功能,只需对SDK进行合理配置。
在应用端需要用到的技术和API:
fileIo(文件管理):用于处理文件上传操作,提供了读取文件内容,文件分片和组合的功能。
hash(文件哈希处理):用于实现文件MD5的计算,将计算的MD5值预先传到服务器端进行预处理,实现文件秒传,同时确保传输的准确性和可靠性。
request(上传下载):用于实现文件上传操作,并支持在上传过程中的断点续传功能。
在服务器端需要用到的技术:
协议需要支持Range:用于在服务器端支持范围请求,方便处理文件上传下载断点续传功能。
文件校验相关逻辑:需要实现校验文件是否有错,确保在传输中断后能够准确恢复并继续传输。
通过结合应用端和服务器端的相关技术,可以共同实现高效且可靠的文件断点续传功能,提供更好的用户体验并确保数据传输的稳定性。
本文基于上传和下载中的后台上传场景,给出了部分断点续传的示例代码,具体可以参考该工程。
(三)文件上传
对于大文件断点续传上传,本文采用request(上传下载)模块中的request.agent任务托管接口,可以自动实现暂停继续重试等操作,无需手动将文件分片和记录上传分片信息。流程图如下图所示:
图3 断点续传上传流程图
导入相关模块:
import { common } from '@kit.AbilityKit';
import { request } from '@kit.BasicServicesKit';
创建相关上传类:
class Upload {// 后台任务private backgroundTask: request.agent.Task | undefined = undefined;// 创建任务前存放的uriprivate waitList: Array<string> = [];...
}
配置Config,创建后台上传任务:
private config: request.agent.Config = {action: request.agent.Action.UPLOAD,headers: HEADER,url: '',mode: request.agent.Mode.BACKGROUND,method: 'POST',title: 'upload',network: request.agent.Network.ANY,data: [],token: 'UPLOAD_TOKEN'
}
...
// 转换uri
private async getFilesAndData(cacheDir: string, fileUris: Array<string>): Promise<Array<request.agent.FormItem>> {
...
}
// 创建文件上传后台任务
async createBackgroundTask(fileUris: Array<string>) {if (this.context === undefined) {return;}// 获取上传urlthis.config.url = await urlUtils.getUrl(this.context);this.config.data = await this.getFilesAndData(this.context.cacheDir, fileUris);this.config.mode = request.agent.Mode.BACKGROUND;try {this.backgroundTask = await request.agent.create(this.context, this.config);await this.backgroundTask.start();let state = AppStorage.get<number>('backTaskState');if (state === BackgroundTaskState.PAUSE) {await this.backgroundTask.pause();}logger.info(TAG, `createBackgroundTask success`);} catch (err) {logger.error(TAG, `task err, err = ${JSON.stringify(err)}`);}
}
任务开始:
await this.backgroundTask.start();
任务暂停:
async pause() {...if (this.backgroundTask === undefined) {return;}await this.backgroundTask.pause();
}
任务继续
async resume() {...if (this.backgroundTask === undefined) {return;}await this.backgroundTask.resume();
}
本文主要引用参考HarmonyOS官方文档