本文由玉刚说写作平台提供写作赞助
原作者:水晶虾饺
本文是学习指南系列的第6篇文章,建议大家收藏起来,欢迎分享给他人。本篇文章我们先学习 Flutter IO 相关的基础知识,然后在上次文章的基础上,继续开发一个 echo 客户端。由于日常开发中 HTTP 比 socket 更常见,我们的 echo 客户端将会使用 HTTP 协议跟服务端通信。Echo 服务器也会使用 Dart 来实现。
文件
为了执行文件操作,我们可以使用 Dart 的 io 包:
创建文件
在 Dart 里,我们通过类 File 来执行文件操作:
相对于 CPU,IO 总是很慢的,所以大部分文件操作都返回一个 Future,并在出错的时候抛出一个异常。如果你需要,也可以使用同步版本,这些方法都带一个后缀 Sync:
async 方法使得我们可以像写同步方法一样写异步代码,同步版本的 io 方法已经没有太多使用的必要了(Dart 1 不支持 async 函数,所以同步版本的方法的存在是有必要的)。
写文件
写 String 时我们可以使用 writeAsString 和 writeAsBytes 方法:
如果只是为了写文件,还可以使用 openWrite 打开一个 IOSink:
读文件
读写原始的 bytes 也是相当简单的:
和写文件类似,它还有一个 openRead 方法:
最后需要注意的是,我们读写 bytes 的时候,使用的对象是 List,而一个 int 在 Dart 里面有 64 位。Dart 一开始设计就是用于 Web,这部分的效率也就不那么高了。
JSON
JSON 相关的 API 放在了 convert 包里面:
把对象转换为 JSON
假设我们有这样一个对象:
为了把他转换为 JSON,我们给他定义一个 toJson 方法(注意,不能改变他的方法签名):
接下来我们调用 json.encode 方法把对象转换为 JSON:
把 JSON 转换为对象
首先,我们给 Point 类再加多一个构造函数:
为了解析 JSON 字符串,我们可以用 json.decode 方法:
返回一个 dynamic 的原因在于,Dart 不知道传进去的 JSON 是什么。如果是一个 JSON 对象,返回值将是一个 Map;如果是 JSON 数组,则会返回 List:
运行结果如下:
需要说明的是,我们把 Map转化为对象时使用时定义了一个构造函数,但这个是任意的,使用静态方法、Dart 工厂方法等都是可行的。之所以限定 toJson 方法的原型,是因为 json.encode 只支持 Map、List、String、int 等内置类型。当它遇到不认识的类型时,如果没有给它设置参数 toEncodable,就会调用对象的 toJson 方法(所以方法的原型不能改变)。
HTTP
为了向服务器发送 HTTP 请求,我们可以使用 io 包里面的 HttpClient。但它实在不是那么好用,于是就有人弄出了一个 http 包。为了使用 http 包,需要修改 pubspec.yaml:
http 包的使用非常直接,为了发出一个 GET,可以使用 http.get 方法;对应的,还有 post、put 等。
HTTP POST 的例子我们在下面实现 echo 客户端的时候再看。
使用 SQLite 数据库
包 sqflite 可以让我们使用 SQLite:
sqflite 的 API 跟 Android 的那些非常像,下面我们直接用一个例子来演示:
运行结果如下:
有 Android 经验的读者会发现,使用 Dart 编写数据库相关代码的时候舒服很多。如果读者对数据库不太熟悉,可以参考《SQL必知必会》。本篇的主要知识点到这里的就讲完了,作为练习,下面我们就一起来实现 echo 客户端的后端。
echo 客户端
HTTP 服务端
在开始之前,你可以在 GitHub 上找到上篇文章的代码,我们将在它的基础上进行开发。
服务端架构
首先我们来看看服务端的架构(说是架构,但其实非常的简单,或者说很简陋):
在服务端框架里,我们把支持的所有路径都加到 routes 里面,当收到客户请求的时候,只需要直接从 routes 里取出对应的处理函数,把请求分发给他就可以了。如果读者对服务端编程没有太大兴趣或不太了解,这部分可以不用太关注。
将对象序列化为 JSON
为了把 Message 对象序列化为 JSON,这里我们对 Message 做一些小修改:
这里我们加入一个 toJson 方法。下面是服务端的 _echo 方法:
HTTP 客户端
我们的 echo 服务器使用了 dart:io 包里面 HttpServer 来开发。对应的,我们也可以使用这个包里的 HttpRequest 来执行 HTTP 请求,但这里我们并不打算这么做。第三方库 http 提供了更简单易用的接口。
首先把依赖添加到 pubspec 里:
客户端实现如下:
现在,让我们把他们和上一节的 UI 结合到一起。首先启动服务器,然后创建客户端:
大功告成,在做了这么多工作以后,我们的应用现在是真正的 echo 客户端了,虽然看起来跟之前没什么两样。接下来,我们就做一些跟之前不一样的——把历史记录保存下来。
历史记录存储、恢复
获取应用的存储路径
为了获得应用的文件存储路径,我们引入多一个库:
通过它我们可以拿到应用的 file、cache 和 external storage 的路径:
保存历史记录
加载历史记录
现在,我们来实现 _history 函数:
_history 的实现很直接,我们只是把 messages 全都返回给客户端。
接下来是客户端部分:
生命周期
最后需要做的是,在 APP 退出后关闭服务器。这就要求我们能够收到应用生命周期变化的通知。为了达到这个目的,Flutter 为我们提供了 WidgetsBinding 类(虽然没有 Android 的 Lifecycle 那么好用就是啦)。
现在,我们的应用是这个样子的:
flutter-echo-demo
所有的代码可以在 GitHub 上找到:
使用 SQLite 数据库
前面的实现中我们把 echo 服务器的数据存放在了文件里。这一节我们改一改,把数据存到 SQLite 中。
别忘了添加依赖:
初始化数据库
加载历史记录
加载历史记录的相关代码在 _loadMessages 方法中,这里我们修改原有的实现,让它从数据库加载数据:
实际上改为使用数据库来存储后,我们并不需要把所有的消息都存放在内存中(也就是这里的 _loadMessage 是不必要的)。客户请求历史记录时,我们再按需从数据库读取数据即可。为了避免修改到程序的逻辑,这里还是继续保持一份数据在内存中。有兴趣的读者可以对程序作出相应的修改。
保存记录
记录的保存很简单,一行代码就可以搞定了:
使用 JSON 的版本,我们每次都需要把所有的数据都保存一遍。对数据库来说,只要把收到的这一条信息存进去即可。读者也应该能够感受到,就我们的需求来说,使用 SQLite 的版本实现起来更简单,也更高效。
关闭数据库
close 方法也要做相应的修改:
这部分代码可以查看 tag echo-db:
领取专属 10元无门槛券
私享最新 技术干货