HarmonyOS开发:网络层迁移——Retrofit/Alamofire到鸿蒙HTTP
HarmonyOS开发:网络层迁移——Retrofit/Alamofire到鸿蒙HTTP
📌 核心要点:鸿蒙的@kit.NetworkKit提供了原生HTTP能力,虽然没有Retrofit的注解优雅,也没有Alamofire的链式调用丝滑,但配合TypeScript的泛型和装饰器,你能封装出同样好用的网络层。
背景与动机
你项目里的网络层是怎么写的?Retrofit + OkHttp?Alamofire + Moya?还是Axios?
不管你用哪个,网络层迁移都是绕不过去的坎。你的API定义、请求拦截器、序列化/反序列化、错误处理、证书校验——这些逻辑都要重新实现。
鸿蒙没有Retrofit,没有Alamofire,没有Axios。它有的是@kit.NetworkKit——一个功能完整但API风格完全不同的HTTP客户端。
你可能会觉得鸿蒙的HTTP API太"原始"了——没有注解定义接口,没有自动序列化,没有优雅的链式调用。但别急,用ArkTS的泛型和装饰器,你能封装出和Retrofit一样好用的网络层。
这篇文章,帮你把网络层从"能用"升级到"好用"。
核心原理
网络层架构对比
graph TB
subgraph Retrofit["Retrofit 架构"]
R1[注解定义接口] --> R2[动态代理]
R2 --> R3[OkHttp 发送请求]
R3 --> R4[Interceptor 拦截器]
R4 --> R5[Converter 序列化]
R5 --> R6[Call/Response]
end
subgraph Alamofire["Alamofire 架构"]
A1[链式API调用] --> A2[Session 管理]
A2 --> A3[RequestInterceptor]
A3 --> A4[URLSession 底层]
A4 --> A5[Response 序列化]
end
subgraph HarmonyHTTP["鸿蒙 HTTP 架构"]
H1[http.createHttp] --> H2[request 方法]
H2 --> H3[拦截器自定义封装]
H3 --> H4[系统网络栈]
H4 --> H5[手动JSON解析]
end
R4 -.->|概念对应| H3
R5 -.->|概念对应| H5
A3 -.->|概念对应| H3
classDef retrofitStyle fill:#3DDC84,stroke:#2E7D32,color:#000,font-weight:bold
classDef alamofireStyle fill:#F05138,stroke:#C73A25,color:#fff,font-weight:bold
classDef arkStyle fill:#0A59F7,stroke:#0A3CC7,color:#fff,font-weight:bold
class R1,R2,R3,R4,R5,R6 retrofitStyle
class A1,A2,A3,A4,A5 alamofireStyle
class H1,H2,H3,H4,H5 arkStyle
API映射对照
| 功能 | Retrofit | Alamofire | 鸿蒙HTTP |
|---|---|---|---|
| GET请求 | @GET注解 | AF.request() | http.request(GET) |
| POST请求 | @POST注解 | AF.request(.post) | http.request(POST) |
| 路径参数 | @Path | URL插值 | 手动拼接 |
| 查询参数 | @Query | parameters参数 | extraData |
| 请求体 | @Body | encodable | JSON.stringify |
| 请求头 | @Header | headers参数 | header选项 |
| 文件上传 | @Multipart | upload() | request(METHOD.POST) |
| 拦截器 | Interceptor | RequestInterceptor | 自定义封装 |
| 序列化 | Converter | ResponseSerializer | JSON.parse |
| 超时设置 | OkHttp配置 | Session配置 | connectTimeout |
| 证书校验 | SSLSocketFactory | ServerTrustManager | caPath选项 |
代码实战
基础用法:Retrofit迁移到鸿蒙HTTP
先看一个典型的Retrofit接口定义:
// Retrofit: API接口定义
interface ApiService {
@GET("users")
suspend fun getUsers(@Query("page") page: Int): Response<List<User>>
@GET("users/{id}")
suspend fun getUserById(@Path("id") userId: String): Response<User>
@POST("users")
suspend fun createUser(@Body user: User): Response<User>
@PUT("users/{id}")
suspend fun updateUser(@Path("id") userId: String, @Body user: User): Response<User>
@DELETE("users/{id}")
suspend fun deleteUser(@Path("id") userId: String): Response<Unit>
}
// 使用
val api = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
val users = api.getUsers(page = 1)
鸿蒙的基础用法:
// 鸿蒙: 直接使用HTTP API
import { http } from '@kit.NetworkKit'
// 基础GET请求
async function getUsers(page: number): Promise<UserInfo[]> {
let httpRequest = http.createHttp()
try {
let response = await httpRequest.request(
`https://api.example.com/users?page=${page}`,
{
method: http.RequestMethod.GET,
header: { 'Content-Type': 'application/json' },
connectTimeout: 15000,
readTimeout: 15000
}
)
if (response.responseCode === 200) {
return JSON.parse(response.result as string) as UserInfo[]
}
throw new Error(`HTTP ${response.responseCode}`)
} finally {
httpRequest.destroy() // 别忘了销毁!
}
}
// 基础POST请求
async function createUser(user: UserInfo): Promise<UserInfo> {
let httpRequest = http.createHttp()
try {
let response = await httpRequest.request(
'https://api.example.com/users',
{
method: http.RequestMethod.POST,
header: { 'Content-Type': 'application/json' },
extraData: JSON.stringify(user),
connectTimeout: 15000,
readTimeout: 15000
}
)
if (response.responseCode === 200 || response.responseCode === 201) {
return JSON.parse(response.result as string) as UserInfo
}
throw new Error(`HTTP ${response.responseCode}`)
} finally {
httpRequest.destroy()
}
}
对比一下:
- Retrofit用注解定义API,鸿蒙直接写函数
- Retrofit自动序列化/反序列化,鸿蒙手动JSON.parse/stringify
- Retrofit自动管理连接,鸿蒙需要手动destroy()
进阶用法:封装Retrofit风格的网络层
手动写每个请求太痛苦了。来封装一个Retrofit风格的网络层:
// 鸿蒙: 封装Retrofit风格的HTTP客户端
// 统一响应格式
interface ApiResponse<T> {
code: number
message: string
data: T
}
// 请求配置
interface RequestConfig {
url: string
method: http.RequestMethod
params?: Record<string, string> // 查询参数
data?: Object // 请求体
headers?: Record<string, string> // 自定义请求头
timeout?: number // 超时时间
}
// 拦截器接口
interface HttpInterceptor {
onRequest?(config: RequestConfig): RequestConfig // 请求前拦截
onResponse?<T>(response: ApiResponse<T>): ApiResponse<T> // 响应后拦截
onError?(error: HttpError): HttpError // 错误拦截
}
// 错误类
class HttpError extends Error {
code: number
detail: string
constructor(code: number, message: string, detail: string = '') {
super(message)
this.code = code
this.detail = detail
}
}
// HTTP客户端封装
class HttpClient {
private baseUrl: string = ''
private interceptors: HttpInterceptor[] = []
private defaultHeaders: Record<string, string> = {
'Content-Type': 'application/json'
}
// 设置基础URL
setBaseUrl(url: string): HttpClient {
this.baseUrl = url
return this
}
// 添加拦截器
addInterceptor(interceptor: HttpInterceptor): HttpClient {
this.interceptors.push(interceptor)
return this
}
// 设置默认请求头
setDefaultHeaders(headers: Record<string, string>): HttpClient {
this.defaultHeaders = { ...this.defaultHeaders, ...headers }
return this
}
// 核心请求方法
async request<T>(config: RequestConfig): Promise<T> {
// 拼接完整URL
let fullUrl = this.baseUrl + config.url
if (config.params) {
let queryString = Object.entries(config.params)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&')
fullUrl += `?${queryString}`
}
// 合并请求头
let headers = { ...this.defaultHeaders, ...config.headers }
// 请求前拦截
let processedConfig = { ...config, url: fullUrl, headers }
for (let interceptor of this.interceptors) {
if (interceptor.onRequest) {
processedConfig = interceptor.onRequest(processedConfig)
}
}
// 发送请求
let httpRequest = http.createHttp()
try {
let response = await httpRequest.request(processedConfig.url, {
method: processedConfig.method,
header: processedConfig.headers,
extraData: processedConfig.data ? JSON.stringify(processedConfig.data) : undefined,
connectTimeout: processedConfig.timeout || 15000,
readTimeout: processedConfig.timeout || 15000
})
// 处理响应
if (response.responseCode >= 200 && response.responseCode < 300) {
let result = JSON.parse(response.result as string) as ApiResponse<T>
// 响应后拦截
for (let interceptor of this.interceptors) {
if (interceptor.onResponse) {
result = interceptor.onResponse<T>(result)
}
}
if (result.code === 0 || result.code === 200) {
return result.data
}
throw new HttpError(result.code, result.message)
}
throw new HttpError(response.responseCode, `HTTP Error: ${response.responseCode}`)
} catch (e) {
let error = e instanceof HttpError ? e : new HttpError(-1, (e as Error).message)
// 错误拦截
for (let interceptor of this.interceptors) {
if (interceptor.onError) {
error = interceptor.onError(error)
}
}
throw error
} finally {
httpRequest.destroy()
}
}
// 便捷方法
async get<T>(url: string, params?: Record<string, string>): Promise<T> {
return this.request<T>({ url, method: http.RequestMethod.GET, params })
}
async post<T>(url: string, data?: Object): Promise<T> {
return this.request<T>({ url, method: http.RequestMethod.POST, data })
}
async put<T>(url: string, data?: Object): Promise<T> {
return this.request<T>({ url, method: http.RequestMethod.PUT, data })
}
async delete<T>(url: string): Promise<T> {
return this.request<T>({ url, method: http.RequestMethod.DELETE })
}
}
// 创建全局实例
export const apiClient = new HttpClient()
.setBaseUrl('https://api.example.com/')
.addInterceptor({
onRequest: (config) => {
// 自动添加Token
let token = AppStorage.get<string>('token') || ''
if (token) {
config.headers = { ...config.headers, 'Authorization': `Bearer ${token}` }
}
console.info(`[HTTP] ${config.method} ${config.url}`)
return config
},
onError: (error) => {
if (error.code === 401) {
// Token过期,跳转登录
console.warn('Token过期,需要重新登录')
}
return error
}
})
使用封装后的客户端:
// 定义API服务(类似Retrofit的接口定义)
class ApiService {
// GET请求
async getUsers(page: number): Promise<UserInfo[]> {
return apiClient.get<UserInfo[]>('users', { page: page.toString() })
}
async getUserById(id: string): Promise<UserInfo> {
return apiClient.get<UserInfo>(`users/${id}`)
}
// POST请求
async createUser(user: UserInfo): Promise<UserInfo> {
return apiClient.post<UserInfo>('users', user)
}
// PUT请求
async updateUser(id: string, user: UserInfo): Promise<UserInfo> {
return apiClient.put<UserInfo>(`users/${id}`, user)
}
// DELETE请求
async deleteUser(id: string): Promise<void> {
return apiClient.delete<void>(`users/${id}`)
}
}
export const apiService = new ApiService()
看到了吗?封装后的API调用方式和Retrofit一样简洁:
// 使用
let users = await apiService.getUsers(1)
let user = await apiService.getUserById('123')
let newUser = await apiService.createUser({ name: '张三', email: 'zhang@example.com' })
完整示例:Alamofire迁移到鸿蒙
Alamofire的链式调用很优雅,但鸿蒙的HTTP API不支持链式调用。用Promise封装可以模拟类似体验:
// 鸿蒙: 模拟Alamofire风格的链式调用
class ChainRequest {
private config: RequestConfig
private client: HttpClient
constructor(client: HttpClient, config: RequestConfig) {
this.client = client
this.config = config
}
// 添加查询参数
parameters(params: Record<string, string>): ChainRequest {
this.config.params = { ...this.config.params, ...params }
return this
}
// 添加请求头
headers(headers: Record<string, string>): ChainRequest {
this.config.headers = { ...this.config.headers, ...headers }
return this
}
// 设置请求体
body(data: Object): ChainRequest {
this.config.data = data
return this
}
// 设置超时
timeout(seconds: number): ChainRequest {
this.config.timeout = seconds * 1000
return this
}
// 验证响应码
validate(): ChainRequest {
// 在拦截器中处理
return this
}
// 发送请求
async response<T>(): Promise<T> {
return this.client.request<T>(this.config)
}
// 发送并解码JSON
async responseDecodable<T>(): Promise<T> {
return this.client.request<T>(this.config)
}
}
// 扩展HttpClient
class AlamofireStyleClient extends HttpClient {
// 链式请求
requestChain(method: http.RequestMethod, url: string): ChainRequest {
return new ChainRequest(this, { url, method })
}
// 便捷方法
get(url: string): ChainRequest {
return this.requestChain(http.RequestMethod.GET, url)
}
post(url: string): ChainRequest {
return this.requestChain(http.RequestMethod.POST, url)
}
put(url: string): ChainRequest {
return this.requestChain(http.RequestMethod.PUT, url)
}
delete(url: string): ChainRequest {
return this.requestChain(http.RequestMethod.DELETE, url)
}
}
// 使用(类似Alamofire风格)
let client = new AlamofireStyleClient().setBaseUrl('https://api.example.com/')
let users = await client.get('users')
.parameters({ page: '1', size: '20' })
.headers({ 'X-Custom-Header': 'value' })
.timeout(30)
.response<UserInfo[]>()
let newUser = await client.post('users')
.body({ name: '张三', email: 'zhang@example.com' })
.response<UserInfo>()
踩坑与注意事项
坑1:httpRequest必须destroy()
鸿蒙的http.createHttp()创建的请求对象,用完必须调用destroy()释放资源。忘了调?内存泄漏等着你。
// ❌ 错误:忘记destroy
let httpRequest = http.createHttp()
let response = await httpRequest.request(url)
// 没有destroy!
// ✅ 正确:用try/finally确保destroy
let httpRequest = http.createHttp()
try {
let response = await httpRequest.request(url)
// 处理响应
} finally {
httpRequest.destroy()
}
坑2:extraData的类型限制
鸿蒙HTTP的extraData只支持string和Object两种类型。如果你传其他类型,请求会失败。
// ❌ 错误:直接传number
httpRequest.request(url, {
method: http.RequestMethod.POST,
extraData: 123 // 不支持!
})
// ✅ 正确:转成JSON字符串
httpRequest.request(url, {
method: http.RequestMethod.POST,
extraData: JSON.stringify({ value: 123 })
})
坑3:并发请求限制
鸿蒙对同一域名的并发请求数有限制(通常6个)。如果你同时发起大量请求,后面的请求会排队等待。建议实现请求队列管理。
坑4:HTTPS证书校验
鸿蒙的HTTPS证书校验和Android/iOS配置方式不同。如果你的后端用了自签名证书,需要额外配置:
// 鸿蒙: 自签名证书配置
let response = await httpRequest.request(url, {
method: http.RequestMethod.GET,
// 指定CA证书路径
caPath: '/data/storage/el2/base/files/ca.crt'
})
坑5:网络权限声明
别忘了在module.json5中声明网络权限:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
HarmonyOS 6适配说明
HarmonyOS 6在网络层有几个重要改进:
-
HTTP/2支持:@kit.NetworkKit默认支持HTTP/2协议,多路复用减少了连接开销。
-
WebSocket增强:新增了完整的WebSocket客户端API,支持自动重连和心跳检测。
-
请求缓存:新增了HTTP响应缓存能力,离线场景下可以返回缓存数据。
-
网络安全配置:新增了network_config.json配置文件,可以声明式配置证书校验规则、域名安全策略等。
-
网络状态监听增强:@kit.NetworkKit的connection模块可以更精确地监听网络状态变化,包括WiFi/蜂窝切换、信号强度变化等。
总结
网络层迁移的核心是封装——鸿蒙的原生HTTP API功能完整但不够优雅,用泛型和类封装后,体验不输Retrofit/Alamofire。
| 维度 | 学习难度 | 使用频率 | 重要程度 |
|---|---|---|---|
| 基础HTTP请求 | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 请求封装 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 拦截器实现 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 序列化/反序列化 | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 错误处理 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| HTTPS/证书 | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
别被鸿蒙HTTP的"原始"API吓到。花半天时间封装一个Retrofit风格的客户端,后续所有API调用都能享受优雅的开发体验。这笔投资绝对值得。
- 点赞
- 收藏
- 关注作者
评论(0)