如果套路了你,不好意思,我是故意的

本文由玉刚说写作平台提供写作赞助

原作者:水晶虾饺

本文是学习指南系列的第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:

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181114B0ERKX00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券