鸿蒙专家 发表于 2026-7-25 11:00:01

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

在鸿蒙上使用Flutter开发应用时,网络请求是绕不开的基础能力。相比ArkTS的@ohos.net.http模块(回调风格),Flutter的http包凭借简洁的async/await写法更受开发者青睐。本文以一个完整的Demo为例,记录在鸿蒙Flutter项目中用http包调用REST API的实践过程,重点梳理参数配置、状态管理以及常见踩坑点。


dependencies:
http: ^1.2.2

添加依赖后,无需额外配置即可在鸿蒙设备上运行。如需拦截器、请求取消等高级功能,可后续迁移至dio。

GET请求:拉取列表

最基本的操作是发送GET请求并解析JSON。代码如下:

import 'dart:convert';
import 'package:http/http.dart' as http;

Future<void> _fetchPosts() async {
final resp = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/posts?_limit=20'),
);
if (resp.statusCode == 200) {
    final list = jsonDecode(resp.body) as List;
    final posts = list.map((e) => _Post.fromJson(e)).toList();
}
}

http.get返回Response对象,statusCode为状态码,body为响应体字符串。jsonDecode将JSON字符串转换为Dart的Map或List,再通过fromJson工厂构造函数转为强类型Model。

Model类封装

建议将JSON解析封装为Model类,避免业务代码中散落json['xxx']:

class Post {
final int id;
final String title;
final String body;
Post({required this.id, required this.title, required this.body});
factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id'] as int,
      title: json['title'] as String,
      body: json['body'] as String,
    );
}
}

字段较多时可使用json_serializable自动生成,但小规模手写更直观。

状态管理:loading / success / error

网络请求至少需要三种状态:加载中、成功、失败。使用枚举管理比布尔值更清晰:

enum _LoadState { idle, loading, success, error }
_LoadState _state = _LoadState.idle;
List<_Post> _posts = [];
String _error = '';

UI根据状态渲染不同组件:

Widget _buildBody() {
switch (_state) {
    case _LoadState.loading:
      return const Center(child: CircularProgressIndicator());
    case _LoadState.error:
      return _buildError();
    case _LoadState.success:
    case _LoadState.idle:
      return _posts.isEmpty ? _buildEmpty() : _buildList();
}
}


GET详情与POST请求

点选列表项获取详情:

Future<void> _fetchDetail(int id) async {
final resp = await http.get(
    Uri.parse('https://jsonplaceholder.typicode.com/posts/$id'),
);
if (resp.statusCode == 200) {
    final data = jsonDecode(resp.body);
    // 使用 data['title'], data['body']
}
}

POST创建资源需设置Content-Type头部:

Future<void> _createPost() async {
final resp = await http.post(
    Uri.parse('https://jsonplaceholder.typicode.com/posts'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({
      'title': '鸿蒙Flutter测试',
      'body': '通过http.post创建',
      'userId': 1,
    }),
);
if (resp.statusCode == 201) {
    final data = jsonDecode(resp.body);
    // data['id'] 为新ID
}
}

注意:POST成功通常返回201 Created而非200 OK。

DELETE与超时控制

DELETE请求最简:

Future<void> _deletePost(Post post) async {
final resp = await http.delete(
    Uri.parse('https://jsonplaceholder.typicode.com/posts/${post.id}'),
);
if (resp.statusCode == 200) {
    // 从列表移除
}
}

超时控制使用Dart的timeout扩展方法:

final resp = await http
    .get(Uri.parse('https://jsonplaceholder.typicode.com/posts'))
    .timeout(const Duration(seconds: 10));

超时会抛出TimeoutException,建议在catch中统一处理。

错误友好化

直接显示系统错误信息体验差,可根据异常字符串匹配友好提示:

String _friendlyError(Object e) {
final msg = e.toString();
if (msg.contains('SocketException') || msg.contains('Failed host')) {
    return '网络连接失败,请检查网络';
}
if (msg.contains('TimeoutException')) {
    return '请求超时,请稍后重试';
}
if (msg.contains('HandshakeException')) {
    return 'SSL握手失败';
}
return msg;
}


下拉刷新与ApiClient封装

使用RefreshIndicator包裹可滚动组件:

RefreshIndicator(
onRefresh: _fetchPosts,
child: _buildList(),
)

注意child必须为ListView等可滚动组件,Column无法触发下拉手势。

项目规模扩大后建议封装ApiClient,统一管理baseUrl、headers和异常:

class ApiClient {
static const _baseUrl = 'https://jsonplaceholder.typicode.com';
static Future<List<Post>> getPosts() async {
    final resp = await http.get(Uri.parse('$_baseUrl/posts?_limit=20'));
    if (resp.statusCode == 200) {
      final list = jsonDecode(resp.body) as List;
      return list.map((e) => Post.fromJson(e)).toList();
    }
    throw ApiException(resp.statusCode, resp.body);
}
}
class ApiException implements Exception {
final int statusCode;
final String body;
ApiException(this.statusCode, this.body);
}


与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顶层为数组时)。应先检查类型:

final data = jsonDecode(resp.body);
if (data is List) {
// 处理数组
} else if (data is Map) {
// 处理对象
}

[*]mounted检查遗漏:异步回调中调用setState前需判断 mounted:

if (!mounted) return;
setState(() { ... });

[*]网络权限缺失:鸿蒙应用必须在module.json5中声明ohos.permission.INTERNET权限,否则请求被拦截,报SocketException。配置如下:

{
"name": "ohos.permission.INTERNET",
"reason": "$string:reason_internet",
"usedScene": {
    "abilities": ["EntryAbility"],
    "when": "always"
}
}

调试模式下可能侥幸通过,但正式包会暴露问题,建议开发初期即配置。
[*]编码问题:http包默认UTF-8解码,若服务端返回GBK编码(较少见)会出现乱码,需手动指定编码。绝大多数API无此问题。


结语

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

热心网友2 发表于 2026-7-25 11:10:00

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

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

热心网友2 发表于 2026-7-25 11:10:00

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` 明确指定更安全,这点赞同。

热心网友2 发表于 2026-7-25 11:10:00

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

写得非常详细,尤其是状态管理的枚举用法和超时控制的例子,很实用。之前在鸿蒙上做Flutter开发时也踩过jsonDecode的类型转换坑,看到你把Model层单独拆出来挺有启发。另外POST成功返回201这点容易忽略,感谢提醒。
页: [1]
查看完整版本: 鸿蒙Flutter http包网络请求踩坑实录:权限、编码与状态