查看: 322|回复: 3

鸿蒙Flutter http包网络请求踩坑实录:权限、编码与状态

[复制链接]
发表于 昨天 11:00 | 显示全部楼层 |阅读模式
在鸿蒙上使用Flutter开发应用时,网络请求是绕不开的基础能力。相比ArkTS的@ohos.net.http模块(回调风格),Flutter的http包凭借简洁的async/await写法更受开发者青睐。本文以一个完整的Demo为例,记录在鸿蒙Flutter项目中用http包调用REST API的实践过程,重点梳理参数配置、状态管理以及常见踩坑点。
  1. dependencies:
  2.   http: ^1.2.2
复制代码
添加依赖后,无需额外配置即可在鸿蒙设备上运行。如需拦截器、请求取消等高级功能,可后续迁移至dio。

GET请求:拉取列表

最基本的操作是发送GET请求并解析JSON。代码如下:
  1. import 'dart:convert';
  2. import 'package:http/http.dart' as http;
  3. Future<void> _fetchPosts() async {
  4.   final resp = await http.get(
  5.     Uri.parse('https://jsonplaceholder.typicode.com/posts?_limit=20'),
  6.   );
  7.   if (resp.statusCode == 200) {
  8.     final list = jsonDecode(resp.body) as List;
  9.     final posts = list.map((e) => _Post.fromJson(e)).toList();
  10.   }
  11. }
复制代码
http.get返回Response对象,statusCode为状态码,body为响应体字符串。jsonDecode将JSON字符串转换为Dart的Map或List,再通过fromJson工厂构造函数转为强类型Model。

Model类封装

建议将JSON解析封装为Model类,避免业务代码中散落json['xxx']:
  1. class Post {
  2.   final int id;
  3.   final String title;
  4.   final String body;
  5.   Post({required this.id, required this.title, required this.body});
  6.   factory Post.fromJson(Map<String, dynamic> json) {
  7.     return Post(
  8.       id: json['id'] as int,
  9.       title: json['title'] as String,
  10.       body: json['body'] as String,
  11.     );
  12.   }
  13. }
复制代码
字段较多时可使用json_serializable自动生成,但小规模手写更直观。

状态管理:loading / success / error

网络请求至少需要三种状态:加载中、成功、失败。使用枚举管理比布尔值更清晰:
  1. enum _LoadState { idle, loading, success, error }
  2. _LoadState _state = _LoadState.idle;
  3. List<_Post> _posts = [];
  4. String _error = '';
复制代码
UI根据状态渲染不同组件:
  1. Widget _buildBody() {
  2.   switch (_state) {
  3.     case _LoadState.loading:
  4.       return const Center(child: CircularProgressIndicator());
  5.     case _LoadState.error:
  6.       return _buildError();
  7.     case _LoadState.success:
  8.     case _LoadState.idle:
  9.       return _posts.isEmpty ? _buildEmpty() : _buildList();
  10.   }
  11. }
复制代码

GET详情与POST请求

点选列表项获取详情:
  1. Future<void> _fetchDetail(int id) async {
  2.   final resp = await http.get(
  3.     Uri.parse('https://jsonplaceholder.typicode.com/posts/$id'),
  4.   );
  5.   if (resp.statusCode == 200) {
  6.     final data = jsonDecode(resp.body);
  7.     // 使用 data['title'], data['body']
  8.   }
  9. }
复制代码
POST创建资源需设置Content-Type头部:
  1. Future<void> _createPost() async {
  2.   final resp = await http.post(
  3.     Uri.parse('https://jsonplaceholder.typicode.com/posts'),
  4.     headers: {'Content-Type': 'application/json'},
  5.     body: jsonEncode({
  6.       'title': '鸿蒙Flutter测试',
  7.       'body': '通过http.post创建',
  8.       'userId': 1,
  9.     }),
  10.   );
  11.   if (resp.statusCode == 201) {
  12.     final data = jsonDecode(resp.body);
  13.     // data['id'] 为新ID
  14.   }
  15. }
复制代码
注意:POST成功通常返回201 Created而非200 OK。

DELETE与超时控制

DELETE请求最简:
  1. Future<void> _deletePost(Post post) async {
  2.   final resp = await http.delete(
  3.     Uri.parse('https://jsonplaceholder.typicode.com/posts/${post.id}'),
  4.   );
  5.   if (resp.statusCode == 200) {
  6.     // 从列表移除
  7.   }
  8. }
复制代码
超时控制使用Dart的timeout扩展方法:
  1. final resp = await http
  2.     .get(Uri.parse('https://jsonplaceholder.typicode.com/posts'))
  3.     .timeout(const Duration(seconds: 10));
复制代码
超时会抛出TimeoutException,建议在catch中统一处理。

错误友好化

直接显示系统错误信息体验差,可根据异常字符串匹配友好提示:
  1. String _friendlyError(Object e) {
  2.   final msg = e.toString();
  3.   if (msg.contains('SocketException') || msg.contains('Failed host')) {
  4.     return '网络连接失败,请检查网络';
  5.   }
  6.   if (msg.contains('TimeoutException')) {
  7.     return '请求超时,请稍后重试';
  8.   }
  9.   if (msg.contains('HandshakeException')) {
  10.     return 'SSL握手失败';
  11.   }
  12.   return msg;
  13. }
复制代码

下拉刷新与ApiClient封装

使用RefreshIndicator包裹可滚动组件:
  1. RefreshIndicator(
  2.   onRefresh: _fetchPosts,
  3.   child: _buildList(),
  4. )
复制代码
注意child必须为ListView等可滚动组件,Column无法触发下拉手势。

项目规模扩大后建议封装ApiClient,统一管理baseUrl、headers和异常:
  1. class ApiClient {
  2.   static const _baseUrl = 'https://jsonplaceholder.typicode.com';
  3.   static Future<List<Post>> getPosts() async {
  4.     final resp = await http.get(Uri.parse('$_baseUrl/posts?_limit=20'));
  5.     if (resp.statusCode == 200) {
  6.       final list = jsonDecode(resp.body) as List;
  7.       return list.map((e) => Post.fromJson(e)).toList();
  8.     }
  9.     throw ApiException(resp.statusCode, resp.body);
  10.   }
  11. }
  12. class ApiException implements Exception {
  13.   final int statusCode;
  14.   final String body;
  15.   ApiException(this.statusCode, this.body);
  16. }
复制代码

与ArkTS网络请求对比

ArkTS的@ohos.net.http使用Promise链式调用,对比Flutter的async/await风格,后者代码更接近同步写法。但ArkTS原生支持自定义CA证书和配置校验,企业内网场景更方便;Flutter需通过HttpClient手动设置SecurityContext,稍复杂。

鸿蒙专属踩坑集锦


  • 忘记设置Content-Type:POST请求若不设置`'Content-Type': 'application/json'`,服务端返回400 Bad Request。
  • 状态码判断错误:创建资源成功应判断201 Created而非200 OK。
  • jsonDecode返回类型:返回dynamic,若直接强转Map可能失败(当JSON顶层为数组时)。应先检查类型:
    1. final data = jsonDecode(resp.body);
    2. if (data is List) {
    3.   // 处理数组
    4. } else if (data is Map) {
    5.   // 处理对象
    6. }
    复制代码
  • mounted检查遗漏:异步回调中调用setState前需判断 mounted:
    1. if (!mounted) return;
    2. setState(() { ... });
    复制代码
  • 网络权限缺失:鸿蒙应用必须在module.json5中声明ohos.permission.INTERNET权限,否则请求被拦截,报SocketException。配置如下:
    1. {
    2.   "name": "ohos.permission.INTERNET",
    3.   "reason": "$string:reason_internet",
    4.   "usedScene": {
    5.     "abilities": ["EntryAbility"],
    6.     "when": "always"
    7.   }
    8. }
    复制代码
    调试模式下可能侥幸通过,但正式包会暴露问题,建议开发初期即配置。
  • 编码问题:http包默认UTF-8解码,若服务端返回GBK编码(较少见)会出现乱码,需手动指定编码。绝大多数API无此问题。


结语

http包的get/post/put/delete四个方法覆盖了90%的网络请求场景,配合状态枚举和友好错误处理即可构建稳定用法。当项目复杂度提升时,可在http基础上封装ApiClient,统一处理baseUrl、token注入和异常转换。更高级的需求(拦截器、文件上传、请求取消)可迁移至dio。最后再次强调:鸿蒙开发中务必先配置INTERNET权限,否则卡在连接错误上浪费大量调试时间。
回复

使用道具 举报

发表于 昨天 11:10 | 显示全部楼层

Re: 鸿蒙Flutter http包网络请求踩坑实录:权限、编码与状态

感谢楼主的详细分享,非常实用的踩坑记录。我最近也在鸿蒙Flutter上做网络请求,GET和POST基本没问题,但碰到DELETE返回200有时body为空,处理时容易忽略。另外,我用的是http包的1.2.2版本,在鸿蒙模拟器上偶尔会遇到SocketException,加了timeout后稳定多了。想问下楼主在权限配置上有没有额外需要设置的?比如网络权限是默认就有还是要在配置里手动声明?
回复 支持 反对

使用道具 举报

发表于 昨天 11:10 | 显示全部楼层

Re: 鸿蒙Flutter http包网络请求踩坑实录:权限、编码与状态

感谢分享,很详细的实战总结!有几个地方想请教一下: - 在鸿蒙上使用 `http` 包时,是否需要手动处理网络权限(比如在 `module.json5` 里配置 `ohos.permission.INTERNET`)?帖子里说“无需额外配置即可运行”,但我印象中鸿蒙应用默认可能没有网络权限,是不是要先在配置里加上? - 对于状态枚举里的 `idle` 状态,在UI中直接和 `success` 一起复用 `_buildEmpty()` 或 `_buildList()`,但 `idle` 时通常列表还没请求吧?这里会不会出现 `_posts` 为空时意外显示空视图的情况?还是说初始状态 `idle` 时直接返回一个空容器更合适? - 超时处理那块,如果 `timeout` 抛出异常,建议在 `catch` 中统一处理,但帖子里没给示例代码,能补充一下 `catch` 里区分网络异常和超时异常的写法吗? 另外,个人觉得对于 `Content-Type` 头,有时后端不要求显式设置也能自动识别(比如 `jsonEncode` 默认就是JSON),但写成 `headers` 明确指定更安全,这点赞同。
回复 支持 反对

使用道具 举报

发表于 昨天 11:10 | 显示全部楼层

Re: 鸿蒙Flutter http包网络请求踩坑实录:权限、编码与状态

写得非常详细,尤其是状态管理的枚举用法和超时控制的例子,很实用。之前在鸿蒙上做Flutter开发时也踩过jsonDecode的类型转换坑,看到你把Model层单独拆出来挺有启发。另外POST成功返回201这点容易忽略,感谢提醒。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-7-26 14:42 , Processed in 0.027180 second(s), 17 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部