HarmonyOS 7 网络任务与后台传输实战 01:用 RequestAgent 实现后台下载与断点续传

之前做了个应用,要下载一些离线资源包。文件不大的时候,直接用 http 请求下,切个后台也没事。后来换成 500MB 的资源包,问题就全出来了——用户切到后台,下载直接停了;锁屏之后再回来,连接断了;网络抖一下,整个下载任务就挂了,得从头再来。
普通的 http 请求跑前台没问题,真要跑大文件后台下载,根本扛不住。后来换了 RequestAgent,把下载任务交给系统管理,才算是把这件事真正做稳了。这篇就讲讲从"能下载"到"后台也能稳定下载"中间踩的那些坑。
一、普通下载跑得好好的,为什么还要 RequestAgent
最开始我就是用 http.createHttp() 直接下的。代码很简单,发个 GET 请求,把返回的数据写到文件里。小文件测着没问题,10MB 的包几秒钟就下完了。
后来换成 500MB 的资源包,问题马上就来了:
用户切到后台,应用一进后台,系统就把网络连接掐了。等用户再切回来,连接早就断了,文件下到一半就停了。
锁屏更严重。手机一锁屏,CPU 都休眠了,下载线程直接被挂起。等用户解锁回来,TCP 连接早就超时了,根本不知道下到哪了。
还有更烦的:用户不小心杀了应用,下载直接停了。再打开应用,只能重新下。500MB 的文件,白下了一半。
这些问题靠自己写重试逻辑根本解决不了——应用都被系统杀了,你拿什么重试?这种事只能交给系统来管。RequestAgent 就是系统层面的下载任务管理,应用退到后台、甚至被杀了,下载任务系统还帮你跑着。
二、先把一个后台下载任务真正跑起来
RequestAgent 的思路和普通 http 请求不一样。你不是直接发请求,而是先创建一个下载任务,把 URL、保存路径、参数都告诉系统。系统帮你管整个下载过程,你只需要监听进度和状态。
这里有个关键认知:下载任务是系统级的,不是应用级的。应用退了、杀了,任务还在。系统会在后台继续下,下完了再通知应用。
我们封装的下载管理器放在 entry/src/main/ets/download/DownloadManager.ets,页面只负责调用,不要把所有任务逻辑都塞进 UI。
import { request } from '@kit.BasicServicesKit';
export class DownloadManager {
private static agent: request.RequestAgent | null = null;
// 初始化下载代理
static async initAgent(): Promise<void> {
if (!this.agent) {
this.agent = await request.createRequestAgent(
'download_agent',
{}
);
}
}
// 创建下载任务
static async createDownloadTask(
url: string,
savePath: string,
taskId: string
): Promise<number> {
// 先查一下这个任务是不是已经存在
const existing = await this.getTaskById(taskId);
if (existing) {
console.info('Task already exists, id: ' + existing.taskId);
return existing.taskId;
}
const config: request.DownloadConfig = {
url: url,
filePath: savePath,
title: '资源下载',
description: taskId,
network: request.NetworkType.NETWORK_WIFI | request.NetworkType.NETWORK_MOBILE,
overwrite: false, // 不覆盖已有文件
retry: {
retryCount: 3, // 失败自动重试3次
retryDelay: 2000
}
};
const task = await this.agent!.createDownload(config);
console.info('Download task created: ' + task.taskId);
// 保存任务 ID,方便后面查询和恢复
await this.saveTaskMapping(taskId, task.taskId);
return task.taskId;
}
// 查询任务状态
static async getTaskById(
localTaskId: string
): Promise<request.DownloadTask | null> {
const systemTaskId = await this.getSystemTaskId(localTaskId);
if (!systemTaskId) return null;
const task = await this.agent!.getTask(systemTaskId);
return task || null;
}
// 任务状态查询
static async queryTaskStatus(
localTaskId: string
): Promise<TaskStatus> {
const task = await this.getTaskById(localTaskId);
if (!task) {
return { state: 'unknown', progress: 0 };
}
const progress = task.progress.processed / task.progress.totalSize * 100;
return {
state: task.state,
progress: Math.round(progress),
totalSize: task.progress.totalSize,
processed: task.progress.processed
};
}
}
// 任务状态
interface TaskStatus {
state: request.DownloadState;
progress: number;
totalSize?: number;
processed?: number;
}
这段代码的核心:createDownloadTask 先查有没有已存在的任务,避免重复创建。overwrite 设成 false,文件已经存在就不重复下了。retry 配置让系统在失败的时候自动重试,不用自己写重试逻辑。
真正运行的时候要注意:systemTaskId 和我们业务里的 taskId 是两回事,要存一个映射表。不然应用重启之后,系统任务 ID 就找不到了。

三、暂停不难,难的是回来以后还能不能接着下
暂停和恢复接口本身很简单,task.pause()、task.resume() 两行代码的事。但真做的时候,问题不在接口,在状态同步。
用户点了暂停,我们 UI 上马上显示"已暂停"。但系统那边可能还在往缓冲里写数据,过了几百毫秒才真正停。这时候用户马上点恢复,两个操作撞在一起,状态就乱了。
还有更麻烦的:应用杀了之后再打开,怎么知道之前那个任务是暂停了还是在下?
这个别急着重新创建任务,先看看之前的任务还在不在。RequestAgent 的任务是持久化的,应用重启之后,之前创建的任务还能查到。
// 在 DownloadManager 里补充:恢复任务
static async resumeTask(
localTaskId: string
): Promise<void> {
const task = await this.getTaskById(localTaskId);
if (!task) {
console.error('Task not found: ' + localTaskId);
return;
}
// 先查当前状态
const status = await task.getTaskInfo();
switch (status.state) {
case request.DownloadState.DOWNLOAD_STATE_PAUSED:
// 暂停状态,直接恢复
await task.resume();
console.info('Task resumed');
break;
case request.DownloadState.DOWNLOAD_STATE_RUNNING:
// 已经在跑了,不用恢复
console.info('Task already running');
break;
case request.DownloadState.DOWNLOAD_STATE_COMPLETED:
// 已经下完了
console.info('Task already completed');
break;
case request.DownloadState.DOWNLOAD_STATE_FAILED:
// 失败了,重新开始
await task.start();
console.info('Task restarted after failure');
break;
default:
console.warn('Unknown state: ' + status.state);
}
}
这里的关键:恢复任务之前先查状态。已经在跑的不要重复 resume,失败了的要重新 start,下完了的就不用管了。别上来就调 resume,状态不对会报错。

四、断点续传到底是怎么回事
很多人以为断点续传要自己写 Range 头,自己记录下到哪个字节。其实 RequestAgent 已经帮你做了。
只要你创建任务的时候用的是同一个保存路径,任务暂停了、断了、应用杀了,下次恢复的时候,系统会自动从上次停的地方继续下,不用从头来。
这里有个坑:文件路径要固定。你这次下载存到 /cache/a.zip,下次恢复如果路径变了,系统就找不到之前下了一半的文件,只能重新下。所以保存路径要用业务 ID 固定下来,不要每次都生成新文件名。
还有一个:任务完成之后,那个临时文件要处理一下。RequestAgent 下完之后,文件就在你指定的路径了,但有些时候是临时格式,要自己 rename 成正式文件名,或者移到正式目录。
五、任务完成以后,别忘了解绑和清理
下载完了就完事了?没那么简单。
任务下完了,你要监听完成事件,拿到文件之后做后续处理——解压、校验 MD5、更新数据库状态。这些都做完了,记得把任务从 RequestAgent 里删掉,别留着占资源。
失败的任务也要清理。重试了三次还是失败,这个任务基本就是废了。要么提示用户重新下,要么删掉任务记录。别让一堆失败任务堆在系统里。
还有个容易忽略的:应用卸载的时候,系统下载任务会不会跟着删?这个不用你管,系统会处理。但如果你换了设备、清了数据,之前的任务 ID 就没用了,要重新创建。
说白了,后台下载这件事,代码本身不复杂,麻烦的是任务状态。什么时候该暂停、什么时候该恢复、什么时候该重新创建、什么时候该清理,每一步都想清楚了,下载功能才真正稳。
下一篇讲更麻烦的事——弱网和网络切换。正常 Wi-Fi 下一切正常,一切到移动网络或者信号差的地方,各种问题就全冒出来了。
更多推荐




所有评论(0)