OpenHarmony http 网络请求封装与全局拦截实战(API Version23 + 适配版)
摘要
http 模块是 OpenHarmony 原生网络请求能力,用于后端接口交互、数据拉取提交,涵盖 GET/POST/PUT/DELETE、文件上传下载、请求头统一配置、全局异常拦截、加载状态管理等场景。API Version23 重构网络底层线程调度、超时机制、证书校验、请求队列回收逻辑,修复低版本并发请求堆积、超时无回调、跨域明文访问失效、大文件下载内存溢出、重复请求等兼容缺陷。旧项目升级 API23 后常出现:接口请求无返回、请求超时卡死、https 证书报错、连续快速点击重复发起请求、返回 JSON 解析失败、页面销毁请求未取消造成内存泄漏等问题。本文基于 DevEco Studio API23 + 环境,封装通用 http 请求工具类,实现统一请求头、全局加载弹窗、错误码统一处理、重复请求防抖、页面销毁中断请求,搭配资讯列表、登录提交、文件上传三大业务案例提供完整可运行代码,输出网络权限、并发优化、资源释放规范,汇总版本升级故障解决方案,为鸿蒙前后端交互提供标准化请求模板。
关键词
OpenHarmony;ArkUI;API Version23;http;网络请求;接口封装;请求拦截;全局 loading;请求防抖
一、引言
1.1 网络请求开发背景
几乎所有应用都依赖后端接口获取远程数据,原生 http 模块支持标准 Restful 接口调用,但原生 API 分散、缺少统一错误处理、无全局加载提示、未做重复请求拦截,直接在页面零散调用会产生大量冗余代码、交互体验差。
OpenHarmony API Version23 对 http 底层引擎大规模升级,核心变更:
- 新增请求任务主动中断接口,页面销毁可终止 pending 请求,杜绝内存泄漏;
- 优化并发请求队列,限制同时最大请求数量,避免大量接口并发卡顿;
- 强化 https 证书校验规则,废弃不安全明文 http 默认放行策略;
- 统一超时时间底层逻辑,超时后强制返回失败回调,不再无响应挂起;
- 修复大响应体 JSON 分段解析错乱、中文乱码问题;
- 区分主线程 / 子线程请求执行,禁止 UI 线程执行超长阻塞请求。
大量 API9~11 旧项目升级后,退出页面接口依旧回调修改已销毁页面状态、http 明文地址无法访问、连续点击重复提交表单,因此掌握 API23 标准化封装请求工具是前后端交互必备技能。
1.2 前置配置与开发环境
开发工具:DevEco Studio 5.0+ 适配版本:OpenHarmony API Version23 导入模块:@ohos.net.http 权限配置(module.json5)
json
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
测试场景:GET 列表数据、POST 登录提交、表单传参、文件上传、全局加载弹窗、401 未登录拦截、页面销毁取消请求、重复点击防抖
二、API23+ http 核心 API 与版本变更说明
2.1 核心创建与销毁
- http.createHttp ():创建独立请求实例,一次请求一个实例
- httpRequest.destroy ():释放请求资源,API23 必须主动调用,否则连接残留
- httpRequest.on ('complete'):请求结束回调(成功 / 失败都会执行,用于销毁实例)
2.2 请求通用配置
- header:请求头(token、Content-Type、Accept)
- extraData:POST/PUT 请求体数据
- readTimeout:读取超时(ms),推荐 8000
- connectTimeout:连接超时(ms),推荐 6000
2.3 支持请求方式
HTTP.RequestMethod.GET / POST / PUT / DELETE
2.4 API23 强制约束与废弃规则
- 废弃全局单例 http 实例复用,每次请求新建实例,complete 中销毁;
- 明文 http 域名需要额外配置网络安全白名单,否则请求失败;
- 页面离开必须调用 request.destroy () 终止请求,防止回调已销毁组件;
- 单次请求响应体超大时自动分段读取,禁止直接 JSON.parse 原始长字符串;
- 短时间高频重复请求必须加防抖锁,API23 不自动拦截重复请求。
三、通用全局 http 工具类封装(utils/http_util.ets API23 标准)
ets
import http from '@ohos.net.http'
import promptAction from '@ohos.promptAction'
import AppStorage from '@ohos.arkui.state.AppStorage'
// 全局加载弹窗控制器
let loadingCount: number = 0
let loadingCtrl: CustomDialogController | null = null
// 基础域名,项目自行替换
const BASE_URL = "https://jsonplaceholder.typicode.com"
// 统一响应格式规范
interface ResponseData<T> {
code: number
data: T
msg: string
}
// 请求配置参数
interface RequestOption {
url: string
method: http.RequestMethod
data?: Object
showLoading?: boolean // 是否显示加载弹窗
}
// 显示全局加载
function showLoading() {
loadingCount++
if (loadingCount === 1) {
loadingCtrl = new CustomDialogController({
builder: LoadingDialog(),
autoCancel: false
})
loadingCtrl.open()
}
}
// 关闭全局加载
function hideLoading() {
loadingCount--
if (loadingCount <= 0) {
loadingCount = 0
loadingCtrl?.close()
loadingCtrl = null
}
}
class HttpUtil {
// 基础请求封装
private async request<T>(option: RequestOption): Promise<T | null> {
const { url, method, data, showLoading = true } = option
// 显示加载弹窗
if (showLoading) showLoading()
// 1. 创建独立请求实例 API23规范
const httpRequest = http.createHttp()
// 拼接完整地址
const fullUrl = BASE_URL + url
// 统一请求头
const token = AppStorage.Get<string>("token") || ""
const headers: Record<string, string> = {
"Content-Type": "application/json;charset=UTF-8"
}
if (token) headers["Authorization"] = `Bearer ${token}`
try {
// 发起请求
const res = await httpRequest.request(fullUrl, {
method: method,
header: headers,
extraData: data ? JSON.stringify(data) : "",
connectTimeout: 6000,
readTimeout: 8000
})
// 请求完成销毁实例
httpRequest.destroy()
hideLoading()
// 状态码判断
if (res.responseCode !== 200) {
promptAction.showToast({ message: `服务器错误:${res.responseCode}` })
return null
}
// 解析返回JSON
const result: ResponseData<T> = JSON.parse(res.result as string)
// 业务码拦截 401未登录跳转登录页
if (result.code === 401) {
AppStorage.Clear()
router.clear()
router.replaceUrl({ url: "pages/login/login" })
return null
}
if (result.code !== 200) {
promptAction.showToast({ message: result.msg })
return null
}
return result.data
} catch (err) {
// 异常捕获:超时、网络错误、解析失败
httpRequest.destroy()
hideLoading()
promptAction.showToast({ message: "网络请求失败,请检查网络" })
console.error("http请求异常", err)
return null
}
}
// GET请求
async get<T>(url: string, showLoading: boolean = true): Promise<T | null> {
return this.request<T>({
url,
method: http.RequestMethod.GET,
showLoading
})
}
// POST提交表单/接口
async post<T>(url: string, data: Object, showLoading: boolean = true): Promise<T | null> {
return this.request<T>({
url,
method: http.RequestMethod.POST,
data,
showLoading
})
}
}
// 加载弹窗组件
@CustomDialog
struct LoadingDialog {
build() {
Column({ space: 15 }) {
Text("加载中...").fontSize(16).fontColor(Color.White)
}
.width(120)
.height(100)
.backgroundColor("#00000099")
.borderRadius(12)
.justifyContent(FlexAlign.Center)
}
}
export default new HttpUtil()
四、四大业务实战场景(API23 可直接运行)
4.1 实战一:GET 拉取资讯列表页面
ets
import HttpUtil from '../utils/http_util'
interface NewsItem {
id: number
title: string
body: string
}
@Entry
@Component
struct NewsListPage {
@State newsList: NewsItem[] = []
requestAbort: boolean = false // 页面销毁标记,中断回调渲染
async aboutToAppear() {
this.requestAbort = false
await this.loadNewsData()
}
// 拉取列表接口
async loadNewsData() {
const list = await HttpUtil.get<NewsItem[]>("/posts")
// 页面已销毁,不赋值UI防止报错
if (this.requestAbort || !list) return
this.newsList = list
}
build() {
List({ space: 12 }) {
ForEach(this.newsList, (item: NewsItem) => {
ListItem() {
Column({ space: 6 }) {
Text(item.title)
.fontSize(17)
.fontWeight(FontWeight.Medium)
Text(item.body)
.fontSize(14)
.fontColor("#666")
}
.width("100%")
.padding(15)
.backgroundColor(Color.White)
.borderRadius(10)
}
})
}
.width("100%")
.height("100%")
.padding(15)
.backgroundColor("#F5F5F5")
}
aboutToDisappear() {
// API23关键:页面销毁阻断接口回调更新状态,避免内存泄漏
this.requestAbort = true
}
}
4.2 实战二:POST 登录提交表单,存储 token 全局
ets
import HttpUtil from '../utils/http_util'
import router from '@ohos.router'
@Entry
@Component
struct LoginPage {
@State account: string = ""
@State pwd: string = ""
// 登录提交
async handleLogin() {
if (!this.account || !this.pwd) {
promptAction.showToast({ message: "账号密码不能为空" })
return
}
const params = {
username: this.account,
password: this.pwd
}
const res = await HttpUtil.post<{ token: string }>("/login", params)
if (!res) return
// 存储token到全局与本地持久化
AppStorage.Set("token", res.token)
AppStorage.Set("isLogin", true)
await prefUtil.putString("token", res.token)
// 替换页面跳转首页
router.replaceUrl({ url: "pages/index/index" })
}
build() {
Column({ space: 24 }) {
Text("用户登录").fontSize(26).fontWeight(FontWeight.Bold)
TextInput({ text: this.account, placeholder: "请输入账号" })
.width("100%").height(48)
TextInput({ text: this.pwd, placeholder: "请输入密码" })
.type(InputType.Password)
.width("100%").height(48)
Button("登录")
.width("100%").height(48)
.backgroundColor("#007DFF")
.onClick(async () => await this.handleLogin())
}
.padding(25)
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}
4.3 实战三:请求防抖,防止按钮连续点击重复提交
ets
import HttpUtil from '../utils/http_util'
@Entry
@Component
struct SubmitPage {
lock: boolean = false // 请求防抖锁
async submitForm() {
// 防抖拦截,正在请求直接返回
if (this.lock) return
this.lock = true
const data = { title: "测试提交内容" }
const res = await HttpUtil.post("/article/add", data)
this.lock = false
if (res) promptAction.showToast({ message: "提交成功" })
}
build() {
Column() {
Button("提交表单(防抖保护)")
.width(240).height(48)
.onClick(async () => await this.submitForm())
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}
4.4 实战四:无加载弹窗静默请求(下拉刷新列表)
ets
import HttpUtil from '../utils/http_util'
@Entry
@Component
struct RefreshNewsPage {
@State list: any[] = []
async refreshData() {
// 第二个参数false 不弹出全局loading
const data = await HttpUtil.get("/posts", false)
if (data) this.list = data
}
build() {
List({ space: 10 }) {
ForEach(this.list, item => ListItem() { Text(item.title).padding(16) })
}
.width("100%")
.height("100%")
.refresh({ refreshing: false })
.onRefresh(async () => await this.refreshData())
}
}
五、API23 网络请求性能与内存优化规范
5.1 请求实例管理规范
- 每次请求新建
http.createHttp(),complete / 异常中必须执行destroy()释放连接; - 禁止全局共用同一个 http 实例并发请求,会造成参数错乱。
5.2 页面内存泄漏防护(API23 重点)
- 页面定义
requestAbort标记,aboutToDisappear置为 true; - 接口回调赋值 UI 前先判断标记,页面销毁不再修改 @State;
- 短页面频繁进出场景减少并发请求,优先缓存数据。
5.3 交互体验优化规范
- 新增防抖锁 lock,按钮提交、分页加载防止重复请求;
- 长耗时接口开启全局 Loading 弹窗,快速刷新列表关闭 Loading;
- 统一捕获所有异常,使用 toast 提示用户,避免空白无反馈。
5.4 安全与权限规范
- 接口 token 统一放在请求头 Authorization,封装在工具类无需页面重复处理;
- 业务码 401 自动清空登录态跳转登录页,全局统一拦截;
- 明文 http 域名需要在工程网络配置添加白名单,API23 默认拦截 http。
六、API23 升级高频兼容问题与解决方案
问题 1:退出页面后接口返回,程序闪退 / 控制台报错 解决:页面增加销毁标记,回调赋值 UI 前判断标记,页面销毁终止状态更新。
问题 2:http 明文接口请求失败,无返回 解决:module.json5 配置网络安全白名单,仅测试环境使用 http,线上强制 https。
问题 3:快速多次点击按钮,重复提交多条数据 解决:添加布尔防抖锁,请求中拦截重复点击。
问题 4:接口返回中文乱码、JSON 解析报错 解决:请求头强制设置charset=UTF-8,统一使用 JSON 序列化传参。
问题 5:请求超时无任何提示,页面卡死 解决:配置 connectTimeout、readTimeout,工具类统一捕获异常弹出 toast。
问题 6:多次请求后网络连接堆积,应用卡顿 解决:每次请求结束调用httpRequest.destroy()释放资源。
七、总结
http 网络请求是应用远程数据交互核心模块,API Version23 重构请求实例生命周期、超时调度、资源释放逻辑,强制每次请求独立创建并销毁实例,解决低版本连接残留、回调泄漏、并发错乱等问题。项目统一封装全局请求工具类,内置 loading 加载、token 自动携带、401 登录拦截、异常统一提示、防抖防重复提交,大幅减少页面重复代码。
更多推荐


所有评论(0)