[Flutter 基础] - 从http到dio的演进之路

330 阅读3分钟

任何开发语言都离不开网络请求,Flutter亦是如此。http库是Flutter官方的一个网络请求库,功能比较基础,在日常开发中直接使用的也相对比较少,毕竟有一个功能强大的Dio库。但作为一个初学者,其实有必要了解一下http库大概是一个什么样子的。


一、安装 http

pubspec.yaml 中添加依赖:

dependencies:
  http: ^1.1.0 # 检查最新版本

运行 flutter pub get


二、基本用法

了解网络请求的同学可能知道,基本的网络请求无非就是了解一下这几种基本的用法:

  1. get请求
  2. post请求
  3. delete请求
  4. put请求
  5. 文件上传

1. 发送 GET 请求

Future<void> fetchData() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
  try {
    final response = await http.get(url);
    if (response.statusCode == 200) {
      print('响应数据: ${response.body}');
    } else {
      print('请求失败: ${response.statusCode}');
    }
  } catch (e) {
    print('网络错误: $e');
  }
}

2. 发送 POST 请求

Future<void> postData() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final headers = {'Content-Type': 'application/json'};
  final body = jsonEncode({'title': 'foo', 'body': 'bar', 'userId': 1});
​
  try {
    final response = await http.post(url, headers: headers, body: body);
    if (response.statusCode == 201) {
      print('创建成功: ${response.body}');
    }
  } catch (e) {
    print('请求失败: $e');
  }
}

3. 发送 put 请求

Future<void> updateData() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
  final headers = {'Content-Type': 'application/json'};
  final body = jsonEncode({'title': 'updated title', 'body': 'updated body'});

  try {
    final response = await http.put(url, headers: headers, body: body);
    if (response.statusCode == 200) {
      print('PUT 响应数据: ${response.body}');
    }
  } catch (e) {
    print('PUT 请求失败: $e');
  }
}

4. 发送delete请求

Future<void> deleteData() async {
  final url = Uri.parse('https://jsonplaceholder.typicode.com/posts/1');
  try {
    final response = await http.delete(url);
    if (response.statusCode == 200) {
      print('DELETE 成功');
    }
  } catch (e) {
    print('DELETE 请求失败: $e');
  }
}

5. 上传文件

上传文件也是一个Post请求,只是传输的数据类型和普通的post不太一样, 需手动处理 multipart/form-data

Future<void> uploadFile() async {
  final url = Uri.parse('https://api.example.com/upload');
  //创建一个request
  final request = http.MultipartRequest('POST', url);
  // 添加文件
  request.files.add(
    await http.MultipartFile.fromPath('file', '/path/to/file.jpg'),
  );
  // 添加字段
  request.fields['key'] = 'value';
  try {
  // 发送一个封装好的request
    final response = await request.send();
    if (response.statusCode == 200) {
      print('上传成功');
    }
  } catch (e) {
    print('上传失败: $e');
  }
}

6、文件下载

其实就是一个get的网络请求,加上文件保存的过程。 通过流式处理保存文件:

Future<void> downloadFile() async {
  final url = Uri.parse('https://example.com/file.zip');
  final response = await http.get(url);
​
  if (response.statusCode == 200) {
    final file = File('/local/path/file.zip');
    await file.writeAsBytes(response.bodyBytes);
    print('文件已保存');
  }
}

三、处理 JSON 数据

使用 dart:convert 库解析和编码 JSON:

1. 解析响应数据

import 'dart:convert';
//解析数据,需要先转化为json类型
void parseJson(String responseBody) {
  final jsonData = jsonDecode(responseBody);
  print('标题: ${jsonData['title']}');
}

2. 自定义模型类

class Post {
  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) {
    return Post(
      userId: json['userId'],
      id: json['id'],
      title: json['title'],
      body: json['body'],
    );
  }
}
​
// 使用示例
final post = Post.fromJson(jsonDecode(response.body));

四、高级用法

1. 设置请求头

final headers = {
  'Authorization': 'Bearer your_token',
  'Content-Type': 'application/json',
};
​
final response = await http.get(url, headers: headers);

2. 处理查询参数

使用 Uri 构建带查询参数的 URL:

final url = Uri.parse('https://api.example.com/data').replace(
  queryParameters: {'page': '1', 'limit': '10'},
);

3. 超时设置

通过 Future.timeout 实现:

try {
  final response = await http.get(url).timeout(Duration(seconds: 5));
} on TimeoutException {
  print('请求超时');
}

五、错误处理

捕获常见错误类型:

try {
  final response = await http.get(url);
} on http.ClientException catch (e) {
  print('客户端错误: $e'); // 如网络不可用
} on SocketException catch (e) {
  print('网络连接失败: $e');
} catch (e) {
  print('未知错误: $e');
}

六、Dio三方库

0. 安装依赖

dependencies:
  dio: ^5.0.0 # 检查最新版本

1. GET 请求

import 'package:dio/dio.dart';
​
Future<void> fetchData() async {
  final dio = Dio();
  try {
    final response = await dio.get('https://jsonplaceholder.typicode.com/posts/1');
    print('GET 响应数据: ${response.data}');
  } on DioException catch (e) {
    print('GET 请求失败: ${e.message}');
  }
}

2. POST 请求

Future<void> postData() async {
  final dio = Dio();
  final data = {'title': 'foo', 'body': 'bar', 'userId': 1};
  try {
    final response = await dio.post(
      'https://jsonplaceholder.typicode.com/posts',
      data: data,
    );
    print('POST 响应数据: ${response.data}');
  } on DioException catch (e) {
    print('POST 请求失败: ${e.message}');
  }
}

3. PUT 请求

Future<void> updateData() async {
  final dio = Dio();
  final data = {'title': 'updated title', 'body': 'updated body'};
  try {
    final response = await dio.put(
      'https://jsonplaceholder.typicode.com/posts/1',
      data: data,
    );
    print('PUT 响应数据: ${response.data}');
  } on DioException catch (e) {
    print('PUT 请求失败: ${e.message}');
  }
}

4. DELETE 请求

Future<void> deleteData() async {
  final dio = Dio();
  try {
    final response = await dio.delete('https://jsonplaceholder.typicode.com/posts/1');
    print('DELETE 成功: ${response.statusCode}');
  } on DioException catch (e) {
    print('DELETE 请求失败: ${e.message}');
  }
}

5. 文件上传

Future<void> uploadFile() async {
  final dio = Dio();
  final formData = FormData.fromMap({
    'file': await MultipartFile.fromFile('/path/to/file.jpg', filename: 'upload.jpg'),
    'key': 'value',
  });
​
  try {
    final response = await dio.post(
      'https://api.example.com/upload',
      data: formData,
      onSendProgress: (sent, total) {
        print('上传进度: ${(sent / total * 100).toStringAsFixed(0)}%');
      },
    );
    print('文件上传成功: ${response.data}');
  } on DioException catch (e) {
    print('文件上传失败: ${e.message}');
  }
}

6. Json 自动解析数据

自定义数据类型解析

Response response = await dio.get(
  '/path',
  options: Options(responseType: ResponseType.plain),
);

7. 拦截器

dio.interceptors.add(
  InterceptorsWrapper(
    onRequest: (RequestOptions options, RequestInterceptorHandler handler) {
      // 添加统一请求头(如 Token)
      options.headers['Authorization'] = 'Bearer token';
      return handler.next(options);
    },
    onResponse: (Response response, ResponseInterceptorHandler handler) {
      // 预处理响应数据
      return handler.next(response);
    },
    onError: (DioException error, ErrorInterceptorHandler handler) {
      // 统一错误处理(如 Token 过期跳转登录)
      if (error.response?.statusCode == 401) {
        // 处理逻辑
      }
      return handler.next(error);
    },
  ),
);

日志拦截器

dio.interceptors.add(LogInterceptor(
  request: true,
  responseBody: true,
));

8. 配置超时

final dio = Dio(
  BaseOptions(
    baseUrl: 'https://api.example.com',
    connectTimeout: Duration(seconds: 5),
    receiveTimeout: Duration(seconds: 3),
    headers: {'Content-Type': 'application/json'},
  ),
);

9. 取消请求

final cancelToken = CancelToken();

// 发送请求时传递 cancelToken
dio.get('/path', cancelToken: cancelToken);

// 取消请求
cancelToken.cancel('用户手动取消');

六、http 与 Dio 的对比

特性http 包Dio
依赖体积轻量(无额外依赖)较大(支持拦截器等高级功能)
JSON 自动解析需手动使用 jsonDecode支持自动解析
拦截器不支持支持请求/响应拦截
文件上传/下载需手动处理 MultipartRequest封装了 FormDatadownload 方法
超时配置需通过 Future.timeout 实现内置全局超时配置
取消请求不支持支持 CancelToken

总结

  • 适用场景

    • 使用 http 包:适合简单请求、小型项目,或希望减少第三方依赖。
    • 使用 Dio:需要拦截器、文件上传/下载、全局配置等高级功能时。
  • 核心优势

    • http 包:官方维护,代码简洁,学习成本低。
    • Dio:功能丰富,适合复杂网络需求。

官方文档: