End

Flutter 陈航 24-网络编程 http dio JSON 反射

本文地址


目录

24 | HTTP网络编程与JSON解析

Flutter 中,Http 网络编程的实现方式主要分为三种:

  • HttpClient
  • Dart 原生 http 请求库
  • 第三方库 dio

HttpClient

HttpClient 是 dart:io 库中提供的网络请求类,实现了基本的网络编程功能。

testGetUrl() async {
  HttpClient httpClient = HttpClient();
  httpClient.idleTimeout = const Duration(seconds: 5);

  Uri uri = Uri.parse("https://storage.flutter-io.cn");
  HttpClientRequest request = await httpClient.getUrl(uri);
  request.headers.add("user-agent", "Custom-UA");

  // 发起请求,等待响应
  HttpClientResponse response = await request.close();

  // 收到响应,打印结果
  if (response.statusCode == HttpStatus.ok) {
    var text = await response.transform(utf8.decoder).join();
    flog(text);
  } else {
    flog('Error: statusCode = ${response.statusCode}');
  }
}

http

http 是 Dart 官方提供的另一个网络请求类,相比于 HttpClient 更加简单。

dependencies:
  http: ^0.13.5
import 'package:http/http.dart' as http;

testGetUrl() async {
  http.Response response = await http.Client().get(
    Uri.parse("https://storage.flutter-io.cn"),
    headers: {"user-agent": "Custom-UA"},
  );

  if (response.statusCode == HttpStatus.ok) {
    flog(response.body);
  } else {
    flog("Error: ${response.statusCode}");
  }
}

dio

HttpClient 和 http 暴露的定制化能力都相对较弱,很多常用的功能都不支持,比如取消请求、定制拦截器、Cookie 管理等。因此对于复杂的网络请求行为,推荐使用目前在 Dart 社区人气较高的 dio 来发起网络请求。

dependencies:
  dio: ^5.0.1
testGetUrl() async {
  var response = await Dio().get(
    "https://storage.flutter-io.cn",
    options: Options(headers: {"user-agent": "Custom-UA"}),
  );

  if (response.statusCode == HttpStatus.ok) {
    flog(response.data.toString());
  } else {
    flog("Error: ${response.statusCode}");
  }
}

dio.get 方法的 options 参数提供了精细化控制网络请求的能力,可以支持设置 Header、超时时间、Cookie、请求方法等。

  • 文件下载:可以使用 download 方法实现
  • 文件上传:可以通过构建表单 FormData 实现
  • 并行请求:可以结合 Future.wait 方法实现
  • 拦截器:提供了与 okHttp 一样的拦截器,可以在请求前或响应后做一些特殊的操作

JSON 解析

不支持运行时反射

在 Flutter 中,没有像原生开发那样提供了 Gson 或 Mantle 等库,用于将 JSON 字符串直接转换为对应的实体类。而这些能力无一例外都需要用到运行时反射,这是 Flutter 从设计之初就不支持的,理由如下:

  • 运行时反射破坏了类的封装性和安全性,会带来安全风险
  • 运行时反射会增加二进制文件大小。使用反射后,因为不清楚哪些代码可能会在运行时用到,因此会默认使用所有代码构建应用程序,这就导致编译器无法优化编译期间未使用的代码,应用安装包体积无法进一步压缩。

解析案例

String jsonString = '''
{
  "name":"张三",
  "score" : 95,
  "teacher": {
    "name": "李四",
    "age" : 40
  }
}
''';
  • 首先根据 JSON 结构定义 Student 类、Teacher 类
  • 并创建对应的解析工厂类,来处理属性与字典之间的映射关系
class Student {
  String name;
  int score;
  Teacher teacher;

  Student({required this.name, required this.score, required this.teacher});

  // JSON 解析工厂类,使用字典数据为对象初始化赋值
  factory Student.fromJson(Map<String, dynamic> parsedJson) {
    return Student(
      name: parsedJson['name'],
      score: parsedJson['score'],
      teacher: Teacher.fromJson(parsedJson['teacher']),
    );
  }
}
class Teacher {
  String name;
  int age;

  Teacher({required this.name, required this.age});

  factory Teacher.fromJson(Map<String, dynamic> parsedJson) {
    return Teacher(name: parsedJson['name'], age: parsedJson['age']);
  }
}
  • 把 JSON 字符串通过 json.decode 方法转换成 Map
  • 然后把它交给工厂类的 fromJson 方法,即可完成 Student 对象的解析
loadStudent() {
  final jsonResponse = json.decode(jsonString);
  Student student = Student.fromJson(jsonResponse);
  flog("${student.name} - ${student.score} - ${student.teacher.name} - ${student.teacher.age}");
}

对于这类 CPU 密集型的操作,可以使用 compute 函数,将解析工作放到新的 Isolate 中完成:

Student parseStudent(String content) => Student.fromJson(json.decode(content));

loadStudent() {
  // 用 compute 函数将 json 解析放到新 Isolate
  compute(parseStudent, jsonString).then((student) => flog("${student.name} - ${student.teacher.name}"));
}

2023-3-8

posted @ 2023-03-08 01:11  白乾涛  阅读(211)  评论(0编辑  收藏  举报