HarmonyOS技术精讲-Network Kit(网络服务)| 01 初识HTTP请求:从零开始获取数据

发布时间:2026/7/7 1:49:41
HarmonyOS技术精讲-Network Kit(网络服务)| 01 初识HTTP请求:从零开始获取数据 从一个困惑点开始很多人在 HarmonyOS NEXT 开发里第一次接触 HTTP 请求时会遇到一个尴尬的情况官方示例能跑通返回的 JSON 也能打印出来但下一步就卡住了——拿到数据之后怎么传给 UI这部分官方文档写得很克制点到为止。另一个常见问题是请求发出去之后页面返回了但请求回调还在执行导致状态更新到已销毁的组件上直接报undefined错误。这篇文章不讲空话直接通过一个完整的新闻列表加载功能把ohos.net.http的完整链路走一遍。包括创建请求、设置头/体、解析响应、Cookie 处理以及上面提到的坑怎么绕过去。它解决什么问题HTTP 数据请求是移动端最基础的能力。无论是登录、加载列表、提交表单底层走的都是 HTTP/HTTPS 协议。ohos.net.http这个模块提供的是 HarmonyOS 原生网络请求能力不依赖第三方库。它解决的问题很明确在 ArkTS 环境下发起网络请求获取服务端数据。适合场景常规 RESTful API 调用JSON 格式文件上传/下载需要额外配置Cookie 需要持久化的场景不适合场景WebSocket 长连接这个应该用ohos.net.webSocket流式响应处理需要自己实现分块读取和第三方库比如 axios对比ohos.net.http的优势是不需要额外引入依赖项目初始化就能用且对底层网络栈的控制更直接。缺点是 API 设计偏底层缺少一些语法糖比如没有自动序列化、拦截器等。对比项ohos.net.http第三方库如 axios 的移植版依赖引入无需要单独引入拦截器无有自动 JSON 解析无有Cookie 管理原生支持需要额外处理性能原生调用额外封装层对于大多数业务场景原生模块已经完全够用。环境说明DevEco Studio 版本DevEco Studio 6.1.0 及以上 HarmonyOS SDK 版本HarmonyOS 6.1.0(23) 及以上 目标设备手机核心实现新闻列表加载假设接口返回的数据格式如下{code:200,data:[{id:1,title:标题1,source:来源A,time:2024-01-01},{id:2,title:标题2,source:来源B,time:2024-01-02}]}第一步创建 HTTP 请求并获取数据这一段主要用于封装网络请求逻辑。把它单独抽成一个文件方便复用。// src/main/ets/common/http/NewsHttp.tsimporthttpfromohos.net.http;import{BusinessError}fromkit.BasicServicesKit;// 定义响应数据结构exportinterfaceNewsItem{id:number;title:string;source:string;time:string;}exportinterfaceNewsResponse{code:number;data:NewsItem[];}exportclassNewsHttp{privatehttpRequest:http.HttpRequest;constructor(){// 创建 HTTP 请求对象一个请求任务只使用一次用完必须销毁this.httpRequesthttp.createHttp();}asyncfetchNews():PromiseNewsResponse{try{// 配置请求参数consturlhttps://api.example.com/news/list;constrequestOption:http.HttpRequestOptions{method:http.RequestMethod.GET,header:{Content-Type:application/json,Accept:application/json},connectTimeout:10000,// 连接超时 10 秒readTimeout:10000,// 读取超时 10 秒usingProtocol:http.HttpProtocol.HTTP1_1,// 使用 HTTP/1.1};// 发起请求constresponse:http.HttpResponseawaitthis.httpRequest.request(url,requestOption);// 检查响应状态码if(response.responseCode!200){thrownewError(请求失败状态码:${response.responseCode});}// 解析 JSON 数据constresult:NewsResponseJSON.parse(response.resultasstring);returnresult;}catch(error){// 统一捕获网络错误和解析错误consterrerrorasBusinessError;console.error(新闻请求失败:${err.message});throwerror;}finally{// 无论成功失败都必须销毁请求对象释放资源this.httpRequest.destroy();}}}注意事项http.createHttp()创建的请求对象是单次任务绑定的每次请求完成后必须调用destroy()否则持续占用资源可能导致内存泄漏。connectTimeout和readTimeout的默认值可能不够用建议根据业务场景显式设置。默认值在低信号环境下容易超时。response.result的类型是Object解析时需要注意。如果接口返回的是非 JSON 格式JSON.parse会报错。第二步在页面中调用并渲染页面组件负责管理 UI 状态和调用网络请求。// src/main/ets/pages/NewsListPage.etsimport{NewsHttp,NewsItem,NewsResponse}from../common/http/NewsHttp;EntryComponentstruct NewsListPage{StatenewsList:NewsItem[][];StateisLoading:booleantrue;aboutToAppear(){this.loadNews();}asyncloadNews(){this.isLoadingtrue;try{consthttpClientnewNewsHttp();constresponse:NewsResponseawaithttpClient.fetchNews();// 拿到数据后更新状态触发 UI 刷新this.newsListresponse.data;}catch(error){console.error(加载新闻失败:${error.message});// 这里可以加入 Toast 提示但不影响 UI 渲染}finally{this.isLoadingfalse;}}build(){Column(){if(this.isLoading){Progress({value:0,type:ProgressType.Linear}).width(80%).margin(20);}elseif(this.newsList.length0){Text(暂无新闻数据).margin(20);}else{List({space:12}){ForEach(this.newsList,(item:NewsItem){ListItem(){Column(){Text(item.title).fontSize(18).fontWeight(FontWeight.Bold).width(100%);Row(){Text(item.source).fontSize(14).fontColor(#666);Text(item.time).fontSize(14).fontColor(#999);}.width(100%).justifyContent(FlexAlign.SpaceBetween).margin({top:8});}.padding(12).width(100%).backgroundColor(#F5F5F5).borderRadius(8);}},(item:NewsItem)item.id.toString());}.width(100%).padding(12);}}.width(100%).height(100%).backgroundColor(#FFFFFF);}}关键点aboutToAppear是在页面显示之前调用的生命周期适合发起初始数据请求。State修饰的变量变化会自动触发 UI 重新渲染不需要手动调用刷新接口。ForEach中的键值必须唯一且稳定否则列表会出现闪烁或重复渲染。这里用item.id.toString()作为键。第三步处理 POST 请求和 Cookie上面是 GET 请求实际项目中 POST 也常见尤其是登录场景。// src/main/ets/common/http/LoginHttp.tsimporthttpfromohos.net.http;import{BusinessError}fromkit.BasicServicesKit;exportinterfaceLoginRequest{username:string;password:string;}exportinterfaceLoginResponse{token:string;}exportclassLoginHttp{privatehttpRequest:http.HttpRequest;constructor(){this.httpRequesthttp.createHttp();}asynclogin(params:LoginRequest):PromiseLoginResponse{try{consturlhttps://api.example.com/login;constrequestOption:http.HttpRequestOptions{method:http.RequestMethod.POST,header:{Content-Type:application/json},extraData:JSON.stringify(params),// POST 请求体必须是字符串// 使用默认 CookieStore会自动管理 SessionusingCookie:true,};constresponseawaitthis.httpRequest.request(url,requestOption);if(response.responseCode!200){thrownewError(登录失败状态码:${response.responseCode});}// 手动设置 Cookie后续请求带上constcookiesresponse.cookies;if(cookiescookies.length0){// CookieStore 是全局的会自动处理http.CookieStore.setCookie(url,cookies[0]);}constresult:LoginResponseJSON.parse(response.resultasstring);returnresult;}catch(error){consterrerrorasBusinessError;console.error(登录请求失败:${err.message});throwerror;}finally{this.httpRequest.destroy();}}}关于 Cookie 管理的几个点usingCookie: true表示开启自动 Cookie 管理。默认使用全局的CookieStore所有请求共享 Cookie。如果只是登录后获取 Cookie全局 CookieStore 会自动存储后续请求不需要手动设置。如果服务端返回了Set-Cookieresponse.cookies数组里会包含值手动调用CookieStore.setCookie也可以但全局模式下会自动处理。特别提醒CookieStore是当前进程内的应用重启后 Cookie 会丢失。如果需要持久化需要自己存到Preferences或数据库里。常见问题与踩坑记录问题 1请求任务未及时销毁导致内存泄漏现象页面反复打开/关闭内存持续增长最终 OOM。原因http.createHttp()每次调用都会创建一个底层 Socket 连接如果不调用destroy()这些连接会一直保持直到进程结束。解法严格按照“创建-使用-销毁”的生命周期管理。在finally块里调用destroy()确保无论成功还是出错都会释放。如果使用类封装在类的析构函数或页面aboutToDisappear里也加上destroy()兜底。aboutToDisappear(){// 页面销毁时确保请求对象被释放if(this.httpRequest){this.httpRequest.destroy();}}问题 2响应编码问题导致中文乱码现象接口返回的中文显示为\uXXXX或乱码。原因默认情况下http.request返回的result是string类型但底层编码可能与预期不一致。解法服务端响应头里加上charsetutf-8大多数情况下问题能解决。如果需要强制指定解码可以用buffer模式接收然后手动解码。但这个在ohos.net.http里较复杂建议优先让服务端配合。// 服务端返回的 Content-Type 必须明确指定编码requestOption.header[Content-Type]application/json; charsetutf-8;问题 3模拟器请求正常真机失败现象在模拟器上能拿到数据但真机上报网络连接失败。原因真机需要网络权限并且需要处理 HTTPS 证书验证问题。解法检查module.json5中是否配置了ohos.permission.INTERNET。如果是自签名证书需要配置usingHttpDns或caPath。生产环境建议使用正规证书。真机网络环境复杂建议在真机上测试时关闭代理或者使用抓包工具如 Charles时检查证书是否正确安装。最佳实践1. 不要在 build() 中创建 HTTP 请求对象build()方法在每次状态变化时都可能被重新调用如果在里面创建请求对象会导致大量无效的对象创建增加内存压力。把网络请求封装到独立的 Service 类中并在页面生命周期中调用。2. 统一管理超时时间每个接口的超时时间不一定相同。上传图片的超时时间应该比普通 GET 请求长。建议把超时时间配置到配置文件里或者在 Service 类中作为参数传入。// 推荐把超时时间作为构造参数传入constructor(timeout:number10000){this.httpRequesthttp.createHttp();this.timeouttimeout;}3. 使用 try-catch 包裹整个请求链路网络请求是不可靠的。无论是网络断开、服务器异常还是 JSON 解析失败都应该被try-catch捕获并给出友好的用户提示。不要在异步回调里直接修改 UI 状态否则页面销毁后状态更新会报错。FAQQ为什么真机正常模拟器不生效A检查模拟器的网络代理设置。模拟器默认可能使用宿主机的代理如果代理配置不正确请求可能被拦截。建议在模拟器上关闭代理或者直接使用真机测试。Q为什么页面返回后状态丢失AState变量的生命周期和页面组件绑定。页面返回后组件被销毁状态自然丢失。如果需要跨页面保持状态可以使用AppStorage或者全局数据管理方案。Q为什么第一次授权成功第二次失败A检查 Cookie 是否正常传递。如果使用了usingCookie: true但在第一次请求后没有正确处理Set-Cookie第二次请求可能不会带上 Session ID。建议在登录成功后检查CookieStore中是否确实存储了 Cookie。Q高并发场景下多个请求同时创建http.createHttp()会不会有问题A理论上每个请求对象是独立的但底层 Socket 资源有上限。建议对并发数做限制或者在请求完毕后立即释放资源。如果并发量非常大比如超过 30 个同时请求可以考虑使用请求队列。Q这个 API 支持 file 协议吗A不支持。ohos.net.http只支持 HTTP/HTTPS 协议。本地文件读取应该使用ohos.file.fs模块。