从零开始掌握鸿蒙HTTP网络编程,让你的应用与世界互联

一、背景与动机:为什么HTTP请求如此重要?

说真的,现在的应用如果离了网络,基本就废了一大半功能。你想想看,新闻App得从服务器拉文章,电商App得获取商品列表,社交App得收发消息——这些背后全都是HTTP请求在默默工作。

鸿蒙系统提供了@ohos.net.http模块,这是官方推荐的网络请求方案。相比传统的WebView或者第三方库,它有几个明显优势:

原生性能优势:直接调用系统底层网络栈,延迟更低,内存占用更小。就像开车走专用道,比挤公交快多了。

API设计友好:支持Promise异步调用,配合ArkTS的async/await语法,代码写起来像同步逻辑一样清晰。再也不用陷入回调地狱了。

安全可控:系统级权限管理,支持HTTPS证书校验,企业级应用也不用担心数据泄露问题。

鸿蒙生态集成:自动适配不同设备的网络环境,手机、平板、车机、智能手表都能用同一套代码。

二、核心原理:HTTP请求在鸿蒙中如何工作?

先理解一个概念:HTTP请求的生命周期。这就像寄快递的过程——填单子、打包、发货、运输、签收、拆包。

创建HTTP请求对象

配置请求参数

发起请求

网络可用?

数据传输中...

触发错误回调

接收响应头

接收响应体

解析数据

销毁请求对象

2.1 核心类与接口

鸿蒙HTTP模块主要包含这几个关键角色:

类/接口 作用 使用场景
http.createHttp() 创建HTTP请求对象 每次请求前必须调用
HttpRequest 请求对象实例 配置参数、发送请求
HttpResponse 响应对象 获取状态码、响应头、响应体
HttpMethod HTTP方法枚举 GET/POST/PUT/DELETE等

2.2 请求流程详解

第一步:创建请求对象

import http from '@ohos.net.http';

// 每个请求都需要独立的对象
let httpRequest = http.createHttp();

第二步:配置并发送请求

// 支持Promise,可以用await
let response = await httpRequest.request(
  'https://api.example.com/data',  // 请求URL
  {
    method: http.RequestMethod.GET,  // 请求方法
    header: { 'Content-Type': 'application/json' },  // 请求头
    connectTimeout: 60000,  // 连接超时60秒
    readTimeout: 60000  // 读取超时60秒
  }
);

第三步:处理响应

if (response.responseCode === 200) {
  let result = JSON.parse(response.result as string);
  console.info('数据获取成功:', result);
}

第四步:销毁对象(重要!)

httpRequest.destroy();  // 释放资源,防止内存泄漏

三、代码实战:三种典型场景

场景一:GET请求获取数据列表

这是最常见的用法——从服务器拉取数据。比如获取新闻列表、商品目录、用户信息等。

import http from '@ohos.net.http';
import { BusinessError } from '@ohos.base';

// 新闻列表数据模型
interface NewsItem {
  id: number;
  title: string;
  author: string;
  publishTime: string;
  coverImage: string;
}

interface NewsResponse {
  code: number;
  message: string;
  data: NewsItem[];
}

// GET请求示例:获取新闻列表
async function fetchNewsList(page: number = 1, pageSize: number = 20): Promise<NewsItem[]> {
  let httpRequest = http.createHttp();
  
  try {
    // 构建带查询参数的URL
    const url = `https://api.news-app.com/v1/articles?page=${page}&size=${pageSize}`;
  
    // 发起GET请求
    let response = await httpRequest.request(url, {
      method: http.RequestMethod.GET,
      header: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer your_token_here',  // 认证token
        'User-Agent': 'HarmonyOS-NewsApp/1.0'
      },
      connectTimeout: 30000,  // 连接超时30秒
      readTimeout: 30000      // 读取超时30秒
    });
  
    // 检查响应状态码
    if (response.responseCode === http.ResponseCode.OK) {
      // 解析JSON响应
      let result: NewsResponse = JSON.parse(response.result as string);
    
      if (result.code === 0) {
        console.info(`成功获取${result.data.length}条新闻`);
        return result.data;
      } else {
        console.error(`业务错误: ${result.message}`);
        return [];
      }
    } else {
      console.error(`HTTP错误: ${response.responseCode}`);
      return [];
    }
  
  } catch (error) {
    let e = error as BusinessError;
    console.error(`请求异常: ${e.code} - ${e.message}`);
    return [];
  
  } finally {
    // 无论成功失败,都要销毁请求对象
    httpRequest.destroy();
  }
}

// 在UI组件中使用
@Entry
@Component
struct NewsListPage {
  @State newsList: NewsItem[] = [];
  @State isLoading: boolean = false;
  
  async aboutToAppear() {
    this.isLoading = true;
    this.newsList = await fetchNewsList(1, 20);
    this.isLoading = false;
  }
  
  build() {
    Column() {
      if (this.isLoading) {
        LoadingProgress()
          .width(50)
          .height(50)
      } else {
        List() {
          ForEach(this.newsList, (item: NewsItem) => {
            ListItem() {
              this.NewsCard(item)
            }
          }, (item: NewsItem) => item.id.toString())
        }
        .width('100%')
        .layoutWeight(1)
      }
    }
  }
  
  @Builder
  NewsCard(item: NewsItem) {
    Row() {
      Image(item.coverImage)
        .width(100)
        .height(80)
        .objectFit(ImageFit.Cover)
    
      Column() {
        Text(item.title)
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .maxLines(2)
      
        Text(`${item.author} · ${item.publishTime}`)
          .fontSize(12)
          .fontColor('#999')
      }
      .layoutWeight(1)
      .margin({ left: 10 })
    }
    .padding(10)
  }
}

场景二:POST请求提交表单数据

用户登录、注册、发布内容等场景,需要向服务器提交数据。

import http from '@ohos.net.http';
import { BusinessError } from '@ohos.base';

// 用户登录请求参数
interface LoginRequest {
  username: string;
  password: string;
  deviceId?: string;  // 设备标识,可选
}

// 登录响应数据
interface LoginResponse {
  code: number;
  message: string;
  data: {
    userId: string;
    token: string;
    refreshToken: string;
    expiresIn: number;
  };
}

// POST请求示例:用户登录
async function userLogin(credentials: LoginRequest): Promise<string | null> {
  let httpRequest = http.createHttp();
  
  try {
    // 添加设备信息
    let requestBody = {
      ...credentials,
      deviceId: 'HarmonyOS-Device-001',
      platform: 'HarmonyOS',
      timestamp: Date.now()
    };
  
    let response = await httpRequest.request(
      'https://api.example.com/v1/auth/login',
      {
        method: http.RequestMethod.POST,
        header: {
          'Content-Type': 'application/json',
          'Accept': 'application/json'
        },
        // POST请求需要序列化请求体
        extraData: JSON.stringify(requestBody),
        connectTimeout: 15000,
        readTimeout: 15000
      }
    );
  
    if (response.responseCode === http.ResponseCode.OK) {
      let result: LoginResponse = JSON.parse(response.result as string);
    
      if (result.code === 0 && result.data) {
        // 保存token到本地存储
        console.info('登录成功,用户ID:', result.data.userId);
        return result.data.token;
      } else {
        console.error('登录失败:', result.message);
        return null;
      }
    } else if (response.responseCode === http.ResponseCode.UNAUTHORIZED) {
      console.error('认证失败:用户名或密码错误');
      return null;
    } else {
      console.error('服务器错误:', response.responseCode);
      return null;
    }
  
  } catch (error) {
    let e = error as BusinessError;
    console.error('网络异常:', e.message);
    return null;
  
  } finally {
    httpRequest.destroy();
  }
}

// 登录页面组件
@Entry
@Component
struct LoginPage {
  @State username: string = '';
  @State password: string = '';
  @State isLogging: boolean = false;
  @State errorMsg: string = '';
  
  async handleLogin() {
    if (!this.username || !this.password) {
      this.errorMsg = '请输入用户名和密码';
      return;
    }
  
    this.isLogging = true;
    this.errorMsg = '';
  
    let token = await userLogin({
      username: this.username,
      password: this.password
    });
  
    this.isLogging = false;
  
    if (token) {
      // 登录成功,跳转到主页
      console.info('准备跳转主页');
    } else {
      this.errorMsg = '登录失败,请重试';
    }
  }
  
  build() {
    Column() {
      Text('用户登录')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 30 })
    
      TextInput({ placeholder: '用户名' })
        .width('80%')
        .height(50)
        .onChange((value) => this.username = value)
    
      TextInput({ placeholder: '密码' })
        .width('80%')
        .height(50)
        .type(InputType.Password)
        .margin({ top: 15 })
        .onChange((value) => this.password = value)
    
      if (this.errorMsg) {
        Text(this.errorMsg)
          .fontColor('#D0021B')
          .fontSize(14)
          .margin({ top: 10 })
      }
    
      Button(this.isLogging ? '登录中...' : '登录')
        .width('80%')
        .height(50)
        .margin({ top: 30 })
        .enabled(!this.isLogging)
        .onClick(() => this.handleLogin())
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

场景三:文件上传(multipart/form-data)

上传图片、视频等文件,需要使用multipart格式。

import http from '@ohos.net.http';
import { BusinessError } from '@ohos.base';
import fs from '@ohos.file.fs';

// 文件上传响应
interface UploadResponse {
  code: number;
  message: string;
  data: {
    fileId: string;
    url: string;
    size: number;
  };
}

// 文件上传示例:上传图片
async function uploadImage(imagePath: string): Promise<string | null> {
  let httpRequest = http.createHttp();
  
  try {
    // 读取文件内容
    let file = fs.openSync(imagePath, fs.OpenMode.READ_ONLY);
    let stat = fs.statSync(imagePath);
    let buffer = new ArrayBuffer(stat.size);
    fs.readSync(file.fd, buffer);
    fs.closeSync(file);
  
    // 构建multipart请求体
    let formData = {
      // 文件字段
      'file': {
        filename: imagePath.split('/').pop() || 'image.jpg',
        type: 'image/jpeg',
        content: buffer
      },
      // 其他表单字段
      'userId': '12345',
      'category': 'avatar'
    };
  
    let response = await httpRequest.request(
      'https://api.example.com/v1/upload/image',
      {
        method: http.RequestMethod.POST,
        header: {
          'Content-Type': 'multipart/form-data',
          'Authorization': 'Bearer your_token'
        },
        extraData: formData,
        connectTimeout: 60000,  // 文件上传可能较慢
        readTimeout: 60000
      }
    );
  
    if (response.responseCode === http.ResponseCode.OK) {
      let result: UploadResponse = JSON.parse(response.result as string);
    
      if (result.code === 0) {
        console.info('上传成功:', result.data.url);
        return result.data.url;
      }
    }
  
    return null;
  
  } catch (error) {
    let e = error as BusinessError;
    console.error('上传失败:', e.message);
    return null;
  
  } finally {
    httpRequest.destroy();
  }
}

// 图片上传组件
@Entry
@Component
struct ImageUploadPage {
  @State uploadedUrl: string = '';
  @State isUploading: boolean = false;
  
  async handleUpload() {
    this.isUploading = true;
  
    // 选择图片(需要配合Picker使用)
    let imagePath = '/data/local/tmp/selected_image.jpg';
    let url = await uploadImage(imagePath);
  
    this.isUploading = false;
  
    if (url) {
      this.uploadedUrl = url;
      console.info('图片已上传:', url);
    }
  }
  
  build() {
    Column() {
      if (this.uploadedUrl) {
        Image(this.uploadedUrl)
          .width(200)
          .height(200)
          .objectFit(ImageFit.Cover)
      } else {
        Column() {
          Text('+')
            .fontSize(40)
            .fontColor('#999')
          Text('点击上传图片')
            .fontSize(14)
            .fontColor('#999')
            .margin({ top: 10 })
        }
        .width(200)
        .height(200)
        .border({ width: 2, color: '#ddd', style: BorderStyle.Dashed })
        .justifyContent(FlexAlign.Center)
        .onClick(() => this.handleUpload())
      }
    
      if (this.isUploading) {
        Text('上传中...')
          .fontSize(14)
          .fontColor('#4A90E2')
          .margin({ top: 20 })
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

四、踩坑与注意事项

坑点一:忘记销毁请求对象

这是最常见的内存泄漏问题。每次创建http.createHttp()后,必须在请求完成后调用destroy()

// ❌ 错误示范:忘记销毁
async function badExample() {
  let httpRequest = http.createHttp();
  let response = await httpRequest.request('https://api.example.com/data');
  // 处理响应...
  // 忘记调用 destroy()!
}

// ✅ 正确做法:使用try-finally确保销毁
async function goodExample() {
  let httpRequest = http.createHttp();
  try {
    let response = await httpRequest.request('https://api.example.com/data');
    // 处理响应...
  } finally {
    httpRequest.destroy();  // 无论如何都会执行
  }
}

坑点二:响应体类型处理不当

response.result的类型可能是string、ArrayBuffer或Object,需要根据实际情况处理。

// 处理不同类型的响应
function handleResponse(response: http.HttpResponse) {
  let result = response.result;
  
  if (typeof result === 'string') {
    // JSON字符串,需要解析
    let data = JSON.parse(result);
    console.info('JSON数据:', data);
  
  } else if (result instanceof ArrayBuffer) {
    // 二进制数据,如图片、文件
    console.info('二进制数据,大小:', result.byteLength);
  
  } else if (typeof result === 'object') {
    // 已经是对象(部分API会自动解析)
    console.info('对象数据:', result);
  }
}

坑点三:超时时间设置不合理

默认超时可能不满足业务需求。网络差的场景下,短超时会导致频繁失败;大文件上传时,短超时直接报错。

// 根据场景设置合理超时
const TIMEOUT_CONFIG = {
  // 普通API请求:30秒
  API: { connectTimeout: 30000, readTimeout: 30000 },
  
  // 登录认证:15秒(要求快速响应)
  AUTH: { connectTimeout: 15000, readTimeout: 15000 },
  
  // 文件上传:120秒(大文件需要更长时间)
  UPLOAD: { connectTimeout: 60000, readTimeout: 120000 },
  
  // 实时数据:10秒(要求低延迟)
  REALTIME: { connectTimeout: 10000, readTimeout: 10000 }
};

坑点四:HTTP状态码判断不完整

只判断200是不够的,还要考虑201、204等其他成功状态码,以及各种错误码的处理。

// 完整的状态码处理
function handleStatusCode(response: http.HttpResponse): boolean {
  const code = response.responseCode;
  
  // 2xx 成功
  if (code >= 200 && code < 300) {
    return true;
  }
  
  // 根据具体状态码处理
  switch (code) {
    case http.ResponseCode.BAD_REQUEST:
      console.error('请求参数错误');
      break;
    case http.ResponseCode.UNAUTHORIZED:
      console.error('未授权,请先登录');
      // 跳转到登录页
      break;
    case http.ResponseCode.FORBIDDEN:
      console.error('无权限访问');
      break;
    case http.ResponseCode.NOT_FOUND:
      console.error('资源不存在');
      break;
    case http.ResponseCode.INTERNAL_ERROR:
      console.error('服务器内部错误');
      break;
    default:
      console.error(`未知错误: ${code}`);
  }
  
  return false;
}

五、HarmonyOS 6适配指南

HarmonyOS 6对HTTP模块进行了若干优化和变更,需要注意以下适配点:

5.1 API变更

新增:请求优先级设置

HarmonyOS 6支持为请求设置优先级,在网络拥堵时优先处理重要请求。

// HarmonyOS 6 新增优先级配置
let httpRequest = http.createHttp();

let response = await httpRequest.request('https://api.example.com/data', {
  method: http.RequestMethod.GET,
  // 新增:请求优先级(HIGH/MEDIUM/LOW)
  priority: http.RequestPriority.HIGH,
  // 新增:是否允许蜂窝网络
  usingProtocol: http.HttpProtocol.HTTP1_1
});

变更:响应头获取方式

// HarmonyOS 5及之前
let headers = response.header;

// HarmonyOS 6:响应头为Map类型,需使用get方法
let headers = response.header;
let contentType = headers.get('Content-Type');
let server = headers.get('Server');

5.2 行为变更

自动重定向处理

HarmonyOS 6默认自动处理重定向(3xx响应),无需手动处理。

// HarmonyOS 6:默认自动跟随重定向
// 如需禁用自动重定向
let response = await httpRequest.request(url, {
  method: http.RequestMethod.GET,
  // 禁用自动重定向
  maxRedirects: 0
});

连接复用优化

系统会自动复用TCP连接,减少握手开销。无需手动配置,但要注意:

// 每个域名建议复用同一个HTTP对象池
class HttpConnectionPool {
  private connections: Map<string, http.HttpRequest> = new Map();
  
  getConnection(domain: string): http.HttpRequest {
    if (!this.connections.has(domain)) {
      this.connections.set(domain, http.createHttp());
    }
    return this.connections.get(domain)!;
  }
  
  // 应用退出时清理
  destroyAll() {
    this.connections.forEach((conn) => conn.destroy());
    this.connections.clear();
  }
}

5.3 完整适配示例

import http from '@ohos.net.http';

// HarmonyOS 6 HTTP请求封装
class HarmonyOS6HttpClient {
  private request: http.HttpRequest | null = null;
  
  // 创建请求(适配HarmonyOS 6)
  async request<T>(
    url: string,
    options: {
      method: http.RequestMethod;
      data?: object | string;
      headers?: Record<string, string>;
      timeout?: number;
      priority?: http.RequestPriority;
    }
  ): Promise<T | null> {
    this.request = http.createHttp();
  
    try {
      let requestOptions: http.HttpRequestOptions = {
        method: options.method,
        header: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
          ...options.headers
        },
        connectTimeout: options.timeout || 30000,
        readTimeout: options.timeout || 30000
      };
    
      // HarmonyOS 6:设置优先级
      if (options.priority) {
        requestOptions.priority = options.priority;
      }
    
      // 处理请求体
      if (options.data) {
        if (typeof options.data === 'string') {
          requestOptions.extraData = options.data;
        } else {
          requestOptions.extraData = JSON.stringify(options.data);
        }
      }
    
      let response = await this.request.request(url, requestOptions);
    
      // 完整状态码判断
      if (response.responseCode >= 200 && response.responseCode < 300) {
        if (typeof response.result === 'string') {
          return JSON.parse(response.result) as T;
        }
        return response.result as T;
      }
    
      return null;
    
    } catch (error) {
      console.error('HTTP请求异常:', error);
      return null;
    
    } finally {
      if (this.request) {
        this.request.destroy();
        this.request = null;
      }
    }
  }
}

// 使用示例
let client = new HarmonyOS6HttpClient();

// 高优先级请求
let userData = await client.request<UserInfo>(
  'https://api.example.com/user/profile',
  {
    method: http.RequestMethod.GET,
    priority: http.RequestPriority.HIGH,
    timeout: 15000
  }
);

六、总结

HTTP请求是应用与外界通信的桥梁,掌握@ohos.net.http模块是鸿蒙开发的基本功。回顾本文要点:

核心流程:创建对象 → 配置参数 → 发送请求 → 处理响应 → 销毁对象。牢记这个五步法,就不会遗漏关键环节。

三大场景:GET请求拉数据、POST请求提数据、文件上传传资源。每种场景都有对应的配置要点和注意事项。

四个坑点:忘记销毁对象导致内存泄漏、响应体类型判断不当、超时设置不合理、状态码处理不完整。遇到问题时,先排查这四个方面。

HarmonyOS 6适配:新增请求优先级、响应头获取方式变更、自动重定向优化、连接复用机制。升级时需要相应调整代码。

网络请求看似简单,实则细节繁多。建议在实际项目中封装统一的HTTP工具类,处理通用逻辑(如token注入、错误处理、日志记录),让业务代码更专注于数据处理而非网络细节。

下一篇文章,我们将深入HTTP请求配置,探讨超时策略、重试机制、拦截器设计等进阶话题,敬请期待!

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐