作为一名鸿蒙开发初学者,最近集中精力啃完了网络请求这块核心知识点。从一开始对着官方文档无从下手,到能独立封装工具类、处理各种异常场景,踩了不少坑,也积累了一些实用经验。今天就以复盘的形式,把鸿蒙网络请求的学习重点、实战技巧和避坑要点整理出来,适合和我一样刚入门的小伙伴参考,也当作自己的学习沉淀~
先明确一个核心:鸿蒙开发中,网络请求的核心依赖是 @ohos.net.http 原生模块,这是官方提供的功能完整、性能优异的网络请求工具,几乎所有应用的网络交互都离不开它。除此之外,社区也有适配鸿蒙的三方库 @ohos/axios,用法和前端 axios 类似,适合熟悉前端开发的小伙伴快速上手。
整个学习过程,我把它分成了「基础入门→实战封装→高级优化→避坑总结」四个阶段,一步步从“会用”到“好用”,下面逐一拆解。
一、基础入门:搞定网络请求的“敲门砖”
入门阶段最核心的目标,是掌握网络请求的基本流程,以及必要的前置配置。这一步看似简单,但少了任何一个环节,都会导致请求失败,新手很容易在这里栽跟头。
- 必做前置:配置网络权限
鸿蒙应用默认禁止网络访问,所以第一步必须在 module.json5 文件中声明网络权限,否则无论代码写得多对,都会直接报网络错误(错误信息可能不明确,容易误导排查)。
配置代码如下,直接复制到 requestPermissions 字段中即可:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
- 核心流程:3步完成一次基础请求
鸿蒙原生网络请求的核心流程很固定,无论是 GET 还是 POST,都离不开「导入模块→创建请求对象→发起请求→处理响应→释放资源」这几个步骤。这里以最常用的 GET 请求为例,贴出基础代码(新手可直接复制测试):
// 1. 导入http模块
import http from '@ohos.net.http';
// 2. 发起GET请求(回调函数写法)
let httpRequest = http.createHttp(); // 每个请求对应一个独立对象,不可复用
httpRequest.request(
"https://jsonplaceholder.typicode.com/users", // 测试接口,可直接访问
{
method: http.RequestMethod.GET, // 请求方法,GET/POST/PUT/DELETE等
connectTimeout: 10000, // 连接超时时间(毫秒),建议设10秒
readTimeout: 10000, // 读取超时时间(毫秒)
header: {
'Content-Type': 'application/json' // 请求头,根据接口要求调整
}
},
(err, data) => {
// 3. 处理响应结果
if (!err) {
// 请求成功,解析响应数据
console.info('响应码:' + data.responseCode);
if (data.responseCode >= 200 && data.responseCode < 300) {
let result = JSON.parse(data.result as string);
console.info('请求结果:' + JSON.stringify(result));
}
} else {
// 请求失败,处理错误
console.error('请求失败:' + JSON.stringify(err));
}
// 关键:请求完成后必须销毁对象,避免内存泄漏
httpRequest.destroy();
}
);
这里有一个新手必记的点:每个请求都要创建独立的 httpRequest 对象,且请求完成后必须调用 destroy() 方法释放资源。如果忘记销毁,会导致内存泄漏,长期运行会让应用卡顿甚至崩溃,这是我一开始踩的第一个大坑。
- 常见请求方法:GET vs POST
实际开发中,GET 和 POST 是最常用的两种请求方法,两者的核心区别的在于参数传递方式,这里整理成表格,一目了然:
请求方法
参数传递方式
适用场景
核心注意点
GET
参数拼接在URL后面
查询数据(如列表、详情)
参数长度有限制,不适合传递敏感数据
POST
参数放在请求体(extraData)中
提交数据(如登录、注册、新增)
需设置正确的 Content-Type,参数无长度限制
POST 请求的基础示例(重点看 extraData 和请求头):
httpRequest.request(
"https://jsonplaceholder.typicode.com/login",
{
method: http.RequestMethod.POST,
header: { 'Content-Type': 'application/json' },
extraData: { username: "admin", password: "123456" }, // 请求体参数
connectTimeout: 10000,
readTimeout: 10000
},
(err, data) => {
// 处理逻辑和GET一致,记得销毁资源
if (!err && data.responseCode === 200) {
let result = JSON.parse(data.result as string);
console.info('登录成功,token:' + result.token);
} else {
console.error('登录失败:' + err?.message);
}
httpRequest.destroy();
}
);
二、实战进阶:封装工具类,提升开发效率
入门之后会发现,直接在业务代码中写原生请求,会出现大量重复代码(比如导入模块、创建对象、释放资源、错误处理),不仅冗余,还不利于后期维护。这时候,封装一个统一的网络工具类,就成了刚需。
封装的核心思路是:用单例模式创建工具类,统一处理请求头、超时时间、参数拼接、错误处理和资源释放,对外提供简洁的 get、post 方法,让业务代码只关注“请求什么”,而不用关注“怎么请求”。
下面是我封装的工具类完整代码(基于 ArkTS,可直接复制到项目中使用):
// network.ets (建议放在 src/main/ets/common 目录下)
import http from '@ohos.net.http';
import hilog from '@ohos.hilog';
// 定义响应数据格式,规范返回结果
export interface HttpResponse<T = any> {
code: number; // 响应码
data: T; // 响应数据
message: string; // 提示信息
}
// 单例模式封装网络工具类
export class HttpService {
private static instance: HttpService;
private baseUrl: string = ''; // 基础路径,统一配置
private readonly DEFAULT_TIMEOUT = 10000; // 默认超时时间10秒
// 私有构造函数,禁止外部实例化
private constructor() {}
// 单例获取方法
static getInstance(): HttpService {
if (!HttpService.instance) {
HttpService.instance = new HttpService();
}
return HttpService.instance;
}
// 设置基础路径(如:https://api.example.com)
setBaseUrl(url: string): void {
this.baseUrl = url;
}
// GET请求封装
async get<T>(path: string, params?: Record<string, any>): Promise<HttpResponse<T>> {
const url = this.buildUrl(path, params);
const httpRequest = http.createHttp();
try {
const response = await httpRequest.request(url, {
method: http.RequestMethod.GET,
connectTimeout: this.DEFAULT_TIMEOUT,
readTimeout: this.DEFAULT_TIMEOUT,
header: this.getDefaultHeaders()
});
// 释放资源
httpRequest.destroy();
// 处理响应结果
if (response.responseCode >= 200 && response.responseCode < 300) {
return {
code: response.responseCode,
data: JSON.parse(response.result as string),
message: '请求成功'
};
} else {
throw new Error(`HTTP请求失败,响应码:${response.responseCode}`);
}
} catch (err) {
// 错误处理,打印日志
hilog.error(0xFF00, 'Network', 'GET请求失败:%{public}s', err.message);
httpRequest.destroy(); // 异常时也要释放资源
throw err;
}
}
// POST请求封装
async post<T>(path: string, data?: Record<string, any>): Promise<HttpResponse<T>> {
const url = this.baseUrl + path;
const httpRequest = http.createHttp();
try {
const response = await httpRequest.request(url, {
method: http.RequestMethod.POST,
connectTimeout: this.DEFAULT_TIMEOUT,
readTimeout: this.DEFAULT_TIMEOUT,
header: this.getDefaultHeaders(),
extraData: data // POST请求体
});
httpRequest.destroy();
if (response.responseCode >= 200 && response.responseCode < 300) {
return {
code: response.responseCode,
data: JSON.parse(response.result as string),
message: '请求成功'
};
} else {
throw new Error(`HTTP请求失败,响应码:${response.responseCode}`);
}
} catch (err) {
hilog.error(0xFF00, 'Network', 'POST请求失败:%{public}s', err.message);
httpRequest.destroy();
throw err;
}
}
// 拼接URL和参数(处理GET请求的参数)
private buildUrl(path: string, params?: Record<string, any>): string {
let url = this.baseUrl + path;
if (!params) return url;
// 拼接参数(编码处理,避免特殊字符报错)
const query = Object.keys(params)
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(params[k])}`)
.join('&');
return url + (url.includes('?') ? '&' : '?') + query;
}
// 默认请求头(可根据项目需求扩展,如添加token)
private getDefaultHeaders(): Record<string, string> {
return {
'Content-Type': 'application/json',
'Accept': 'application/json',
// 示例:从AppStorage获取token(需结合项目状态管理)
'Authorization': Bearer ${AppStorage.Get('token') || ''}
};
}
}
工具类的使用示例(简洁到离谱,业务代码再也不用写冗余逻辑):
// 1. 初始化工具类(建议在应用入口处初始化一次)
const httpService = HttpService.getInstance();
httpService.setBaseUrl('https://jsonplaceholder.typicode.com');
// 2. 发起GET请求(async/await写法,更简洁)
async getUserList() {
try {
const resp = await httpService.get('/users');
this.userList = resp.data; // 直接使用响应数据
} catch (err) {
// 统一错误提示(如弹窗提示用户)
showToast('获取用户列表失败,请重试');
}
}
// 3. 发起POST请求
async login() {
try {
const resp = await httpService.post('/login', {
username: 'admin',
password: '123456'
});
// 存储token
AppStorage.Set('token', resp.data.token);
showToast('登录成功');
} catch (err) {
showToast('登录失败,账号或密码错误');
}
}
封装之后,不仅代码量大大减少,而且后期如果需要修改超时时间、请求头,只需要修改工具类,不用逐个修改业务代码,维护成本直接降低一半。
三、高级优化:缓存策略与异常处理
学会封装工具类后,基本能满足大部分日常开发需求,但要想让应用体验更好,还需要做一些高级优化——比如添加缓存策略、完善异常处理,解决弱网、无网场景下的用户体验问题。
- 缓存策略:减少重复请求,提升响应速度
对于一些不常变化的数据(如首页公告、分类列表),每次打开页面都发起网络请求,不仅浪费流量,还会影响响应速度。这时候可以采用「LRU内存缓存 + Preferences持久化缓存」的双层缓存策略:
LRU内存缓存:缓存最近访问的数据,内存不足时自动淘汰最久未访问的数据,适合短期缓存。
Preferences持久化缓存:将数据存储到本地,应用重启后仍能获取,适合长期缓存。
核心思路:发起请求前,先检查缓存中是否有数据;如果有,直接使用缓存数据;如果没有,发起网络请求,请求成功后将数据存入缓存。(具体实现可结合鸿蒙的 Preferences 模块和 LRU 缓存类,这里就不贴完整代码了,感兴趣的小伙伴可以留言交流)
- 异常处理:覆盖所有可能的失败场景
网络请求的失败场景有很多(无网络、超时、响应码错误、数据解析失败等),新手很容易只处理一部分异常,导致应用崩溃。这里整理了常见的异常场景及处理方式:
无网络:通过鸿蒙的网络状态API判断网络是否可用,提前提示用户“请检查网络连接”。
请求超时:在工具类中设置合理的超时时间,超时后提示用户“请求超时,请重试”。
响应码错误:根据响应码判断错误类型(404:接口不存在;401:未登录;500:服务器错误),给出对应的提示。
数据解析失败:请求成功但数据格式不符合预期,捕获JSON.parse异常,提示“数据加载异常”。
四、避坑总结:新手必看,少走弯路
结合自己踩过的坑,整理了10个新手常犯的错误,看完能帮你节省大量排查问题的时间:
忘记配置网络权限,导致请求直接失败,报错信息模糊。
请求完成后未调用 destroy() 方法,导致内存泄漏。
复用 httpRequest 对象,导致多个请求冲突,出现异常。
POST 请求未设置 Content-Type,导致后端无法解析请求体。
GET 请求参数未编码,包含特殊字符(如中文、&),导致请求失败。
未处理超时异常,网络较差时应用一直处于加载状态,用户体验差。
直接使用 response.result,未判断响应码,导致请求失败时解析数据报错。
工具类未用单例模式,多次实例化,造成资源浪费。
缓存数据未设置过期时间,导致数据过时,展示错误信息。
异常处理不完整,未捕获所有可能的错误,导致应用崩溃。