网络请求是现代移动应用的基础能力,HarmonyOS通过@ohos.net.http模块提供了强大的HTTP客户端功能。本文将全面讲解如何在HarmonyOS应用中进行网络通信,涵盖从基础请求到高级特性的完整实战内容。

一、网络请求基础概念

1.1 HTTP协议概述

HTTP(HyperText Transfer Protocol)是互联网上应用最为广泛的应用层协议,理解其基本概念对网络编程至关重要。

HTTP请求方法

  • GET:获取资源(幂等操作)
  • POST:创建资源或提交数据
  • PUT:更新资源
  • DELETE:删除资源
  • PATCH:部分更新资源

HTTP状态码分类

  • 1xx:信息响应
  • 2xx:成功响应(200 OK,201 Created)
  • 3xx:重定向响应
  • 4xx:客户端错误(404 Not Found)
  • 5xx:服务器错误

1.2 HarmonyOS网络权限配置

在进行网络请求前,需要在module.json5文件中配置网络权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "需要网络权限进行数据请求"
      }
    ]
  }
}

二、基础HTTP请求实战

2.1 创建HTTP请求实例

import http from '@ohos.net.http'

@Component
struct BasicHttpExample {
  @State responseData: string = ''
  @State isLoading: boolean = false

  // 创建HTTP请求实例
  private httpRequest: http.HttpRequest = http.createHttp()

  build() {
    Column({ space: 20 }) {
      Text('基础HTTP请求演示')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      
      Text('响应数据:')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
      
      Scroll() {
        Text(this.responseData)
          .fontSize(14)
          .fontColor('#666')
          .textAlign(TextAlign.Start)
      }
      .height(200)
      .backgroundColor('#F5F5F5')
      .padding(10)
      
      Row({ space: 15 }) {
        Button('GET请求')
          .onClick(() => this.sendGetRequest())
          .enabled(!this.isLoading)
        
        Button('POST请求')
          .onClick(() => this.sendPostRequest())
          .enabled(!this.isLoading)
      }
      
      if (this.isLoading) {
        Progress({ value: 50, total: 100 })
          .width(200)
        Text('请求中...')
          .fontSize(14)
          .fontColor('#666')
      }
    }
    .width('100%')
    .padding(20)
    .onDetach(() => {
      // 组件销毁时释放资源
      this.httpRequest.destroy()
    })
  }

2.2 GET请求实现

// GET请求示例
async sendGetRequest() {
  this.isLoading = true
  this.responseData = '请求中...'

  try {
    // 配置请求选项
    let httpRequestOptions: http.HttpRequestOptions = {
      method: http.RequestMethod.GET,
      header: {
        'Content-Type': 'application/json'
      },
      expectDataType: http.HttpDataType.OBJECT, // 期望返回对象类型
      usingCache: true, // 启用缓存
      priority: 1 // 请求优先级
    }

    // 发送请求(使用公共测试API)
    let response = await this.httpRequest.request(
      'https://jsonplaceholder.typicode.com/posts/1',
      httpRequestOptions
    )

    // 处理响应
    if (response.responseCode === http.ResponseCode.OK) {
      this.responseData = JSON.stringify(response.result, null, 2)
      console.log('GET请求成功:', response.result)
    } else {
      this.responseData = `请求失败,状态码: ${response.responseCode}`
    }

  } catch (error) {
    console.error('GET请求异常:', error)
    this.responseData = `请求异常: ${error.message}`
  } finally {
    this.isLoading = false
  }
}

2.3 POST请求实现

// POST请求示例
async sendPostRequest() {
  this.isLoading = true
  this.responseData = '请求中...'

  try {
    // 准备请求数据
    const postData = {
      title: 'HarmonyOS网络请求测试',
      body: '这是一个测试POST请求的内容',
      userId: 1
    }

    let httpRequestOptions: http.HttpRequestOptions = {
      method: http.RequestMethod.POST,
      header: {
        'Content-Type': 'application/json'
      },
      extraData: JSON.stringify(postData), // 请求体数据
      expectDataType: http.HttpDataType.OBJECT
    }

    let response = await this.httpRequest.request(
      'https://jsonplaceholder.typicode.com/posts',
      httpRequestOptions
    )

    if (response.responseCode === http.ResponseCode.CREATED) {
      this.responseData = JSON.stringify(response.result, null, 2)
      console.log('POST请求成功:', response.result)
    } else {
      this.responseData = `请求失败,状态码: ${response.responseCode}`
    }

  } catch (error) {
    console.error('POST请求异常:', error)
    this.responseData = `请求异常: ${error.message}`
  } finally {
    this.isLoading = false
  }
}

三、高级网络请求特性

3.1 请求配置与管理

class HttpService {
  private httpRequest: http.HttpRequest
  
  constructor() {
    this.httpRequest = http.createHttp()
    
    // 配置全局请求参数
    this.configureHttpRequest()
  }

  private configureHttpRequest() {
    // 设置请求超时(毫秒)
    this.httpRequest.setTimeout(10000)
    
    // 设置自定义HTTP头
    this.httpRequest.setExtraHeaders({
      'User-Agent': 'HarmonyOS-App/1.0',
      'Accept-Language': 'zh-CN,zh;q=0.9'
    })
  }

  // 支持查询参数的GET请求
  async getWithParams(url: string, params?: Record<string, any>) {
    let fullUrl = url
    
    if (params) {
      const queryString = Object.keys(params)
        .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
        .join('&')
      fullUrl += `?${queryString}`
    }

    const options: http.HttpRequestOptions = {
      method: http.RequestMethod.GET,
      header: { 'Content-Type': 'application/json' },
      usingCache: true,
      priority: 1
    }

    return await this.httpRequest.request(fullUrl, options)
  }
}

3.2 文件上传实现

// 文件上传功能
async uploadFile(fileUri: string, uploadUrl: string) {
  try {
    // 创建FormData格式数据
    const formData = new FormData()
    
    // 获取文件信息
    const fileInfo = await this.getFileInfo(fileUri)
    
    // 添加文件到FormData
    formData.append('file', {
      uri: fileUri,
      type: fileInfo.type,
      name: fileInfo.name
    } as any)
    
    // 添加其他字段
    formData.append('description', 'HarmonyOS文件上传测试')
    
    const options: http.HttpRequestOptions = {
      method: http.RequestMethod.POST,
      header: {
        'Content-Type': 'multipart/form-data'
      },
      extraData: formData
    }

    const response = await this.httpRequest.request(uploadUrl, options)
    
    if (response.responseCode === http.ResponseCode.OK) {
      console.log('文件上传成功')
      return response.result
    } else {
      throw new Error(`上传失败: ${response.responseCode}`)
    }
    
  } catch (error) {
    console.error('文件上传异常:', error)
    throw error
  }
}

3.3 网络状态监控

import network from '@ohos.net.network'

@Component
struct NetworkStatusMonitor {
  @State networkType: string = '未知'
  @State isConnected: boolean = false

  private netHandle: network.NetHandle | null = null

  aboutToAppear() {
    this.startNetworkMonitoring()
  }

  aboutToDisappear() {
    this.stopNetworkMonitoring()
  }

  // 开始网络状态监控
  async startNetworkMonitoring() {
    try {
      // 获取当前网络连接
      this.netHandle = network.getDefaultNet()
      
      // 获取网络类型
      this.updateNetworkType()
      
      // 监听网络状态变化
      network.on('netAvailable', (data: network.NetCapabilityInfo) => {
        this.isConnected = true
        this.updateNetworkType()
        console.log('网络已连接')
      })
      
      network.on('netCapabilitiesChange', (data: network.NetCapabilityInfo) => {
        this.updateNetworkType()
        console.log('网络能力发生变化')
      })
      
      network.on('netConnectionPropertiesChange', (data: network.ConnectionProperties) => {
        console.log('网络连接属性发生变化')
      })
      
      network.on('netBlockStatusChange', (data: network.NetBlockStatusInfo) => {
        this.isConnected = !data.blocked
        console.log('网络阻塞状态变化:', data.blocked)
      })
      
    } catch (error) {
      console.error('网络监控启动失败:', error)
    }
  }

  // 更新网络类型信息
  async updateNetworkType() {
    if (!this.netHandle) return
    
    try {
      const netCapabilities = await this.netHandle.getNetCapabilities()
      
      if (netCapabilities.hasNetCap(network.NetCap.NET_CAPABILITY_WIFI)) {
        this.networkType = 'WiFi'
      } else if (netCapabilities.hasNetCap(network.NetCap.NET_CAPABILITY_CELLULAR)) {
        this.networkType = '移动网络'
      } else if (netCapabilities.hasNetCap(network.NetCap.NET_CAPABILITY_ETHERNET)) {
        this.networkType = '有线网络'
      } else {
        this.networkType = '其他网络'
      }
    } catch (error) {
      console.error('获取网络类型失败:', error)
    }
  }

  stopNetworkMonitoring() {
    network.off('netAvailable')
    network.off('netCapabilitiesChange')
    network.off('netConnectionPropertiesChange')
    network.off('netBlockStatusChange')
  }

  build() {
    Column({ space: 15 }) {
      Text('网络状态监控')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
      
      Row({ space: 10 }) {
        Text('连接状态:')
          .fontSize(14)
        Text(this.isConnected ? '已连接' : '未连接')
          .fontSize(14)
          .fontColor(this.isConnected ? '#34C759' : '#FF3B30')
      }
      
      Row({ space: 10 }) {
        Text('网络类型:')
          .fontSize(14)
        Text(this.networkType)
          .fontSize(14)
          .fontColor('#007DFF')
      }
    }
    .padding(20)
  }
}

四、网络请求封装与最佳实践

4.1 统一的HTTP客户端封装

class HttpClient {
  private static instance: HttpClient
  private httpRequest: http.HttpRequest
  private baseURL: string
  private interceptors: RequestInterceptor[] = []

  private constructor(baseURL: string) {
    this.baseURL = baseURL
    this.httpRequest = http.createHttp()
    this.setupInterceptors()
  }

  static getInstance(baseURL?: string): HttpClient {
    if (!HttpClient.instance) {
      if (!baseURL) {
        throw new Error('首次调用需要提供baseURL')
      }
      HttpClient.instance = new HttpClient(baseURL)
    }
    return HttpClient.instance
  }

  // 设置请求拦截器
  private setupInterceptors() {
    // 请求拦截器示例
    this.interceptors.push({
      onRequest: (config) => {
        // 添加认证token
        const token = this.getAuthToken()
        if (token) {
          config.header = config.header || {}
          config.header['Authorization'] = `Bearer ${token}`
        }
        return config
      },
      onResponse: (response) => {
        // 统一处理响应
        if (response.responseCode >= 400) {
          throw this.handleError(response)
        }
        return response
      },
      onError: (error) => {
        // 统一错误处理
        console.error('请求错误:', error)
        throw error
      }
    })
  }

  // 统一的GET方法
  async get<T>(endpoint: string, params?: Record<string, any>): Promise<T> {
    const url = this.buildURL(endpoint, params)
    
    const config: http.HttpRequestOptions = {
      method: http.RequestMethod.GET,
      header: { 'Content-Type': 'application/json' },
      usingCache: true
    }

    // 执行拦截器
    const finalConfig = this.runRequestInterceptors(config)
    
    const response = await this.httpRequest.request(url, finalConfig)
    const processedResponse = this.runResponseInterceptors(response)
    
    return processedResponse.result as T
  }

  // 统一的POST方法
  async post<T>(endpoint: string, data?: any): Promise<T> {
    const url = `${this.baseURL}${endpoint}`
    
    const config: http.HttpRequestOptions = {
      method: http.RequestMethod.POST,
      header: { 'Content-Type': 'application/json' },
      extraData: data ? JSON.stringify(data) : undefined
    }

    const finalConfig = this.runRequestInterceptors(config)
    const response = await this.httpRequest.request(url, finalConfig)
    const processedResponse = this.runResponseInterceptors(response)
    
    return processedResponse.result as T
  }

  // 构建完整URL
  private buildURL(endpoint: string, params?: Record<string, any>): string {
    let url = `${this.baseURL}${endpoint}`
    
    if (params) {
      const queryParams = new URLSearchParams(params).toString()
      url += `?${queryParams}`
    }
    
    return url
  }

  // 错误处理
  private handleError(response: http.HttpResponse): Error {
    switch (response.responseCode) {
      case http.ResponseCode.BAD_REQUEST:
        return new Error('请求参数错误')
      case http.ResponseCode.UNAUTHORIZED:
        return new Error('未授权访问')
      case http.ResponseCode.FORBIDDEN:
        return new Error('访问被禁止')
      case http.ResponseCode.NOT_FOUND:
        return new Error('资源不存在')
      case http.ResponseCode.INTERNAL_ERROR:
        return new Error('服务器内部错误')
      default:
        return new Error(`请求失败: ${response.responseCode}`)
    }
  }

  private runRequestInterceptors(config: http.HttpRequestOptions): http.HttpRequestOptions {
    return this.interceptors.reduce((acc, interceptor) => {
      return interceptor.onRequest ? interceptor.onRequest(acc) : acc
    }, config)
  }

  private runResponseInterceptors(response: http.HttpResponse): http.HttpResponse {
    return this.interceptors.reduce((acc, interceptor) => {
      return interceptor.onResponse ? interceptor.onResponse(acc) : acc
    }, response)
  }

  private getAuthToken(): string | null {
    // 从安全存储中获取token
    return localStorage.getItem('auth_token')
  }
}

interface RequestInterceptor {
  onRequest?(config: http.HttpRequestOptions): http.HttpRequestOptions
  onResponse?(response: http.HttpResponse): http.HttpResponse
  onError?(error: Error): Error
}

4.2 API服务层封装

// 具体的API服务
class UserApiService {
  private httpClient: HttpClient

  constructor() {
    this.httpClient = HttpClient.getInstance('https://api.example.com')
  }

  // 用户登录
  async login(credentials: LoginCredentials): Promise<AuthResponse> {
    return await this.httpClient.post<AuthResponse>('/auth/login', credentials)
  }

  // 获取用户信息
  async getUserProfile(userId: number): Promise<UserProfile> {
    return await this.httpClient.get<UserProfile>(`/users/${userId}`)
  }

  // 更新用户信息
  async updateUserProfile(userId: number, profile: Partial<UserProfile>): Promise<UserProfile> {
    return await this.httpClient.post<UserProfile>(`/users/${userId}`, profile)
  }

  // 分页获取用户列表
  async getUsers(page: number = 1, limit: number = 10): Promise<UserListResponse> {
    return await this.httpClient.get<UserListResponse>('/users', {
      page,
      limit
    })
  }
}

// 数据模型
interface LoginCredentials {
  username: string
  password: string
}

interface AuthResponse {
  token: string
  expiresIn: number
  user: UserProfile
}

interface UserProfile {
  id: number
  username: string
  email: string
  avatar?: string
  createdAt: string
}

interface UserListResponse {
  users: UserProfile[]
  total: number
  page: number
  limit: number
}

五、实战应用:新闻客户端

5.1 新闻数据获取

@Component
struct NewsClient {
  @State newsList: NewsItem[] = []
  @State isLoading: boolean = false
  @State errorMessage: string = ''

  private newsService = new NewsApiService()

  build() {
    Column({ space: 20 }) {
      Text('新闻客户端')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      
      if (this.isLoading) {
        this.buildLoadingState()
      } else if (this.errorMessage) {
        this.buildErrorState()
      } else {
        this.buildNewsList()
      }
      
      Button('刷新新闻')
        .onClick(() => this.loadNews())
        .enabled(!this.isLoading)
    }
    .width('100%')
    .padding(20)
    .onAppear(() => {
      this.loadNews()
    })
  }

  @Builder
  buildLoadingState() {
    Column({ space: 10 }) {
      Progress({ value: 50, total: 100 })
        .width(200)
      Text('加载中...')
        .fontSize(14)
        .fontColor('#666')
    }
  }

  @Builder
  buildErrorState() {
    Column({ space: 10 }) {
      Image($r('app.media.error'))
        .width(60)
        .height(60)
      Text(this.errorMessage)
        .fontSize(14)
        .fontColor('#FF3B30')
        .textAlign(TextAlign.Center)
    }
  }

  @Builder
  buildNewsList() {
    List({ space: 10 }) {
      ForEach(this.newsList, (news: NewsItem) => {
        ListItem() {
          NewsItemComponent({ news })
        }
      })
    }
    .layoutWeight(1)
  }

  async loadNews() {
    this.isLoading = true
    this.errorMessage = ''
    
    try {
      const response = await this.newsService.getNews()
      this.newsList = response.articles
    } catch (error) {
      this.errorMessage = error.message
      console.error('加载新闻失败:', error)
    } finally {
      this.isLoading = false
    }
  }
}

class NewsApiService {
  private httpClient: HttpClient

  constructor() {
    this.httpClient = HttpClient.getInstance('https://newsapi.org/v2')
  }

  async getNews(category: string = 'technology'): Promise<NewsResponse> {
    return await this.httpClient.get<NewsResponse>('/top-headlines', {
      country: 'us',
      category: category,
      apiKey: 'your-api-key-here' // 实际使用时替换为真实API密钥
    })
  }
}

interface NewsItem {
  title: string
  description: string
  url: string
  urlToImage: string
  publishedAt: string
  author: string
}

interface NewsResponse {
  status: string
  totalResults: number
  articles: NewsItem[]
}

六、错误处理与性能优化

6.1 全面的错误处理策略

class ErrorHandler {
  static handleNetworkError(error: any): string {
    if (error.code === http.ResponseCode.TIMEOUT) {
      return '请求超时,请检查网络连接'
    } else if (error.code === http.ResponseCode.UNAVAILABLE) {
      return '网络不可用,请检查网络设置'
    } else if (error.code >= 500) {
      return '服务器繁忙,请稍后重试'
    } else if (error.code >= 400) {
      return '请求错误,请检查输入参数'
    } else {
      return '网络请求失败,请重试'
    }
  }

  static isNetworkError(error: any): boolean {
    return error && (
      error.code === http.ResponseCode.UNAVAILABLE ||
      error.code === http.ResponseCode.TIMEOUT ||
      !navigator.onLine
    )
  }

  static shouldRetry(error: any): boolean {
    // 可重试的错误类型
    return this.isNetworkError(error) || error.code >= 500
  }
}

6.2 请求重试机制

async requestWithRetry(
  url: string, 
  options: http.HttpRequestOptions, 
  maxRetries: number = 3
): Promise<http.HttpResponse> {
  let lastError: Error
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await this.httpRequest.request(url, options)
      return response
      
    } catch (error) {
      lastError = error
      
      if (!ErrorHandler.shouldRetry(error) || attempt === maxRetries) {
        break
      }
      
      // 指数退避策略
      const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000)
      await this.sleep(delay)
      
      console.log(`请求失败,第${attempt}次重试...`)
    }
  }
  
  throw lastError
}

private sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms))
}

总结

通过本文的深入学习,您应该掌握了HarmonyOS网络请求的完整知识体系:

  1. 基础请求:GET、POST等HTTP方法的实现
  2. 高级特性:文件上传、网络状态监控等复杂场景
  3. 架构设计:统一的HTTP客户端封装和API服务层设计
  4. 实战应用:新闻客户端的完整数据流实现
  5. 错误处理:全面的异常处理和重试机制

网络请求是应用与外界交互的桥梁,良好的网络层设计能显著提升应用的用户体验和稳定性。

需要参加鸿蒙认证的请点击 鸿蒙认证链接

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐