一:前言 - 什么是反射机制,Flutter为什么禁用反射机制?
在Flutter中它的网络请求和数据解析稍微的比较麻烦一点,因为Flutter不支持反射机制。相信大家都看到这么一条,就是Flutter不支持反射,那首先有一点需要我们明白的。什么是反射?不知道大家看到这个问题的时候,有多少人脑子里面是一下子能闪出反射的概念的,我们首先还是说说,什么是反射机制。
反射机制简单来说就是动态获取类或者对象中的属性,对于任何一个类,我们都能够知道这个类有哪些方法和属性。对于任何一个对象,我们都能够对它的方法和属性进行调用。我们把这种动态获取对象信息和调用对象方法的功能称之为反射机制。要往深了去理解,的确反射机制是一个比较难的概念,这里有一篇写的比较好的 浅谈反射机制 传送门给大家,有兴趣的看这篇应该对反射是可以有一个比较深的认知的。
大概知道之后,再说一点就是其实单纯的Dart语言是支持反射机制的,只不过Flutter把它禁止了而已,那我们得追究一下 为什么Flutter要禁止Dart的反射机制呢?
这个问题其实官网给过我们答案,我们看看官方是怎么说的:
简单的总结一下:由于反射默认会使用所有的代码,就导致在发布应用的时候没法去除掉未使用的代码,没法显著的优化程序的大小,所以Flutter禁用了Dart的反射机制。
二:Flutter的JSON序列化
既然我们在前面说了Flutter不支持反射机制,那它的JSON序列化又是怎样进行的呢?
首先Flutter中基本的JSON序列化是非常简单的,lutter有一个内置dart:convert库,其中包含一个简单的JSON编码器和解码器。但是不管是dart:convert来处理还是我们使用模型来处理,都是需要我们手动进行的,不仅仅效率比较低,出错的概率也会比较大,在序列化的过程中可能因为一些很细小的错误,导致我们花费大量的时间排查其中的问题,这就对开发者是很不友好了,那有没有什么能帮助我们自动进行JSON的序列化处理的呢,答案也是有,下面就是我们Flutter处理JSON序列化的主角:json_serializable
首先要把json_serializable导入到我们项目中的话,还需要一个常规和两个开发依赖项,具体得我们看看pubspec.yaml添加的内容:
# Your other regular dependencies here json_annotation: ^4.4.0 # Your other dev_dependencies here json_serializable: ^6.1.5 build_runner: ^2.1.8
注意: 这几个插件的版本具体的是跟着我自己的Flutter版本变化的,它们之间版本是相互有影响的,我没记错在执行命令生成g.dart文件的时候,版本不对还有错误产生,具体的错误我之前也忘记没有收集,在这就只能大概的提一句,要真的遇上问题的小伙伴,也可以朝着这个方向去解决查找问题。
有了这几个插件之后,我们接着往后面看该怎么处理, 在官网是给我们一个User的model的处理,具体的代码如下,我们可以参考学习下,并且注意下代码里面注释的内容:
import 'package:json_annotation/json_annotation.dart'; // user.g.dart 将在我们运行生成命令后自动生成 part 'user.g.dart'; ///这个标注是告诉生成器,这个类是需要生成Model类的 @JsonSerializable() class User{ User(this.name, this.email); String name; String email; //不同的类使用不同的mixin即可 factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json); Map<String, dynamic> toJson() => _$UserToJson(this); }
注意:还有一个关键词@JsonKey,比如我们的接口返回一个字段A,但是在模型中我们想使用字段B代替接口返回的A,那我们就可以使用@JsonKey关键字。我们举一个很现实的例子,就像你在iOS中,服务端接口给您返回一个id,你项目在使用OC的情况下是没办法在model中直接使用id接收的,因为关键字冲突,所以我们会处理成ID或者别的去接收,大概就是这么个情况,理解就可以了。
/// Tell json_serializable that "registration_date_millis" should be /// mapped to this property. @JsonKey(name: 'id') final int goodsId;
但在具体的开发中我们也需要自己给model中写这些代码吗?是的,但我推荐给大家一个可以帮我们生成model的地方。 【我在这里-为了便利使用json_serializable库】
有几个小地方需要我们注意下,标注出来了,处理完之后你需要的就只是复制了。
这样你复制了内容,创建自己的model.dart文件,里面会有一些引用的错误,你可以不必理会,等我们处理完之后会顺带这修复的,接下来就是运行下面的命令来生成我们的序列化模板,在我们的项目根目录下运行:
flutter packages pub run build_runner build
我们可以在需要时为我们的model生成json序列化代码。 这触发了一次性构建,它通过我们的源文件,挑选相关的并为它们生成必要的序列化代码。虽然这非常方便,但如果我们不需要每次在model类中进行更改时都要手动运行构建命令的话会更好。那我们有办法持续性的生成序列化模板吗,答案是肯定的,接下来我们再运行命令:
flutter packages pub run build_runner watch
这个命令就帮助我们在项目根目录下运行来启动_watcher_,只需启动一次观察器,然后并让它在后台运行,这是安全的。具体的表现就像下面的动图一样的,在我们创建好我们的TestModel.dart文件之后,我们只需要保存,后面的序列化模板(TestModel.g.dart)文件也会随着自己生成,这就是前面命令运行完之后的持续性生成序列化模板的作用。
这样我们持续在创建g.dart文件,我们的序列化准备工作也就完成了,具体的序列化的代码我们在下面网络请求到出局之后一起看。
三:网络请求和JSON序列化
在Flutter的网络请求插件中,不得不提的使我们的Dio,在Pub上好评率很高,并且在GitHub也收获了近万Star。官方文档是这样描述Dio的:Dio是一个强大的DartHttp请求库,支持RestfulAPI、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配器等...可以说是覆盖了所有涉及到的网络请求。 并且是国人开源的,所以我们只需要利用这个插件就足以应付Flutter的各种网络请求需求了。关于这个插件的具体使用我们不在这里赘述,的确网上太多太多的资料供大家查阅。
【Dio Git地址】 前面提了这是国人开源的,大家可以翻阅中文开发文档查阅问题更方便。
这是Git给的一个例子,使用也是很简单,但具体根据自己项目进行封装等的就需要自己去处理。
import 'package:dio/dio.dart'; void getHttp() async { try { var response = await Dio().get('http://www.google.com'); print(response); } catch (e) { print(e); } }
下面是我们自己项目中具体的一些处理,前面说的我们处理好序列化的东西后就可以在请求到数据后直接处理成model了,重点就在
Responded<T> result = Responded<T>.fromJson(data);
static void send<T>(Request req, {SuccessObj<T>? success, SuccessObjList<T>? successList, Failure? failure}) async { try { // 创建dio Dio dio = _createDio(); // dio发起请求 var response = await _convertToDio(dio, req); // 拿到的数据做一个简单的解码 var data = jsonDecode(response.toString()); // 解析成我们需要的数据模型 Responded<T> result = Responded<T>.fromJson(data); if (result.code == 200) { if (result.dataArray != null) { if (successList != null) { successList(result.dataArray); } } else { if (success != null) { if (result.data != null) { success(result.data!); } else { if (failure != null) { failure(Exceptions(result.code!, "没有相关数据!!")); } } } } } else { var exception = result.exception; // ignore: prefer_conditional_assignment if (null == exception) { exception = Exceptions(result.code!, result.message ??= ""); } if (failure != null) { failure(exception); } } } catch (e) { if (failure != null) { if (e is DioError) { failure(Exceptions.create(e)); } } } }
在我们生成的g.dart文件中,重点就是就是我们需要的编码和解析的方法,比如我写的测试demo中:
// GENERATED CODE - DO NOT MODIFY BY HAND part of 'BodyModel.dart'; // ************************************************************************** // JsonSerializableGenerator // ************************************************************************** BodyModel _$BodyModelFromJson(Map<String, dynamic> json) => BodyModel( json['userId'] as int, json['id'] as int, json['title'] as String, json['body'] as String, ); Map<String, dynamic> _$BodyModelToJson(BodyModel instance) => <String, dynamic>{ 'userId': instance.userId, 'id': instance.id, 'title': instance.title, 'body': instance.body, };
至此,关于Flutter网络请求和JSON序列化的东西我们就基本上梳理完了,小伙伴要疑问,可以留言或者私信我,一起学习探索。
编写本文章时候的Flutter版本:
---------------------------------------------------------------
➜ mixedflutter flutter --version
Flutter 2.10.5 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 5464c5bac7 (3 weeks ago) • 2022-04-18 09:55:37 -0700
Engine • revision 57d3bac3dd
Tools • Dart 2.16.2 • DevTools 2.9.2
---------------------------------------------------------------
Demo地址:
iOS项目地址:https://gitee.com/MrRisingSun/iOSMixedFlutter
Flutter项目地址:https://gitee.com/MrRisingSun/mixed-flutter
---------------------------------------------------------------
## 努力做一个合格的程序员。