当前位置: 首页 > news >正文

HarmonyOS技术精讲-Network Kit(网络服务)| 02 文件传输大师:上传下载与断点续传

HarmonyOS技术精讲-Network Kit(网络服务)| 02 文件传输大师:上传下载与断点续传

开篇:上传下载不只是调一下API

HarmonyOS开发里,上传下载算是最基础的需求了。但很多人第一次接触@ohos.net.netmanager时,会发现一个现象:官方示例能跑起来,真机上一测试,要么下载到一半进度不动了,要么上传大文件时直接崩溃。

这个问题在HarmonyOS NEXT开发中比较常见。Network Kit提供了UploadTaskDownloadTask两个核心类,但它们的真正使用边界和生命周期管理,官方文档描述得比较简略。这篇文章会从两个实际场景出发——头像上传(multipart表单)和视频文件下载(带断点续传),把完整的实现逻辑拆开讲清楚。

这个能力解决了什么问题

Network Kit的文件传输能力,本质上是对底层网络IO的封装。它把HTTP请求、进度监听、任务暂停/恢复等操作抽象成了统一的API,开发者不需要自己拼装request body,也不需要手动管理socket连接。

适用场景:

  • 常规文件上传(图片、音频、视频)
  • 需要进度回调的下载任务
  • 需要断点续传的场景

不适用场景:

  • 实时音视频流传输(需要用WebSocket)
  • 超高频的小文件批量上传(建议用并发池)

和直接用http.request相比:

方案大文件支持进度回调断点续传内存管理
http.request容易OOM需要自己实现不支持全量加载
Network Kit流式写入原生支持有API支持按块处理

对于文件传输类需求,Network Kit是更稳定的选择。

环境说明

DevEco Studio 版本:DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本:HarmonyOS 6.1.0(23) 及以上 目标设备:手机、平板

核心实现:上传头像(multipart表单)

第一步:权限配置

module.json5中声明网络权限:

{"module":{"requestPermissions":[{"name":"ohos.permission.INTERNET"}]}}

第二步:构建上传任务

// UploadManager.etsimport{fileIo}from'@kit.CoreFileKit';import{netmanager}from'@kit.NetworkKit';exportclassUploadManager{/** * 上传头像文件,使用multipart/form-data格式 * @param fileUri 文件在沙箱中的URI * @param progressCallback 进度回调,参数为已上传字节数 */staticasyncuploadAvatar(fileUri:string,progressCallback:(sent:number)=>void):Promise<netmanager.UploadTask>{// 1. 构造上传数据constfile=fileIo.openSync(fileUri,fileIo.OpenMode.READ_ONLY);constfileSize=fileIo.statSync(fileUri).size;// 2. 配置上传任务constuploadConfig:netmanager.UploadConfig={url:'https://your-server.com/api/avatar/upload',header:{'Content-Type':'multipart/form-data'},method:'POST',files:[{filename:'avatar.jpg',name:'file',// 表单字段名uri:fileUri,type:'image/jpeg'}],data:[{name:'user_id',value:'12345'}]};// 3. 创建上传任务constuploadTask=awaitnetmanager.createUploadTask(uploadConfig);// 4. 注册进度回调uploadTask.on('progress',(uploadedSize:number,totalSize:number)=>{// 注意:totalSize可能为0,需要从文件实际大小获取if(totalSize>0){constpercent=Math.round((uploadedSize/totalSize)*100);// eg: 日志输出 "上传进度: 45%"}progressCallback(uploadedSize);});// 5. 开始上传uploadTask.start();returnuploadTask;}}

注意:

  • files数组中的name字段对应服务端表单的key
  • totalSize在部分场景可能返回0,建议用fileSize做兜底
  • 创建任务后需要主动调用start()

核心实现:视频下载与断点续传

断点续传的核心逻辑:下载前检查本地是否已有部分文件,如果有,告诉服务端从哪个字节开始传,然后追加写入。

// DownloadManager.etsimport{fileIo}from'@kit.CoreFileKit';import{netmanager}from'@kit.NetworkKit';exportclassDownloadManager{// 保存任务状态privatestatictaskMap=newMap<string,{task:netmanager.DownloadTask;filePath:string;downloadedBytes:number;}>();/** * 下载视频,支持断点续传 * @param url 下载URL * @param savePath 保存路径(含文件名) * @param progressCallback 进度回调 */staticasyncdownloadVideo(url:string,savePath:string,progressCallback:(progress:number)=>void):Promise<void>{// 1. 检查本地是否存在部分下载文件letdownloadedBytes=0;letfileExists=false;try{conststat=fileIo.statSync(savePath);if(stat.size>0){downloadedBytes=stat.size;fileExists=true;}}catch{// 文件不存在,从头开始下载}// 2. 创建下载配置constdownloadConfig:netmanager.DownloadConfig={url:url,filePath:savePath,header:{// 关键:断点续传需要带上Range头'Range':`bytes=${downloadedBytes}-`},enableMetered:true,// 允许在移动网络下下载enableRoaming:true// 允许漫游时下载};// 3. 创建下载任务constdownloadTask=awaitnetmanager.createDownloadTask(downloadConfig);// 4. 保存任务引用this.taskMap.set(url,{task:downloadTask,filePath:savePath,downloadedBytes:downloadedBytes});// 5. 注册进度回调downloadTask.on('progress',(receivedSize:number,totalSize:number)=>{// receivedSize是本次下载的增量,不是总大小lettotalDownloaded=downloadedBytes+receivedSize;if(totalSize>0){constpercent=Math.round((totalDownloaded/totalSize)*100);progressCallback(percent);}});// 6. 注册完成回调downloadTask.on('complete',()=>{console.log('下载完成');this.taskMap.delete(url);});// 7. 注册失败回调downloadTask.on('fail',(err:Error)=>{// 网络中断等情况// 下次启动时,通过检查本地文件大小自动续传});// 8. 开始下载downloadTask.start();}/** * 暂停下载 */staticpauseDownload(url:string):void{constentry=this.taskMap.get(url);if(entry){entry.task.pause();}}/** * 恢复下载 */staticresumeDownload(url:string):void{constentry=this.taskMap.get(url);if(entry){entry.task.resume();}}}

为什么这样设计:

  • taskMap保存活跃任务,方便管理暂停和恢复
  • Range头是实现断点续传的关键,服务端需支持HTTP 206 Partial Content
  • enableMeteredenableRoaming是HarmonyOS特有的配置项,不设置可能在某些网络下被拦截

常见问题

问题1:进度回调不触发

现象:注册了progress事件,但上传/下载过程中回调不触发。

原因:progress回调在子线程执行,如果回调中执行了UI操作(如修改@State变量),ArkUI的线程安全机制会阻止更新。

解决方案:使用emitBySystem方式或者通过@Watch监听状态变化。推荐做法:

// 使用类变量存储进度值classDownloadModel{progress:number=0;}@StatedownloadModel:DownloadModel=newDownloadModel();// 在回调中:downloadTask.on('progress',(receivedSize:number,totalSize:number)=>{AppStorage.set<number>('downloadProgress',percent);});

然后在页面中使用@StorageLink@LocalStorageLink监听。

问题2:断点续传不生效,总是从头下载

现象:设置了Range头,但响应依然是200而非206,文件从0开始下载。

原因:官方文档没有明确说明,但实际测试中,header中的Range头如果直接设置,在某些情况下会被底层忽略。正确做法是:不显式设置Range,而是通过filePath参数结合文件是否存在来判断。

解决方案:先创建空文件,再通过fileIo写入时追加。或者确认服务端支持206:

// 更可靠的方案:下载前先尝试HEAD请求获取服务端支持constresponse=awaithttp.httpRequest(url,{method:'HEAD'});constacceptRanges=response.header['Accept-Ranges'];if(acceptRanges!=='bytes'){// 服务端不支持断点续传,重新下载}

问题3:下载大文件时应用被系统杀死

现象:下载1GB以上文件时,应用退到后台一段时间后被系统回收。

原因:HarmonyOS为了节省资源,会杀后台应用。下载任务需要注册为长时任务。

解决方案:使用@ohos.backgroundTaskManager申请长时任务许可:

import{backgroundTaskManager}from'@kit.BackgroundTasksKit';// 在下载开始前申请constrequestId=backgroundTaskManager.requestBackgroundTaskRunning(backgroundTaskManager.BackgroundTaskType.DOWNLOAD,{begin:0,interval:180000// 每3分钟延长一次});

最佳实践

  1. 文件路径优先使用沙箱路径:不要使用/data/storage/el2/base这样的绝对路径,用getContext().cacheDirgetContext().filesDir。不同应用间路径不可通用。

  2. 下载任务完成后立即销毁监听:频繁注册progress事件但不移除,会在页面销毁后继续触发回调,导致undefined错误。用完记得调用downloadTask.off('progress')

  3. 批量上传时控制并发数:不要让多个上传任务同时发起,建议使用队列或信号量控制并发为3-5个。超过这个数量,ArkUI的渲染线程可能出现卡顿。

入口文件示例

// pages/TransferPage.ets@Entry@Componentstruct TransferPage{@StateuploadProgress:number=0@StatedownloadProgress:number=0build(){Column(){// 上传头像按钮Button('选择头像上传').onClick(async()=>{// 这里需要配合文件选择器获取fileUriconstfileUri=awaitthis.getAvatarUri();awaitUploadManager.uploadAvatar(fileUri,(sent)=>{this.uploadProgress=sent;});})// 下载视频按钮Button('下载视频').onClick(async()=>{constsavePath=getContext().cacheDir+'/videos/sample.mp4';awaitDownloadManager.downloadVideo('https://example.com/sample.mp4',savePath,(progress)=>{this.downloadProgress=progress;});})}.padding(20)}// 获取头像文件URI的占位方法,实际需要调用文件选择器APIasyncgetAvatarUri():Promise<string>{// 略return'';}}

FAQ

Q:为什么真机可以正常下载,模拟器上却提示网络错误?
A:模拟器可能无法正确处理部分网络包的校验。真机环境更接近生产,建议以真机测试结果为准。

Q:上传文件时,totalSize总是返回0怎么回事?
A:这可能是底层实现的一个限制。建议在创建UploadConfig时,从文件系统读取实际大小作为参考值。

Q:页面销毁后,下载任务还在跑,如何清理?
A:在页面的aboutToDisappear生命周期中,调用downloadTask.off()downloadTask.off('complete')移除所有回调。

http://www.jsqmd.com/news/1147660/

相关文章:

  • Linux shell编程基础第五次作业——理解shell
  • WSEN-ISDS与PIC32MZ的6DOF运动追踪方案详解
  • Forecast-MAE 自监督预训练:Argoverse 2 上 minADE1 提升 5.1% 的 4 步配置解析
  • 过敏性鼻炎调理市场迎来新变化 牛初乳免疫干预成消费关注焦点
  • 森海塞尔Momentum 5与Bose QuietComfort Ultra(第二代)对比:该选哪款耳机?
  • 【技术前沿】Gen6 SSD怎么测?一文看懂 NVMe SSD 测试全景
  • 【新手落地】 OpenClaw 桌面 Agent,全程可视化安装操作流程(含安装包)
  • IIM-20670与CEC1302的硬件协同与运动跟踪优化
  • 2026白银黄金回收白银回收铂金回收市民首选无隐形扣费正规备案回收门店联系方式推荐
  • IIM-20670运动传感器与dsPIC33FJ256GP710A微控制器应用指南
  • 智能企业AI建站工具从零到上线:全流程保姆级攻略
  • Linux网络故障定位汇总
  • TDA7468与PIC18F2525音频控制系统设计与优化
  • Krita AI Diffusion终极优化指南:让CPU渲染速度提升300%的完整方案
  • Windows 11 CUDA 环境配置:从驱动检查到PyTorch 2.3验证的5步完整流程
  • ICM-42605与TM4C129EKCPDT实现高精度6DOF运动追踪
  • 【AI学习之旅01】开篇:为什么我要学AI——学习动机、目标设定与完整路线图
  • AD7490与PIC18F46K22实现高精度多通道数据采集方案
  • BMI160与TM4C1294NCZAD构建高精度运动检测系统
  • virtCCA_driver深度解析:3大核心功能详解(KAE加速、密钥封装、安全内存监控)
  • WSEN-ISDS 6自由度IMU传感器与PIC18F46K22的集成应用
  • Full Page Screen Capture:终极指南,一键获取完整网页截图
  • 云端算力平台全景扫描—预算与全场景兼顾算力平台全踩坑总结
  • 终极Photoshop AI插件SD-PPP完整指南:如何在5分钟内开启智能设计革命
  • 6DOF运动追踪系统设计与IMU数据处理实战
  • 3分钟让QQ音乐文件告别平台限制:QMCDecode实现跨设备音频自由
  • 充放电测试系统选型:100V~1000V电压/电流/功率三角怎么取舍?
  • PyTorch EMA 模型权重平滑:torch.lerp 实现与 Adam 优化器 0.999 参数对比
  • 鸣潮工具箱:如何彻底解决游戏性能与账号管理的双重困境?
  • IIM-20670与MKV42F128VLH16的硬件协同设计与运动跟踪实现