当前位置: 首页 > news >正文

《Flutter全栈开发实战指南:从零到高级》- 14 -网络请求与数据解析

网络请求与数据解析

在移动开发中需要与云端服务器进行频繁的数据交互,本节内容讲带你详细了解网络请求与数据解析,让你的应用真正地“活”起来。

一、 为什么网络层如此重要?

举个例子:你正在开发一个新闻App,那些滚动的时事新闻、视频等内容,不可能全部打包在App安装包里,它们需要从服务器实时获取。这个“获取”的过程,就是通过网络请求完成的。

简单来说,流程就是:App 问服务器要数据 -> 服务器返回数据 -> App 把数据展示出来

在Flutter中,常用的两个网络请求库:官方推荐的 http社区维护得 dio。我们将从两者入手,带你彻底玩转网络请求。

二、 http库dio库 如何选择?

选择哪个库,就像选择工具,没有绝对的好坏,只有合不合适。

1. http

http 库是Flutter团队维护的底层库,它:

  • 优点:官方维护,稳定可靠;API简单直接,学习成本低。
  • 缺点:功能相对基础,许多高级功能(如拦截器、文件上传/下载进度等)需要自己手动实现。

核心方法:

  • get(): 向指定URL发起GET请求,用于获取数据。
  • post(): 发起POST请求,用于提交数据。
  • put(), delete(), head() 等:对应其他HTTP方法。

2. dio

dio 是一个强大的第三方HTTP客户端,它:

  • 优点:支持拦截器全局配置请求取消FormData文件上传/下载超时设置等。
  • 缺点:相对于http库更重一些。

如何选择?

  • 新手入门:可以从 http 开始,上手快。
  • 中大型项目:强烈推荐 dio,它能帮你节省大量造轮子的时间。

本节内容主要以 dio 为例进行讲解,它更符合项目开发的实际情况。

三、 引入依赖

首先,在你的 pubspec.yaml 文件中声明依赖。

dependencies:flutter:sdk: flutterdio: ^5.0.0 # 用于JSON序列化json_annotation: ^4.8.0dev_dependencies:flutter_test:sdk: flutterbuild_runner: ^2.3.0json_serializable: ^6.5.0

执行 flutter pub get 安装依赖。

四、 http

虽然我们推荐使用dio库,但了解http库的基本用法仍是必要的。

以获取一篇博客文章信息为例

import 'package:http/http.dart' as http; 
import 'dart:convert'; class HttpExample {static Future<void> fetchPost() async {try {// 1. 发起GET请求final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),);// 2. 状态码200表示成功if (response.statusCode == 200) {// 3. 使用 dart:convert 解析返回的JSON字符串Map<String, dynamic> jsonData = json.decode(response.body);// 4. 从解析后的Map中取出数据String title = jsonData['title'];String body = jsonData['body'];print('标题: $title');print('内容: $body');} else {// 请求失败print('请求失败,状态码: ${response.statusCode}');print('响应体: ${response.body}');}} catch (e) {// 捕获异常print('请求发生异常: $e');}}
}

代码解读:

  1. async/await:网络请求是耗时操作,必须使用异步。await 会等待请求完成,而不会阻塞UI线程。
  2. Uri.parse:将字符串URL转换为Uri对象。
  3. response.statusCode:响应状态码,200系列表示成功
  4. json.decode():反序列化将JSON串转换为Dart中的 Map<String, dynamic>List

五、 dio

下面我们重点讲解下dio库:

1. Dio-发起请求

我们先创建一个Dio实例并进行全局配置。

import 'package:dio/dio.dart';class DioManager {// 单例static final DioManager _instance = DioManager._internal();factory DioManager() => _instance;DioManager._internal() {_dio = Dio(BaseOptions(baseUrl: 'https://jsonplaceholder.typicode.com', connectTimeout: const Duration(seconds: 5), // 连接超时时间receiveTimeout: const Duration(seconds: 3), // 接收数据超时时间headers: {'Content-Type': 'application/json', },));}late final Dio _dio;Dio get dio => _dio;
}// GET请求
void fetchPostWithDio() async {try {// baseUrl后面拼接路径Response response = await DioManager().dio.get('/posts/1');// dio会自动检查状态码,非200系列会抛异常,所以这里直接处理数据Map<String, dynamic> data = response.data; // 这里dio帮我们自动解析了JSONprint('获取数据: ${data['title']}');} on DioException catch (e) {print('请求异常: $e');if (e.response != null) {// 错误状态码print('错误状态码: ${e.response?.statusCode}');print('错误信息: ${e.response?.data}');} else {// 抛异常print('异常: ${e.message}');}} catch (e) {// 未知异常print('未知异常: $e');}
}

Dio相比Http的优点:

  • 自动JSON解析response.data 直接就是Map或List,无需手动 json.decode,太方便了!
  • 配置清晰BaseOptions 全局配置一目了然。
  • 结构化异常DioException 包含了丰富的错误信息。

2. Dio-网络请求流程

为了让大家更直观地理解,我们用一个流程图来展示Dio处理请求的完整过程:

客户端拦截器Dio服务端发起请求await dio.get('/users')请求拦截器添加Token、日志等处理后的请求发送网络请求返回响应响应拦截器解析JSON、错误处理处理后的响应返回最终结果客户端拦截器Dio服务端

3. Dio-拦截器

拦截器允许我们在请求发送前和响应返回后,插入自定义逻辑,对所有经过的请求和响应进行检查和加工。

案例:自动添加认证Token

class AuthInterceptor extends Interceptor {void onRequest(RequestOptions options, RequestInterceptorHandler handler) {// 请求发送前,为每个请求的Header加上Tokenconst String token = 'your_auth_token_here';if (token.isNotEmpty) {options.headers['Authorization'] = 'Bearer $token';}handler.next(options);}void onResponse(Response response, ResponseInterceptorHandler handler) {// 响应成功处理print('请求成功: ${response.requestOptions.path}');handler.next(response);}void onError(DioException err, ErrorInterceptorHandler handler) {// 失败处理// 当Token过期时,自动跳转到登录页if (err.response?.statusCode == 401) {print('Token已过期,请重新登录!');// 这里可以跳转到登录页面// NavigationService.instance.navigateTo('/login');}handler.next(err);}
}// 将拦截器添加到Dio实例中
void main() {final dio = DioManager().dio;dio.interceptors.add(AuthInterceptor());// 这里可以添加其他拦截器dio.interceptors.add(LogInterceptor(responseBody: true)); 
}

拦截器的添加顺序就是它们的执行顺序。onRequest 正序执行,onResponseonError 倒序执行。

六、 JSON序列化与反序列化

这是新手最容易踩坑的地方。直接从JSON转换成Dart对象(Model),能让我们的代码更安全、更易维护。

1. 为什么要序列化?

  • 类型安全:直接操作Map,编译器不知道data[‘title‘]是String还是int,容易写错;
  • 代码效率:使用点语法post.title访问属性,比post[‘title‘]更高效且有代码提示;
  • 可维护性:当接口字段变更时,你只需要修改一个Model类,而不是分散在各处的字符串key;;

2. 使用 json_serializable自动序列化

通过代码生成的方式,自动创建 fromJsontoJson 方法,一劳永逸。

步骤1:创建Model类并使用注解

// post.dart
import 'package:json_annotation/json_annotation.dart';// 运行 `flutter pub run build_runner build` 后,会生成 post.g.dart 文件
part 'post.g.dart';// 这个注解告诉生成器这个类需要生成序列化代码
()
class Post {// 使用@JsonKey可以自定义序列化行为// 例如,如果JSON字段名是`user_id`,而Dart字段是`userId`,可以这样映射:// @JsonKey(name: 'user_id')final int userId;final int id;final String title;final String body;Post({required this.userId,required this.id,required this.title,required this.body,});// 生成的代码会提供这两个方法factory Post.fromJson(Map<String, dynamic> json) => _$PostFromJson(json);Map<String, dynamic> toJson() => _$PostToJson(this);
}

步骤2:运行代码生成命令

在项目根目录下执行:

flutter pub run build_runner build

这个命令会扫描所有带有 @JsonSerializable() 注解的类,并生成对应的 .g.dart 文件(如 post.g.dart)。这个文件里包含了 _$PostFromJson_$PostToJson 的具体实现。

步骤3:自动生成

// 具体的请求方法中使用
void fetchPostModel() async {try {Response response = await DioManager().dio.get('/posts/1');// 将响应数据转换为Post对象Post post = Post.fromJson(response.data);print('文章标题: ${post.title}');} on DioException catch (e) {// ... 错误处理}
}

json_serializable 的优势:

  • 自动处理类型转换,避免手误;
  • 通过 @JsonKey 注解可以处理各种复杂场景;

七、 网络层在MVVM模式中的定位

实际项目中,我们不会直接在UI页面里写网络请求代码。让我们看看网络层在MVVM架构中是如何工作的:

调用
调用
使用
返回JSON
转换为Model
更新状态
View
视图层
ViewModel
视图模型
Model
模型层
Dio
网络层

各个分层职责:

  • View:只关心数据的展示和用户交互;
  • ViewModel:持有业务状态,处理UI逻辑,不关心数据从哪里来;
  • Model:决定数据是从网络获取还是本地数据库读取,它调用网络层;
  • Dio:纯粹的网络请求执行者,负责API调用、错误初步处理等;

这样分层的好处是:最终目的是解耦,各司其职,修改网络层不会影响业务逻辑,代码结构清晰,同事方便单元测试。

八、 错误处理

一个好的应用,必须支持处理各种异常情况。

1. DioException

DioException 的类型 (type) 帮助我们准确判断错误根源。

void handleDioError(DioException e) {switch (e.type) {case DioExceptionType.connectionTimeout:case DioExceptionType.sendTimeout:case DioExceptionType.receiveTimeout:print('超时错误,请检查网络连接是否稳定。');break;case DioExceptionType.badCertificate:print('证书错误。');break;case DioExceptionType.badResponse:// 服务器返回了错误状态码print('服务器错误: ${e.response?.statusCode}');// 可以根据不同状态码做不同处理if (e.response?.statusCode == 404) {print('请求的资源不存在(404)');} else if (e.response?.statusCode == 500) {print('服务器内部错误(500)');} else if (e.response?.statusCode == 401) {print('未授权,请重新登录(401)');}break;case DioExceptionType.cancel:print('请求被取消。');break;case DioExceptionType.connectionError:print('网络连接错误,请检查网络是否开启。');break;case DioExceptionType.unknown:print('未知错误: ${e.message}');break;}
}

2. 重试机制

对于因网络波动导致的失败,自动重试能大幅提升用户体验。

class RetryInterceptor extends Interceptor {final Dio _dio;RetryInterceptor(this._dio);Future<void> onError(DioException err, ErrorInterceptorHandler handler) async {// 只对超时和网络连接错误进行重试if (err.type == DioExceptionType.connectionTimeout ||err.type == DioExceptionType.receiveTimeout ||err.type == DioExceptionType.connectionError) {final int retryCount = err.requestOptions.extra['retry_count'] ?? 0;const int maxRetryCount = 3;if (retryCount < maxRetryCount) {// 增加重试计数err.requestOptions.extra['retry_count'] = retryCount + 1;print('网络不稳定,正在尝试第${retryCount + 1}次重试...');// 等待一段时间后重试await Future.delayed(Duration(seconds: 1 * (retryCount + 1)));try {// 重新发送请求final Response response = await _dio.fetch(err.requestOptions);// 返回成功responsehandler.resolve(response);return;} catch (retryError) {// 如果失败继续传递错误handler.next(err);return;}}}// 如果不是指定错误或已达最大重试次数,则继续传递错误handler.next(err);}
}

九、 封装一个完整的网络请求库

到这已经把所有的网络请求知识学完了,下面我们用学到的知识封装一个通用的网络请求工具类。

// http_client.dart
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';class HttpClient {static final HttpClient _instance = HttpClient._internal();factory HttpClient() => _instance;late final Dio _dio;HttpClient._internal() {_dio = Dio(BaseOptions(baseUrl: 'https://api.yourserver.com/v1',connectTimeout: const Duration(seconds: 10),receiveTimeout: const Duration(seconds: 10),headers: {'Content-Type': 'application/json'},));// 添加拦截器_dio.interceptors.add(LogInterceptor(requestBody: kDebugMode, responseBody: kDebugMode,));_dio.interceptors.add(AuthInterceptor());_dio.interceptors.add(RetryInterceptor(_dio));}// 封装GET请求Future<Response<T>> get<T>(String path, {Map<String, dynamic>? queryParameters,Map<String, dynamic>? headers,}) async {try {final options = Options(headers: headers);return await _dio.get<T>(path,queryParameters: queryParameters,options: options,);} on DioException {rethrow; }}// 封装POST请求Future<Response<T>> post<T>(String path, {dynamic data,Map<String, dynamic>? queryParameters,Map<String, dynamic>? headers,}) async {try {final options = Options(headers: headers);return await _dio.post<T>(path,data: data,queryParameters: queryParameters,options: options,);} on DioException {rethrow;}}// 获取列表数据Future<List<T>> getList<T>(String path, {Map<String, dynamic>? queryParameters,T Function(Map<String, dynamic>)? fromJson,}) async {final response = await get<List<dynamic>>(path, queryParameters: queryParameters);// 将List<dynamic>转换为List<T>if (fromJson != null) {return response.data!.map<T>((item) => fromJson(item as Map<String, dynamic>)).toList();}return response.data as List<T>;}// 获取单个对象Future<T> getItem<T>(String path, {Map<String, dynamic>? queryParameters,required T Function(Map<String, dynamic>) fromJson,}) async {final response = await get<Map<String, dynamic>>(path, queryParameters: queryParameters);return fromJson(response.data!);}
}// 
class PostRepository {final HttpClient _client = HttpClient();Future<Post> getPost(int id) async {final response = await _client.getItem('/posts/$id',fromJson: Post.fromJson, );return response;}Future<List<Post>> getPosts() async {final response = await _client.getList('/posts',fromJson: Post.fromJson,);return response;}Future<Post> createPost(Post post) async {// Model转JSONfinal response = await _client.post('/posts',data: post.toJson(),);return Post.fromJson(response.data);}
}

总结

又到了写总结诶的时候了,让我们用一张表格来回顾所有知识点:

知识点核心用途
库选择http 轻量,dio 强大中大型项目首选 dio
异步编程使用 async/await 处理耗时操作不能阻塞UI线程
JSON序列化自动生成推荐 json_serializable
错误处理区分网络异常和服务器错误精确捕获 DioException 并分类处理
拦截器统一处理请求/响应用于添加Token、日志、重试逻辑
架构分层MVVM分离解耦
请求封装统一封装GET/POST等基础方法提供 getItem, getList 等语义化方法

网络请求在实际项目中直观重要,没有网络就没有数据,掌握好本章内容,你就能为你Flutter应用注入源源不断的活力。让我们下期见!

http://www.dtcms.com/a/605747.html

相关文章:

  • 模板网站配置文件seo难不难
  • div2 1052 个人补题笔记
  • 【1.10】基于FPGA的costas环开发4——鉴相器模块开发
  • C语言编译软件 | 如何选择适合自己的编译器
  • 怎么做网站外贸wordpress 本地 域名绑定
  • DSP中断工作原理
  • 【LeetCode】109. 有序链表转换二叉搜索树
  • Verilog 利用伪随机,时序,按键消抖等,实现一个(打地鼠)游戏
  • 【音视频】均衡器(Equalizer)技术详解
  • win11安装mysql社区版数据库
  • 菏泽定制网站建设推广花艺企业网站建设规划
  • 哪些网站可以做推广婚庆公司网站源码
  • LVS负载均衡群集(一) -- NAT模式
  • 【ZeroRnge WebRTC】RFC 8445:ICE 协议规范(中文整理与译注)
  • librtp 实现详解:仓颉语言中的 RTP和RTCP 协议库开发实践
  • Android http网络请求的那些事儿
  • 两台 centos 7.9 部署 pbs version 18.1.4 集群
  • 【动手学深度学习】8.1. 序列模型
  • 【AI软件开发】从文献管理到知识编织:构建AI驱动的学术研究工作流
  • 网站上面图片上传尺寸建设部二级结构工程师注销网站
  • PostIn从初级到进阶(3) - 如何对接口快速设计并管理接口文档
  • 按键精灵安卓/ios脚本开发辅助工具:yolo转换教程
  • 人工智能驱动下的OCR API技术演进与实践应用
  • 昆明网站建设介绍湛江专业雷剧全集
  • 网站到期时间营销型网站服务公司
  • 常用设计模式:工厂方法模式
  • 视频矩阵哪个品牌好?2025 视频矩阵品牌标杆出炉
  • MongoDB 分片
  • 网站访客qq获取苏州建网站公司
  • Vue 3与 Vue 2响应式的区别