/** * 文件下载器(框架 §8.3 / §9)。封装 @kit.NetworkKit http 流式请求 requestInStream。 * * 边收边写盘(不全量入内存,支持大 zip),经回调汇报百分比进度。 * http 流式请求为**原生异步、不阻塞 UI 线程**——故无需 TaskPool 即满足"下载不阻塞 UI"。 * 进度回调在 UI 线程触发(http 回调即在调用线程),调用方可直接更新 UI。 */ import { http } from '@kit.NetworkKit'; import { fileIo as fs } from '@kit.CoreFileKit'; import { FileSystem } from '../fs/FileSystem'; import { Logger } from 'common'; /** 下载进度回调:percent 为 0~100 整数(总长未知时回 -1)。 */ export type ProgressCallback = (percent: number) => void; export class Downloader { private static readonly log: Logger = Logger.tag('Downloader'); /** 下载 url 到 savePath(覆盖旧文件),可选进度回调。失败抛出。 */ static async download(url: string, savePath: string, onProgress?: ProgressCallback): Promise { FileSystem.ensureDir(FileSystem.dirOf(savePath)); FileSystem.rmrf(savePath); const file: fs.File = fs.openSync(savePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE | fs.OpenMode.TRUNC); let offset: number = 0; const req: http.HttpRequest = http.createHttp(); req.on('dataReceive', (chunk: ArrayBuffer) => { fs.writeSync(file.fd, chunk, { offset }); offset += chunk.byteLength; }); if (onProgress !== undefined) { req.on('dataReceiveProgress', (info: http.DataReceiveProgressInfo) => { const pct: number = info.totalSize > 0 ? Math.floor(info.receiveSize * 100 / info.totalSize) : -1; onProgress(pct); }); } try { const code: number = await req.requestInStream(url, { method: http.RequestMethod.GET, usingCache: false, connectTimeout: 15000, readTimeout: 300000, }); if (code < 200 || code >= 300) { throw new Error(`download http ${code}`); } Downloader.log.i(`download ${url} -> ${savePath} (${offset} bytes)`); } finally { req.off('dataReceive'); if (onProgress !== undefined) { req.off('dataReceiveProgress'); } req.destroy(); fs.closeSync(file); } } }