HarmonyOS6.0开发实战:HTTP 网络请求与 API 交互全指南

Hello!我是小邢哥,专注编程实战技巧分享,深耕AI与机器学习领域多年,致力于把复杂技术拆解成易懂教程,陪大家在AI时代稳步进阶。

在鸿蒙应用开发中,网络请求是连接 "本地应用" 与 "远程服务" 的核心桥梁 ------ 无论是拉取后端 API 数据(如用户信息、商品列表)、上传文件,还是调用第三方服务(如天气接口、地图接口),都依赖 HTTP 协议实现。

一、HTTP 协议:鸿蒙网络请求的 "底层规则"

HTTP 作为应用层协议,是鸿蒙网络请求的基础,但在鸿蒙开发中,我们需重点关注其与 "鸿蒙系统特性" 结合的部分 ------ 比如协议版本支持、无状态问题的鸿蒙解决方案、安全适配等。

1.1 鸿蒙开发中需关注的 HTTP 核心特性

回顾 HTTP 协议的核心特性,以下 4 点直接影响鸿蒙网络请求的实现:

核心特性 鸿蒙开发中的影响与适配
请求 - 响应模式 鸿蒙应用作为客户端,需主动发起 GET/POST 等请求;服务器被动响应后,需处理响应体(如 JSON 解析)、状态码(如 404/500 错误)
无状态特性 鸿蒙应用需通过 "请求头携带 Token"(如 JWT)或 "Cookie 存储" 实现会话跟踪(鸿蒙推荐用 Token,避免 Cookie 跨设备同步问题)
可扩展性 鸿蒙支持自定义请求头(如Authorization: Bearer xxx)、HTTP/2 协议(默认开启,提升传输效率)、HTTPS 加密(强制要求,HTTP 需额外配置)
依赖 TCP 鸿蒙的@ohos.net.http模块已封装 TCP 连接管理,开发者无需关心底层连接建立 / 断开,只需关注请求参数与响应处理

1.2 鸿蒙对 HTTP 的特殊适配

  1. 协议版本支持:鸿蒙 4.0 及以上版本默认支持 HTTP/1.1、HTTP/2,部分设备支持 HTTP/3(基于 QUIC),无需额外配置即可享受高性能传输;

  2. 安全限制:鸿蒙默认禁止明文 HTTP 请求(http://),需在module.json5中配置 "网络安全策略" 才能使用;

  3. 权限管控:所有网络请求必须申请INTERNET权限,否则会直接抛出权限异常。

二、前置准备:鸿蒙网络请求的 "准入配置"

在写一行网络请求代码前,必须完成权限配置安全配置(针对 HTTP),这是鸿蒙系统的强制要求,也是新手最易踩坑的点。

2.1 配置网络权限(必做)

鸿蒙应用需在module.json5(模块配置文件)中声明ohos.permission.INTERNET权限,否则无法发起任何网络请求。

配置步骤:
  1. 打开项目的src/main/module.json5文件;

  2. 在module -> reqPermissions数组中添加权限声明:

    {
    "module": {
    "name": "entry",
    "type": "entry",
    "description": "鸿蒙网络请求示例",
    "mainElement": "EntryAbility",
    "deviceTypes": ["phone", "tablet"],
    // 网络权限配置
    "reqPermissions": [
    {
    "name": "ohos.permission.INTERNET", // 网络权限
    "reason": "需要访问网络获取API数据", // 权限申请理由(用户可见)
    "usedScene": {
    "ability": ["EntryAbility"],
    "when": "always" // 始终需要该权限
    }
    }
    ],
    // 其他配置...
    }
    }

注意:鸿蒙 6.0 及以上版本中,usedScene.when支持inuse(仅使用时申请),更符合隐私规范,建议优先使用。

2.2 明文 HTTP 请求配置(可选)

鸿蒙默认只允许https://请求(加密传输),若需调用http://接口(如测试环境接口),需额外配置 "网络安全策略":

配置步骤:
  1. 在src/main/resources/rawfile目录下创建network_security_config.xml文件(若rawfile不存在则新建);

  2. 写入安全配置,允许指定域名的 HTTP 请求:

    <?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true" /> </network-security-config>
  3. 在module.json5的module -> metaData中引用该配置:

    {
    "module": {
    // 其他配置...
    "metaData": {
    "customData": [
    {
    "name": "ohos.application.networkSecurityConfig",
    "value": "rawfile/network_security_config.xml"
    }
    ]
    }
    }
    }

警告:生产环境必须使用 HTTPS(https://),禁止配置cleartextTrafficPermitted="true",避免数据泄露风险。

三、核心实战:鸿蒙 HTTP 请求 API 全解析

鸿蒙提供@ohos.net.http模块实现 HTTP 请求,支持 GET(查询)、POST(提交)、PUT(更新)、DELETE(删除)等所有 HTTP 方法。以下从 "基础流程" 到 "实战示例" 逐步讲解,代码可直接在鸿蒙 Page 组件中运行。

3.1 鸿蒙 HTTP 请求的核心流程

无论哪种请求方法,鸿蒙 HTTP 请求都遵循 "5 步流程",确保资源正确释放(避免内存泄漏):

  1. 导入模块 :引入@ohos.net.http的核心类;

  2. 创建请求对象:实例化Http对象,管理单个请求的生命周期;

  3. 配置请求参数:设置 URL、方法、请求头、超时等;

  4. 发送请求并处理响应:获取状态码、响应头、响应体,解析数据(如 JSON);

  5. 销毁请求对象:请求结束后调用destroy(),释放资源。

3.2 实战 1:GET 请求(查询数据)

GET 请求用于 "获取资源"(如拉取列表、详情),参数通常拼接在 URL 后(如https://api.xxx.com/list?page=1\&size=10)。

完整代码示例(拉取图片 API):
复制代码
import http from '@ohos.net.http';
import { JsonUtils } from '../utils/JsonUtils'; // 复用前文封装的JSON工具类
import { Column, Button, Text, Progress, Toast } from '@ohos.arkui-components';

@Entry
@Component
struct HttpGetDemo {
  // 状态管理:加载中、请求结果、错误信息
  @State isLoading: boolean = false;
  @State result: string = '';
  @State errorMsg: string = '';

  build() {
    Column({ space: 20 }) {
      Text('HTTP GET请求示例')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)

      // 加载状态显示
      if (this.isLoading) {
        Progress({ value: 50, total: 100, type: ProgressType.CIRCULAR })
        Text('正在拉取数据...')
          .fontSize(14)
          .fontColor('#666')
      }

      // 错误信息显示
      if (this.errorMsg) {
        Text(`错误:${this.errorMsg}`)
          .fontSize(14)
          .fontColor('#ff4444')
          .backgroundColor('#fff3f3')
          .padding(10)
          .borderRadius(5)
          .width('90%')
      }

      // 结果显示(JSON格式化)
      if (this.result && !this.isLoading && !this.errorMsg) {
        Text('拉取结果:')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
          .width('90%')
          .textAlign(TextAlign.Start)
        
        Text(this.result)
          .fontSize(12)
          .backgroundColor('#f5f5f5')
          .padding(10)
          .borderRadius(5)
          .width('90%')
          .textAlign(TextAlign.Start)
          .whiteSpace(WhiteSpace.PreWrap) // 保留换行
      }

      // 发起GET请求按钮
      Button('发起GET请求(拉取女生图片API)')
        .width('90%')
        .backgroundColor('#007DFF')
        .fontColor('#fff')
        .onClick(() => this.sendGetRequest())
    }
    .height('100%')
    .padding(20)
    .backgroundColor('#fff')
  }

  /**
   * 发送GET请求:拉取https://api.52vmy.cn/api/img/tu/girl接口数据
   */
  private async sendGetRequest() {
    // 1. 重置状态
    this.isLoading = true;
    this.errorMsg = '';
    this.result = '';

    // 2. 创建HTTP请求对象
    let httpRequest: http.HttpRequest = http.createHttp();

    try {
      // 3. 配置请求参数并发送请求
      // 注意:GET参数可直接拼接在URL后,或通过params配置(鸿蒙会自动拼接)
      const response = await httpRequest.request(
        'https://api.52vmy.cn/api/img/tu/girl', // 请求URL
        {
          method: http.RequestMethod.GET, // 请求方法
          header: {
            'Content-Type': 'application/json', // 告知服务器响应数据为JSON
            'User-Agent': 'HarmonyOS-App/1.0' // 自定义请求头(标识应用)
          },
          connectTimeout: 5000, // 连接超时:5秒(默认30秒)
          readTimeout: 5000 // 读取超时:5秒(默认30秒)
          // GET参数可选配置(与URL拼接等效)
          // params: {
          //   count: 1 // 示例:请求1条数据(需接口支持)
          // }
        }
      );

      // 4. 处理响应:先判断状态码
      if (response.responseCode === 200) {
        // 响应体是ArrayBuffer,需转为字符串
        const responseBody = String.fromCharCode.apply(null, new Uint8Array(response.result as ArrayBuffer));
        // 用JSON工具类格式化(便于显示)
        this.result = JsonUtils.stringify(JSON.parse(responseBody), 2);
        Toast.show({ message: 'GET请求成功' });
      } else {
        // 非200状态码(如404、500)
        this.errorMsg = `请求失败,状态码:${response.responseCode}`;
      }
    } catch (error) {
      // 5. 捕获异常(如网络断开、超时)
      this.errorMsg = `请求异常:${(error as Error).message}`;
    } finally {
      // 6. 无论成功/失败,都销毁请求对象(释放资源)
      httpRequest.destroy();
      this.isLoading = false;
    }
  }
}

3.3 实战 2:POST 请求(提交数据)

POST 请求用于 "提交资源"(如登录、上传表单、创建数据),参数通常放在 "请求体" 中(而非 URL),支持application/json(JSON 格式)、application/x-www-form-urlencoded(表单格式)两种常见类型。

2.1 POST 提交 JSON 格式(最常用)

适用于后端接口要求 "JSON 参数" 的场景(如登录接口):

复制代码
/**
 * 发送POST请求(JSON格式参数)
 * 示例:模拟登录接口(假设接口为https://api.xxx.com/login)
 */
private async sendPostJsonRequest() {
  this.isLoading = true;
  this.errorMsg = '';
  this.result = '';

  let httpRequest: http.HttpRequest = http.createHttp();

  try {
    // 1. 准备JSON参数(登录账号密码)
    const loginParams = {
      username: 'harmony_dev',
      password: '123456' // 实际开发中需加密传输,禁止明文!
    };

    // 2. 发送POST请求
    const response = await httpRequest.request(
      'https://api.xxx.com/login', // 模拟登录接口URL
      {
        method: http.RequestMethod.POST,
        header: {
          'Content-Type': 'application/json', // 关键:声明参数为JSON格式
          'Authorization': 'Basic dGVzdDp0ZXN0' // 若接口需要基础认证,可添加
        },
        connectTimeout: 5000,
        readTimeout: 5000,
        // 3. 把JSON参数转为字符串,放入请求体
        extraData: JSON.stringify(loginParams)
      }
    );

    // 4. 处理响应
    if (response.responseCode === 200) {
      const responseBody = String.fromCharCode.apply(null, new Uint8Array(response.result as ArrayBuffer));
      const responseJson = JSON.parse(responseBody);
      // 示例:提取Token(登录成功后后端返回)
      const token = responseJson.data.token;
      // 存储Token(后续请求携带,实现会话跟踪)
      // PreferencesUtil.put('user_token', token); // 可封装Preferences工具类
      this.result = JsonUtils.stringify(responseJson, 2);
      Toast.show({ message: `登录成功,Token:${token.substring(0, 10)}...` });
    } else {
      this.errorMsg = `登录失败,状态码:${response.responseCode}`;
    }
  } catch (error) {
    this.errorMsg = `登录异常:${(error as Error).message}`;
  } finally {
    httpRequest.destroy();
    this.isLoading = false;
  }
}
2.2 POST 提交表单格式

适用于后端接口要求 "表单参数" 的场景(如传统 Web 接口):

复制代码
/**
 * 发送POST请求(表单格式参数)
 * 示例:模拟用户注册接口
 */
private async sendPostFormRequest() {
  this.isLoading = true;
  this.errorMsg = '';
  this.result = '';

  let httpRequest: http.HttpRequest = http.createHttp();

  try {
    // 1. 准备表单参数(转为URL编码格式)
    const formParams = new URLSearchParams();
    formParams.append('username', 'new_harmony_user');
    formParams.append('email', 'user@example.com');
    formParams.append('password', 'encrypted_pwd'); // 实际开发需加密

    // 2. 发送POST请求
    const response = await httpRequest.request(
      'https://api.xxx.com/register', // 模拟注册接口URL
      {
        method: http.RequestMethod.POST,
        header: {
          // 关键:声明参数为表单格式
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        connectTimeout: 5000,
        readTimeout: 5000,
        // 3. 表单参数转为字符串,放入请求体
        extraData: formParams.toString()
      }
    );

    // 4. 处理响应
    if (response.responseCode === 200) {
      const responseBody = String.fromCharCode.apply(null, new Uint8Array(response.result as ArrayBuffer));
      this.result = JsonUtils.stringify(JSON.parse(responseBody), 2);
      Toast.show({ message: '注册成功' });
    } else {
      this.errorMsg = `注册失败,状态码:${response.responseCode}`;
    }
  } catch (error) {
    this.errorMsg = `注册异常:${(error as Error).message}`;
  } finally {
    httpRequest.destroy();
    this.isLoading = false;
  }
}

四、鸿蒙网络请求的 "避坑指南" 与高级特性

4.1 常见错误与解决方案

错误场景 原因 解决方案
权限异常(permission denied) 未配置INTERNET权限 在module.json5的reqPermissions中添加权限
HTTP 请求被拒绝(cleartext not permitted) 鸿蒙禁止明文 HTTP 请求 1. 改用 HTTPS;2. 配置network_security_config.xml
超时异常(timeout) 网络差、接口响应慢、超时时间设置过短 1. 延长connectTimeout/readTimeout(建议 5-10 秒);2. 增加网络状态判断
响应体解析失败(JSON.parse error) 响应体不是 JSON 格式、接口返回错误信息(如 HTML) 1. 先打印responseBody确认格式;2. 增加解析异常捕获
跨域问题(CORS error) 前端直接调用非同源接口(浏览器环境常见,鸿蒙应用端少见) 1. 后端配置 CORS 头(Access-Control-Allow-Origin);2. 通过后端代理转发请求

4.2 高级特性:网络状态监听

在发起请求前,先判断设备是否联网,可避免无效请求(提升用户体验)。鸿蒙提供@ohos.net.connection模块监听网络状态:

复制代码
import connection from '@ohos.net.connection';

/**
 * 检查设备是否联网
 * @returns true:联网;false:未联网
 */
private async checkNetworkStatus(): Promise<boolean> {
  try {
    // 获取当前网络状态
    const netStatus = await connection.getActiveNetworkInfo();
    // 网络类型:1=无网络,2=移动网络,3=WiFi
    return netStatus.type !== connection.NetworkType.NETWORK_TYPE_NONE;
  } catch (error) {
    console.error('检查网络状态失败:', error);
    return false;
  }
}

// 在请求前调用
private async sendGetRequest() {
  // 先检查网络
  const isOnline = await this.checkNetworkStatus();
  if (!isOnline) {
    this.errorMsg = '当前无网络,请检查网络设置';
    Toast.show({ message: '无网络连接' });
    return;
  }

  // 后续请求逻辑...
}

4.3 高级特性:请求取消

若用户在请求过程中退出页面,需取消未完成的请求,避免内存泄漏或无效回调。鸿蒙的HttpRequest支持abort()方法:

复制代码
// 1. 定义请求对象(改为类成员变量,便于外部访问)
private httpRequest: http.HttpRequest | null = null;

// 2. 发起请求时赋值
private async sendGetRequest() {
  this.httpRequest = http.createHttp(); // 赋值给类成员
  try {
    const response = await this.httpRequest.request(/* 参数省略 */);
    // ...处理响应
  } catch (error) {
    // 捕获取消请求的异常(abort会触发error)
    if ((error as Error).message.includes('abort')) {
      console.log('请求已取消');
      this.errorMsg = '请求已取消';
    } else {
      this.errorMsg = `请求异常:${(error as Error).message}`;
    }
  }
}

// 3. 页面销毁时取消请求(如退出Page组件)
aboutToDisappear() {
  if (this.httpRequest) {
    this.httpRequest.abort(); // 取消请求
    this.httpRequest.destroy(); // 销毁对象
    this.httpRequest = null;
  }
}

五、最佳实践:封装鸿蒙网络工具类

实际开发中,若每个组件都写重复的请求逻辑(创建对象、配置头、错误处理),会导致代码冗余。建议封装一个HttpUtil工具类,统一管理网络请求。

5.1 封装HttpUtil.ets

复制代码
// utils/HttpUtil.ets
import http from '@ohos.net.http';
import connection from '@ohos.net.connection';
import { PreferencesUtil } from './PreferencesUtil'; // 封装的Preferences工具类(用于存Token)

/**
 * 鸿蒙HTTP网络工具类:统一请求配置、错误处理、Token携带
 */
export class HttpUtil {
  // 基础URL(不同环境可切换,如开发/测试/生产)
  private static BASE_URL = 'https://api.52vmy.cn';

  /**
   * 统一发送HTTP请求
   * @param url 接口路径(如'/api/img/tu/girl')
   * @param method 请求方法(GET/POST/PUT/DELETE)
   * @param params 请求参数(GET:拼接URL;POST:放入请求体)
   * @param header 自定义请求头(会覆盖默认头)
   * @returns 解析后的JSON响应体
   */
  static async request<T>(
    url: string,
    method: http.RequestMethod = http.RequestMethod.GET,
    params?: Record<string, any>,
    header?: Record<string, string>
  ): Promise<T> {
    // 1. 检查网络状态
    const isOnline = await this.checkNetwork();
    if (!isOnline) {
      throw new Error('当前无网络,请检查网络设置');
    }

    // 2. 创建请求对象
    const httpRequest = http.createHttp();
    // 完整URL(基础URL + 接口路径)
    const fullUrl = this.BASE_URL + url;

    try {
      // 3. 配置默认请求头(所有请求共享)
      const defaultHeader: Record<string, string> = {
        'Content-Type': 'application/json',
        'User-Agent': 'HarmonyOS-App/1.0',
        // 自动携带Token(登录后存储,未登录则不携带)
        'Authorization': await this.getTokenHeader()
      };

      // 4. 合并自定义请求头(自定义会覆盖默认)
      const finalHeader = { ...defaultHeader, ...header };

      // 5. 处理请求参数
      let requestConfig: http.HttpRequestOptions = {
        method,
        header: finalHeader,
        connectTimeout: 8000,
        readTimeout: 8000
      };

      // 处理GET参数(拼接URL)
      if (method === http.RequestMethod.GET && params) {
        const queryStr = new URLSearchParams(params).toString();
        requestConfig.url = queryStr ? `${fullUrl}?${queryStr}` : fullUrl;
      }

      // 处理POST/PUT参数(放入请求体)
      if ([http.RequestMethod.POST, http.RequestMethod.PUT].includes(method) && params) {
        requestConfig.url = fullUrl;
        // 根据Content-Type判断参数格式
        if (finalHeader['Content-Type'] === 'application/x-www-form-urlencoded') {
          requestConfig.extraData = new URLSearchParams(params).toString();
        } else {
          requestConfig.extraData = JSON.stringify(params);
        }
      }

      // 6. 发送请求
      const response = await httpRequest.request(requestConfig.url || fullUrl, requestConfig);

      // 7. 处理响应状态码
      if (response.responseCode < 200 || response.responseCode >= 300) {
        throw new Error(`请求失败,状态码:${response.responseCode}`);
      }

      // 8. 解析响应体(ArrayBuffer → 字符串 → JSON)
      const responseBody = String.fromCharCode.apply(null, new Uint8Array(response.result as ArrayBuffer));
      const responseJson = JSON.parse(responseBody) as T;

      return responseJson;
    } catch (error) {
      // 统一错误处理(可添加日志上报)
      console.error(`HTTP请求失败(${fullUrl}):`, error);
      throw error; // 抛出错误,让调用方处理UI反馈
    } finally {
      // 9. 销毁请求对象
      httpRequest.destroy();
    }
  }

  /**
   * 检查网络状态
   */
  private static async checkNetwork(): Promise<boolean> {
    try {
      const netStatus = await connection.getActiveNetworkInfo();
      return netStatus.type !== connection.NetworkType.NETWORK_TYPE_NONE;
    } catch (error) {
      console.error('检查网络状态失败:', error);
      return false;
    }
  }

  /**
   * 获取Token请求头(格式:Bearer xxx)
   */
  private static async getTokenHeader(): Promise<string> {
    try {
      // 从Preferences中获取存储的Token
      const token = await PreferencesUtil.get('user_token', '');
      return token ? `Bearer ${token}` : '';
    } catch (error) {
      console.error('获取Token失败:', error);
      return '';
    }
  }

  // 封装常用请求方法(简化调用)
  static async get<T>(url: string, params?: Record<string, any>, header?: Record<string, string>): Promise<T> {
    return this.request<T>(url, http.RequestMethod.GET, params, header);
  }

  static async post<T>(url: string, params?: Record<string, any>, header?: Record<string, string>): Promise<T> {
    return this.request<T>(url, http.RequestMethod.POST, params, header);
  }

  static async put<T>(url: string, params?: Record<string, any>, header?: Record<string, string>): Promise<T> {
    return this.request<T>(url, http.RequestMethod.PUT, params, header);
  }

  static async delete<T>(url: string, params?: Record<string, any>, header?: Record<string, string>): Promise<T> {
    return this.request<T>(url, http.RequestMethod.DELETE, params, header);
  }
}

5.2 工具类使用示例

复制代码
// 组件中调用(简化90%代码)
private async useHttpUtil() {
  this.isLoading = true;
  try {
    // 1. 调用GET接口(拉取图片API)
    const getResult = await HttpUtil.get('/api/img/tu/girl', { count: 1 });
    this.result = JsonUtils.stringify(getResult, 2);

    // 2. 调用POST接口(模拟登录)
    // const loginResult = await HttpUtil.post('/login', {
    //   username: 'harmony_dev',
    //   password: 'encrypted_pwd'
    // });
    // await PreferencesUtil.put('user_token', loginResult.data.token); // 存储Token

    Toast.show({ message: '请求成功' });
  } catch (error) {
    this.errorMsg = (error as Error).message;
    Toast.show({ message: `请求失败:${this.errorMsg}` });
  } finally {
    this.isLoading = false;
  }
}

六、总结

鸿蒙 HTTP 网络请求的核心是 "@ohos.net.http模块 + 前置配置 + 统一封装":

  1. 前置配置是基础:必须申请INTERNET权限,HTTPS 优先,HTTP 需额外配置安全策略;

  2. 核心流程要牢记:创建请求对象 → 配置参数 → 发送请求 → 处理响应 → 销毁对象,避免内存泄漏;

  3. 错误处理不可少:网络状态、超时、状态码、解析异常需全面覆盖;

  4. 封装工具类是关键:统一管理基础 URL、请求头、Token,减少重复代码,提升维护效率。

实际开发中,还需结合鸿蒙的 "分布式能力"(如跨设备网络共享)、"数据安全"(如 Token 加密存储、HTTPS 证书验证)进一步优化,让网络请求更健壮、更安全。

相关推荐
HaiLang_IT7 小时前
2026届 网络与信息安全专业毕业设计选题推荐与指导(含热门研究方向)
网络·安全·信息安全
万少7 小时前
八千字 《宠知汇》HarmonyOS应用案例热点技术解析
harmonyos
写点什么吧7 小时前
网络原理-进阶
网络
爱笑的眼睛118 小时前
深入探索HarmonyOS文件压缩与解压缩API:从基础到高级应用
华为·harmonyos
瑞禧生物ruixibio10 小时前
CAS号:868141-12-2,UDP-6-N3-Galactose ,分子特点
网络·网络协议·udp
鸿蒙小白龙14 小时前
OpenHarmony Location Kit技术详解:定位、地理围栏与编码转换
华为·harmonyos·open harmony
FAFU_kyp18 小时前
Spring Boot 邮件发送系统 - 从零到精通教程
java·网络·spring boot
报错小能手18 小时前
计算机网络自顶向下方法25——运输层 TCP流量控制 连接管理 “四次挥手”的优化
服务器·网络·计算机网络
郭源潮119 小时前
《Muduo网络库:实现TcpServer类终章》
服务器·网络·c++·网络库