在移动应用开发中,网络请求是与后端服务交互的核心方式。Flutter作为跨平台开发框架,提供了多种网络请求解决方案。本文将全面剖析Flutter中的两种主流网络请求方式:官方http包和功能强大的Dio库,从基础使用到高级技巧,助你掌握Flutter网络编程的精髓。
一、Flutter网络请求概述
1.1 为什么需要专门的网络请求库
在原生开发中,Android使用OkHttp或HttpURLConnection,iOS使用URLSession进行网络请求。Flutter作为跨平台框架,需要统一的网络请求解决方案。Dart语言虽然提供了dart:io库中的HttpClient,但直接使用较为底层,开发效率不高。因此,社区推出了更高级的封装库。
1.2 http与Dio库对比
特性 | http包 | Dio库 |
---|---|---|
开发者 | Flutter团队 | 社区维护 |
功能复杂度 | 简单 | 丰富 |
拦截器支持 | 不支持 | 支持 |
文件上传/下载 | 需要手动实现 | 内置支持 |
请求取消 | 不支持 | 支持 |
转换器 | 不支持 | 支持 |
全局配置 | 有限 | 全面 |
学习曲线 | 低 | 中 |
二、官方http包详解
2.1 安装与基本配置
在pubspec.yaml中添加依赖:
dependencies:
http: ^1.1.0
运行flutter pub get
安装包。
2.2 核心API解析
http包提供了简洁的API:
import 'package:http/http.dart' as http;
// GET请求
Future<http.Response> get(Uri url, {Map<String, String>? headers});
// POST请求
Future<http.Response> post(Uri url,
{Map<String, String>? headers, Object? body, Encoding? encoding});
// PUT、DELETE等类似
2.3 完整请求示例
import 'package:http/http.dart' as http;
import 'dart:convert';
class HttpService {
static const String baseUrl = 'https://jsonplaceholder.typicode.com';
// 获取数据
Future<List<dynamic>> fetchPosts() async {
final response = await http.get(Uri.parse('$baseUrl/posts'));
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw Exception('Failed to load posts');
}
}
// 创建数据
Future<Map<String, dynamic>> createPost(Map<String, dynamic> post) async {
final response = await http.post(
Uri.parse('$baseUrl/posts'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode(post),
);
if (response.statusCode == 201) {
return jsonDecode(response.body);
} else {
throw Exception('Failed to create post');
}
}
// 错误处理增强版
Future<dynamic> safeRequest(Future<http.Response> request) async {
try {
final response = await request;
if (response.statusCode >= 200 && response.statusCode < 300) {
return jsonDecode(response.body);
} else {
throw HttpException(
'Request failed with status: ${response.statusCode}',
uri: response.request?.url,
);
}
} on SocketException {
throw const SocketException('No Internet connection');
} on FormatException {
throw const FormatException('Bad response format');
}
}
}
2.4 最佳实践
封装请求层:将网络请求逻辑集中管理
统一错误处理:避免在每个请求中重复处理错误
使用async/await:使异步代码更易读
JSON序列化:考虑使用json_serializable自动生成模型类
三、Dio库深度探索
3.1 Dio的优势特性
拦截器系统:全局处理请求和响应
FormData支持:简化文件上传
请求取消:通过CancelToken实现
超时配置:全局和单独请求级别
下载进度:内置进度回调
适配器系统:可自定义底层实现
3.2 高级配置示例
import 'package:dio/dio.dart';
class DioClient {
final Dio _dio = Dio();
DioClient() {
// 全局配置
_dio.options = BaseOptions(
baseUrl: 'https://jsonplaceholder.typicode.com',
connectTimeout: const Duration(seconds: 5),
receiveTimeout: const Duration(seconds: 3),
responseType: ResponseType.json,
);
// 拦截器
_dio.interceptors.add(InterceptorsWrapper(
onRequest: (options, handler) {
// 添加认证token
options.headers['Authorization'] = 'Bearer token';
return handler.next(options);
},
onError: (error, handler) async {
// 401自动刷新token
if (error.response?.statusCode == 401) {
try {
final newToken = await refreshToken();
error.requestOptions.headers['Authorization'] = 'Bearer $newToken';
final response = await _dio.fetch(error.requestOptions);
return handler.resolve(response);
} catch (e) {
return handler.reject(error);
}
}
return handler.next(error);
},
));
// 日志拦截器
_dio.interceptors.add(LogInterceptor(
request: true,
requestHeader: true,
requestBody: true,
responseHeader: true,
responseBody: true,
));
}
Future<String> refreshToken() async {
// 实现token刷新逻辑
return 'new_token';
}
// 封装GET请求
Future<Response> get(String path, {Map<String, dynamic>? params}) async {
try {
return await _dio.get(path, queryParameters: params);
} on DioException catch (e) {
throw _handleError(e);
}
}
// 文件上传
Future<Response> uploadFile(String path, String filePath) async {
FormData formData = FormData.fromMap({
'file': await MultipartFile.fromFile(filePath),
});
return await _dio.post(path, data: formData);
}
// 错误处理
dynamic _handleError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
throw 'Connection timeout';
case DioExceptionType.receiveTimeout:
throw 'Receive timeout';
// 其他错误类型处理...
default:
throw 'Network error';
}
}
}
3.3 Dio高级特性实战
3.3.1 文件分块上传
Future<void> uploadLargeFile(String filePath) async {
final cancelToken = CancelToken();
final file = File(filePath);
final fileSize = await file.length();
const chunkSize = 1024 * 1024; // 1MB
try {
for (var offset = 0; offset < fileSize; offset += chunkSize) {
final chunk = await file.openRead(offset, offset + chunkSize);
final formData = FormData.fromMap({
'file': MultipartFile(
chunk,
chunkSize,
filename: 'large_file.bin',
contentType: MediaType('application', 'octet-stream'),
),
'offset': offset,
});
await _dio.post(
'/upload',
data: formData,
cancelToken: cancelToken,
onSendProgress: (sent, total) {
print('Upload progress: ${(sent / total * 100).toStringAsFixed(1)}%');
},
);
}
print('Upload completed');
} catch (e) {
if (CancelToken.isCancel(e)) {
print('Upload cancelled');
} else {
rethrow;
}
}
}
3.3.2 并发请求管理
Future<List<dynamic>> fetchMultipleResources() async {
final responses = await Future.wait([
_dio.get('/posts'),
_dio.get('/comments'),
_dio.get('/users'),
]);
return responses.map((response) => response.data).toList();
}
四、性能优化与安全
4.1 网络请求优化策略
连接复用:Dio默认使用HttpClient的连接池
数据压缩:添加
Accept-Encoding: gzip
头缓存策略:结合dio_cache_interceptor实现
请求合并:对频繁的小请求进行合并
分页加载:大数据集分批次请求
4.2 安全最佳实践
HTTPS必须:所有请求使用HTTPS
证书锁定:实现CertificatePinning
敏感数据:不在URL中传递敏感参数
Token管理:使用安全存储保存认证token
输入验证:服务端返回数据必须验证
五、测试与调试
5.1 Mock网络请求
// 使用http包的mock
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
void main() {
test('测试获取帖子', () async {
final client = MockClient((request) async {
return http.Response(
jsonEncode([{'id': 1, 'title': 'Test Post'}]),
200,
headers: {'content-type': 'application/json'},
);
});
final service = PostService(client: client);
final posts = await service.fetchPosts();
expect(posts.length, 1);
});
}
5.2 Dio Mock适配器
import 'package:dio/dio.dart';
import 'package:dio/adapter.dart';
void main() {
final dio = Dio();
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate =
(client) => client..findProxy = (uri) => 'DIRECT';
// 或者使用MockAdapter
dio.httpClientAdapter = MockAdapter()
..whenGet('/posts').reply(200, [{'id': 1}]);
}
六、总结与选择建议
经过对http包和Dio库的深度解析,我们可以得出以下结论:
简单项目:如果只是基础REST API调用,官方http包完全够用
复杂应用:需要拦截器、文件操作等高级功能时,Dio是更好的选择
特殊需求:考虑结合两者优势,或在Dio基础上进行二次封装
无论选择哪种方式,良好的架构设计比具体技术选型更重要。建议:
实现统一的网络层抽象
集中处理错误和日志
考虑使用代码生成处理JSON序列化
为网络层编写全面的测试用例
Flutter的网络生态系统仍在不断发展,掌握这些核心技能将帮助你构建更健壮的移动应用。