plainify

AI画家第五弹——从0到1部署你的RESTful API

上一篇文章中我们已经利用 Flask 制作了一个 RESTful API,然而文中末尾我们我们将代码运行起来的时候确发现是 localhost:xxxx,这也就意味着我们没法通过外网访问我们提供的 API,所以这样做出来的程序即使部署到服务器上也没有任何用处。这一篇我们就来详细的说下怎么部署到服务器上然后实现外网访问吧。 环境准备 工欲善其事,必先利其器。还是老样子,既然要选择部署到服务器上,我们首先肯定得要有一个服务器,这里我选择 XX 云的学生机(别问为什么,问就是便宜)。系统选择 Ubuntu18.04,需要安装的东西有: Python 3.6 Nginx pyenv pipenv Flask + Gunicorn 这里我们用 pyenv 管理不同的 python 版本,这里可能就有人要问了,Ubuntu 不是自带 python2 和 python3 吗,为什么还要用 pyenv 来管理,之前的文章我们也说过,用 pyenv 管理 python 会非常的方便,方便到只需要一行代码就可以安装切换系统的 Python 版本,废话不多说,接下来就开始我们的操作吧。 连接到远程服务器 如何购买 XX 云的云主机我就不多说了,当你买好之后我们在后台查看到我们购买的云主机的外网 IP,比如我的服务器的外网 IP 地址就是94.191.9.43(希望各位大佬手下留情,别弄他): 接下来就是连接到服务器了,工具有很多,xshell、putty 或者 terminal 都可以,这里我推荐一个工具Termius,推荐他的原因有两个:颜值,作为一个颜控,Termius 是我见过的最好看的 SSH 客户端,没有之一。同时还是跨平台的,win/mac/linux/android/ios 都支持,这点不知道比 xshell 高到哪里去了。 连接也很简单,我们在 termius 中添加需要连接的 host,然后输入用户名和密码即可登录了,出现如下字样说明登录成功。 利用 pyenv 安装制定版本的 python 之前的文章介绍过如何在 mac 下安装 pyenv,虽然 linux 下的安装方法类似,但毕竟本文的题目是是从 0 到 1 部署,还是再详细说一下吧。 ...

AI画家第六弹——终章

Photo by Tim Gouw from Pexels 正如文章标题所言,本文是我的**《AI画家》**系列的终章,从4月16号开始的第一篇文章《介绍一下我的毕业设计》到今天已经过了将近2个月了(2个月就更新7篇文章我也是服了我自己了😂),其实这个系列就是我的毕业设计,如今我的毕设做完了,论文也定稿了,想着也该给这个系列画个句号了。 在公众号上发系列文章是我的一次尝试,因为知道自己的文笔不好,写不好什么感悟感想,只能水水技术文了,然而即使要写技术文也得找个切入点,可事实上哪会有那么多切入点,无奈只能靠弄个技术文系列,吸引下路人的眼球,勉强度日了。 既然是终章,那就对整个系列做个简单的总结吧,希望这篇不会是烂尾文。 开题 在《介绍一下我的毕业设计》一文中我提到,刚开始我单纯因为觉得好玩才开始研究图像风格迁移,后来认为名字逼格够高,足够唬住老师才把这个课题作为我的毕设,现在回过头来结合最后的答辩觉得自己选这个题目的毕设是真的傻X,不是因为这个选题不好,而是把这个选题作为毕业设计的题目真的傻X,说到这就想起一些在答辩现场不好的事情了,溜了溜了。 过程 作者自认为在这个系列中思路还是比较清晰的。 首先是第一弹,想要做深度学习有关的东西,刚开始肯定是要搭建好一个深度学习的环境的。在这篇文章中,详细的介绍了在我的游戏本上利用GPU搭建深度学习环境的过程,包括安装nvidia drive、CUDA、cuDNN和TensorFlow的GPU版本,以及简单介绍了如何利用Pyenv和Pipenv管理自己电脑上的Python虚拟环境。 然后是第二弹,介绍了什么是图像风格迁移,以及如何用TensorFlow实现了一个基本的图像风格迁移的程序,在这篇文章中我只是简单的介绍了图像风格迁移的原理,一方面是因为公式实在是有点多,公众号对于公式的支持不是很好,另一方面作为一个入门介绍,放太多的数学公式的话估计本来人数不多的公众号又要取关一批人了。 第三弹介绍了一个目前非常流行的Python web框架Flask,介绍他的原因不仅仅是因为我的毕业设计就是用这玩意做的,而且在开题的时候我也提到了最后要将这个深度学习算法落地,能够做出一个可用的RESTful API,而相较于其他Web框架,Flask有着得天独厚的优势:首先TensorFlow的首选语言是Python,Flask也是用Python写的,调用起来非常方便;相对于Django,Flask非常轻便,使用起来也非常的灵活。然而在用Flask之前,必须要有些基础知识,因此在这篇文章开篇,我又介绍了前后端分离,介绍了什么是RESTful API,如果对这些概念没有理解的话就算把代码写出来了也很难知道是干嘛的。 既然在第三弹介绍了一个用来编写RESTful API的工具,那第四弹自然就是如何编写这个RESTful API了。其实在第二弹中就指出了那个算法的不足之处——“慢”,所以在这篇文章中就使用了第二个算法训练出来的模型,之所以不介绍新的风格迁移算法,主要是因为这个算法有点难度,我理解的时候也花了好长时间,等到基本上理解了,第四弹都已经写完了😂。所以在这篇文章中我干脆直接用别人训练好的现成的模型了。其中介绍了如何利用蓝图(BluePrint)进行模块化开发,并给出了我自己认为的比较好的分层方法,然后利用七牛云存储为服务器减压,最后利用Postman请求该API完成测试。 第五弹的主要内容就是介绍如何将第四弹中已经在本地测试成功的项目发布到服务器上实现公网访问,介绍了如何使用了XX云主机,并在上面安装配置了项目所需要使用到的环境的过程,介绍了Termius、FileZilla等工具。在这篇文章中我希望会有些和别的文章不一样的东西,因此我不仅把实现过程展现了出来,还分享了一些我在实际使用过程中产生的疑问,踩到的坑以及我个人在部署过程中总结的一些经验。 最后 总的来说,这个系列不仅仅是我在做毕业设计时的一个缩影,也是我自己对于公众号运营的一个探索。期间也收到了很多读者的支持与鼓励,感谢你们的一路陪伴🙏 推荐阅读 《介绍一下我的毕业设计》 《AI 绘画第一弹——用GPU为你的训练过程加速》 《AI绘画第二弹——图像风格迁移》 《AI画家第三弹——毕业设计大杀器之Flask》 《AI画家第四弹——利用Flask发布风格迁移API》 《AI画家第五弹——从0到1部署你的RESTful API》

plainify

AI画家第四弹——利用Flask发布风格迁移API

上篇文章介绍了python web开发中经常使用到的一个框架flask,如果有遗忘的,可以点此回顾👉AI画家第三弹——毕业设计大杀器之Flask,本文的主要任务就是完成上篇文章末尾的要求,利用Flask发布你自己的风格迁移API。 本文源码可在微信公众号「01二进制」后台回复「风格迁移API」获得 需求分析 我们知道软件工程的第一步就是需求分析,放在这里就是要知道我们需要实现的功能是什么样的。我画了一张简陋的图来描述这次的需求: 真的是很简陋的一张图啊,其实理解起来很容易,就是用户上传一张图片,Flask获取到这张图片,调用风格迁移的模型,然后生成结果图,在传递回前端即可。 环境准备 既然明白了需求,那么接下来要做的事情自然是环境搭建了,老样子,这里我们仍然使用Pipenv来创建虚拟环境,如何搭建pipenv环境我就不说了,在微信公众号「01二进制」后台回复「风格迁移API」获得源码之后直接在终端输入pipenv install即可。 开始 hello world 我们首先在项目根目录创建一个main.py的文件作为整个项目的启动文件,上文我们说过,为了简化大型应用并为扩展提供集中的注册入口,我们并不会将所有的视图函数直接写在main.py,而是采用蓝图的方式分模块开发,因此我们需要在项目根目录新建app/文件夹,在其中的__init__.py中编写如下代码: from flask import Flask def create_app(): app = Flask(__name__) return app 这样我们就可以通过在main.py中编写如下代码实现一个hello world应用了。 from app import create_app app = create_app() @app.route('/') def hello(): return 'hello,world' if __name__ == '__main__': app.run(port=8080, debug=True) 启动main.py即可发现项目启动了,在浏览器输入localhost:8080即可看到hello,world字样。 蓝图编写 我们肯定是不能满足于小小的hello world的,既然说到了模块化开发,那怎么个模块法? 这里每个人的想法都是不一样的,其实也没有一个统一的标准,这里我就说下我自己的分级方法吧。 stylize是项目根路径 app是项目 app/api是项目的api部分,一个项目肯定不只有api,还可能会有web等页面内容 app/api/v1表明该api的版本是v1,当然日后也有可能会有v2、v3等等 app/api/v1/img表明这里存放的都是和img有关的api app/api/v1/img/stylize.py表明这个文件存放的是风格迁移的视图函数 认识完结构的划分之后,就来编写我们的蓝图吧。 首先我们需要在app/api/v1/img/__init__.py中编写如下代码: from flask import Blueprint # 定义一个蓝图 img = Blueprint('img', __name__) from app.api.v1.img import stylize # 这段代码用来测试该接口是否可用 @img.route('/') def say_hello(): return '这里是图片处理类的接口' 这样我们就定义了一个叫做img的蓝图,然后我们在app/api/v1/__init__.py中编写如下代码: from flask import Blueprint # 定义一个蓝图 v1 = Blueprint('v1', __name__) from app.api.v1.img import img 这样我们就实现了v1蓝图的编写。 那这样是不是就可以使用蓝图了呢?当然不是,我们还需要在app中配置这个蓝图,把蓝图加载到app中,否则flask是无法识别蓝图的。加载的方法也很简单,我们在app/__init__.py文件中添加一个函数: ...

plainify

AI绘画第二弹——图像风格迁移

这篇文章是我的《AI 绘画系列》的第三篇,点击此处可以查看整个系列的文章 本篇文章的源码可以在微信公众号「01 二进制」后台回复「图像风格迁移」获得 简介 所谓图像风格迁移,是指将一幅内容图 A 的内容,和一幅风格图 B 的风格融合在一起,从而生成一张具有 A 图风格和 B 图内容的图片 C 的技术。目前这个技术已经得到了比较广泛的应用,这里安利一个 app——“大画家”,这个软件可以将用户的照片自动变换为具有艺术家的风格的图片。 准备 其实刚开始写这篇文章的时候我是准备详细介绍下原理的,但是后来发现公式实在是太多了,就算写了估计也没什么人看,而且这篇文章本来定位的用户就是只需要实现功能的新人。此外,有关风格迁移的原理解析的博客实在是太多了,所以这里我就把重点放在如何使用 TensorFlow 实现一个快速风格迁移的应用上,原理的解析就一带而过了。如果只想实现这个效果的可以跳到**“运行”**一节。 第一步我们需要提前安装好 TensorFlow,如果有 GPU 的小伙伴可以参考我的这篇文章搭建一个 GPU 环境:《AI 绘画第一弹——用 GPU 为你的训练过程加速》,如果打算直接用 CPU 运行的话,执行下面一行话就可以了 pip install numpy tensorflow scipy 原理 本篇文章是基于A Neural Algorithm of Artistic Style一文提出的方法实现的,如果嫌看英文论文太麻烦的也可以查看我对这篇文章的翻译【译】一种有关艺术风格迁移的神经网络算法。 为了将风格图的风格和内容图的内容进行融合,所生成的图片,在内容上应当尽可能接近内容图,在风格上应当尽可能接近风格图,因此需要定义内容损失函数和风格损失函数,经过加权后作为总的损失函数。 预训练模型 CNN 具有抽象和理解图像的能力,因此可以考虑将各个卷积层的输出作为图像的内容,这里我们采用了利用 VGG19 训练好的模型来进行迁移学习,一般认为,卷积神经网络的训练是对数据集特征的一步步抽取的过程,从简单的特征,到复杂的特征。训练好的模型学习到的是对图像特征的抽取方法,而该模型就是在 imagenet 数据集上预训练的模型,所以理论上来说,也可以直接用于抽取其他图像的特征,虽然效果可能没有在原有数据集上训练出的模型好,但是能够节省大量的训练时间,在特定情况下非常有用。 加载预训练模型 def vggnet(self): # 读取预训练的vgg模型 vgg = scipy.io.loadmat(settings.VGG_MODEL_PATH) vgg_layers = vgg['layers'][0] net = {} # 使用预训练的模型参数构建vgg网络的卷积层和池化层 # 全连接层不需要 # 注意,除了input之外,这里参数都为constant,即常量 # 和平时不同,我们并不训练vgg的参数,它们保持不变 # 需要进行训练的是input,它即是我们最终生成的图像 net['input'] = tf.Variable(np.zeros([1, settings.IMAGE_HEIGHT, settings.IMAGE_WIDTH, 3]), dtype=tf.float32) # 参数对应的层数可以参考vgg模型图 net['conv1_1'] = self.conv_relu(net['input'], self.get_wb(vgg_layers, 0)) net['conv1_2'] = self.conv_relu(net['conv1_1'], self.get_wb(vgg_layers, 2)) net['pool1'] = self.pool(net['conv1_2']) net['conv2_1'] = self.conv_relu(net['pool1'], self.get_wb(vgg_layers, 5)) net['conv2_2'] = self.conv_relu(net['conv2_1'], self.get_wb(vgg_layers, 7)) net['pool2'] = self.pool(net['conv2_2']) net['conv3_1'] = self.conv_relu(net['pool2'], self.get_wb(vgg_layers, 10)) net['conv3_2'] = self.conv_relu(net['conv3_1'], self.get_wb(vgg_layers, 12)) net['conv3_3'] = self.conv_relu(net['conv3_2'], self.get_wb(vgg_layers, 14)) net['conv3_4'] = self.conv_relu(net['conv3_3'], self.get_wb(vgg_layers, 16)) net['pool3'] = self.pool(net['conv3_4']) net['conv4_1'] = self.conv_relu(net['pool3'], self.get_wb(vgg_layers, 19)) net['conv4_2'] = self.conv_relu(net['conv4_1'], self.get_wb(vgg_layers, 21)) net['conv4_3'] = self.conv_relu(net['conv4_2'], self.get_wb(vgg_layers, 23)) net['conv4_4'] = self.conv_relu(net['conv4_3'], self.get_wb(vgg_layers, 25)) net['pool4'] = self.pool(net['conv4_4']) net['conv5_1'] = self.conv_relu(net['pool4'], self.get_wb(vgg_layers, 28)) net['conv5_2'] = self.conv_relu(net['conv5_1'], self.get_wb(vgg_layers, 30)) net['conv5_3'] = self.conv_relu(net['conv5_2'], self.get_wb(vgg_layers, 32)) net['conv5_4'] = self.conv_relu(net['conv5_3'], self.get_wb(vgg_layers, 34)) net['pool5'] = self.pool(net['conv5_4']) return net 训练思路 我们使用 VGG 中的一些层的输出来表示图片的内容特征和风格特征。比如,我使用[‘conv4_2’,’conv5_2’]表示内容特征,使用[‘conv1_1’,’conv2_1’,’conv3_1’,’conv4_1’]表示风格特征。在settings.py中进行配置。 ...

plainify

API与SDK:有什么区别?

前言 什么是 API? 什么是 SDK? 两者之间有何关系? 欢迎来到本次的每周一问系列。 既然点进来了,相信你或多或少都听说过这两个名词了,因此,在为你解答之前,让我们先从一个例子出发。 假如你想开发一个 OCR 应用(通俗的说就是文字识别应用),他的功能是识别用户上传的一张图片,然后将图片中的文字识别出来返回给用户。如下图所示: 通常,OCR 应用的后端服务都会部署在云上,那么我们应该如何在移动应用程序与基于云的服务之间进行通信呢? 这就是 API 和 SDK 的用武之地了。 API API 的特点 通信 首先我们要明白的是 API 是和通信有关的,是用于应用(服务)与其他应用(服务)对话所定义的协议。在上述例子中,你可以简单理解为 API 是 OCR 应用和云端服务之间沟通的桥梁。 那么 API 到底是什么? API 全称 Application Programming Interface,即 「应用程序接口」 。 一般是指一些预先定义的函数,目的是供应用程序与开发人员基于某软件或硬件得以访问一组程序的能力,而又无需访问源码,或理解内部工作机制的细节。 以 Java 为例,当你想要实现一个数组排序的功能时,你是会先手写一个排序算法,还是直接使用Arrays.sort()函数?我想你心里是有答案的。 抽象 其次,我们要理解,API 的另一个重要特点——抽象。 抽象指的又是什么? 还是以这个 OCR 应用为例,当我们在使用云端提供的文字识别能力时(比如百度文字识别),他的背后可能会有成千上万的代码,比如提供识别能力的机器学习的代码、提供 Web 能力的后端代码等等。 但是你作为一个 APP 的开发者,你需要去看这些代码是怎么写的吗?难道不知道背后的源码就不能调用百度提供的文字识别能力了吗?当然不是。 通常服务商已经给你提供了文档,告诉你如何去调用相应服务,只要你按照他的要求来即可。 因此,在你的 APP 和 OCR 服务之间,API 抽象出所有复杂的逻辑,简化了调用过程,这使得你只需要考虑获取所需的数据即可。 标准化 API 是标准化的,这意味着存在有关如何定义 API 的行业标准,比如 SOAP、REST、GraphQL 等。 ...

plainify

Docker 安装 RocketMQ 并结合 SpringBoot 使用实例

在之前的《浅入浅出消息队列》一文中,我们了解了消息队列的作用、优缺点和使用场景,相信你对消息队列已经有了一个大致的概念,文末给自己埋的坑说日后会写一篇实战教程,正好现在实习结束了,也许久没有写实战教程了,于是这就来填坑了。 前置知识 阅读本文前,建议有一些前置知识,包括且不限于: 常见的 Linux 命令 消息队列的相关知识 Docker 的基本使用 docker-compose 的基础知识 SpringBoot 的基本使用 那废话不多说,我们就开始吧。 本文的所涉及到的代码可在微信公众号「01 二进制」后台回复「rocketmq」获得。 为什么要以 RocketMQ 为例? 本文主要是为了通过实例的方式直观的了解消息队列。那么问题来了,消息队列那么多(ActiveMQ、RabbitMQ、Kafka),我为什么要选择 RocketMQ 呢?这里我们不谈原理,只说说体验,仅是个人选择,不喜勿喷。 背靠阿里,不看测评,纯粹看他经历过多次双十一的检验就已经知道其性能是处于第一批次的。 作为一个 Java 程序员,如果选择一个纯 Java 编写的软件,后期阅读其源码难度也会小很多。(RabbitMQ 底层是 Erlang,kafka 底层是 Scala) 在阿里实习的时候一直都是使用 RocketMQ 的内部版本,于我而言,RocketMQ 更熟悉。 初识 RocketMQ 在使用消息队列前,我们要知道消息队列是什么,这一块内容参考之前的文章《浅入浅出消息队列》,这里不再赘述。 本段节来讲解 RocketMQ 所涉及到的相关概念,我们先来简单看下官方给出的 RocketMQ 架构图 从上图我们可以很直观的看出,一个完整的 RocketMQ 架构包含四个部分:NameServer、Broker、Producer 和 Consumer。 NameServer:主要用作注册中心,用于管理 Topic 信息和路由信息的管理 Broker:负责存储、消息 tag 过滤和转发。需将自身信息上报给注册中心 NameServer Producer:生产者 Consumer:消费者 从寄信的角度理解 上面的解释可能难以理解,我们从寄信这一实例来看以下四个部分所承担的责任。 Producer 和 Consumer 不必多说,消息的生产者和消费者,生产者负责投递消息,消费者负责接收消息,是我们要编写的应用程序。可以理解为寄信人和收信人。 Broker 负责消息存储,以 Topic(主题)为维度,以队列的形式存储消息。可以理解为信箱,专门存储信件,收信人(Consumer)可以从这里获取信件。 NameServer 负责对源数据进行管理,包括了对 Topic 和 Broker 的管理。可以理解为邮局,负责管理邮件的分发,维护信箱(Broker)的状态。 由上各部分角色的功能可知,我们需要先安装启动 NameServer,再启动 Broker 即可搭建完 RocketMQ ...

Flutter 表单组件

前言 最近在利用flutter制作校园软件,需要制作一个登录界面,所以要用到Flutter中的一些表单控件,今天就来总结下flutter中的一些表单控件。 本文参考: 《Flutter 基础组件-表单》 FormState class Form Class TextField class TextFormField ...

Flutter主题切换——让你的APP也能一键换肤

为了让你的 App 更美观,主题切换已经是一个必不可少的功能了,但如果想在传统的 Android 和 iOS 上分别适配不同的主题相当繁琐。但这一切,在 Flutter 中都非常容易实现。今天我们就来看看,如何在 Flutter 中给你的 App 添加换肤功能。我们要实现的效果如下: 添加依赖 在该案例中,我使用到了 provider 和 flustars 两个库,简单介绍一下这两个库: provider 官方推荐的状态管理库,相比其他状态管理库使用起来比较方便。 状态管理:通俗的讲,当我们想在多个页面(组件/Widget)之间共享状态(数据),或者一个页面(组件/Widget)中的多个子组件之间共享状态(数据),这个时候我们就可以用 Flutter 中的状态管理来管理统一的状态(数据),实现不同组件直接的传值和数据共享。 flustars 号称“Flutter 全网最全常用工具类”,其中包括了SpUtil、ScreenUtil、TimelineUtil等常见工具类,这里我们要使用的是SpUtil这个部分,用于存储用户所选择的主题信息。 以上就是关于我们使用的两个第三方库的介绍,如果想要使用,我们需要在pubspec.yaml文件中添加如下内容: provider: ^4.0.5 flustars: ^0.2.6+1 准备工作做好了,接下来我们就开始编码吧。 添加主题样式 我们需要先想好自己所需要切换的主题样式列表,如果觉得麻烦的可以直接用下面的内容: Map<String, Color> themeColorMap = { 'gray': Colors.grey, 'blue': Colors.blue, 'blueAccent': Colors.blueAccent, 'cyan': Colors.cyan, 'deepPurple': Colors.purple, 'deepPurpleAccent': Colors.deepPurpleAccent, 'deepOrange': Colors.orange, 'green': Colors.green, 'indigo': Colors.indigo, 'indigoAccent': Colors.indigoAccent, 'orange': Colors.orange, 'purple': Colors.purple, 'pink': Colors.pink, 'red': Colors.red, 'teal': Colors.teal, 'black': Colors.black, }; 使用 Provider 进行全局状态管理 然后我们就需要使用 Provider 来进行全局的状态管理了。首先先创建一个app_provider.dart文件,然后添加如下代码: class AppInfoProvider with ChangeNotifier { String _themeColor = ''; String get themeColor => _themeColor; setTheme(String themeColor) { _themeColor = themeColor; notifyListeners(); } } 因为是全局的状态管理,接下来我们需要在main.dart文件中配置一下刚才创建的 provider,有多个状态管理就使用 MultiProvider,单个的使用 Provider.value 就行了。(考虑到未来项目的扩展,这里我就直接使用 MultiProvider)了 ...

plainify

Flutter实现文件下载

前言 之前有做一个工具集的微信小程序「开挂 Lite」,但是由于小程序自身限制,没有办法实现下载文件的功能,只能把下载链接解析出来。而且受限于微信平台,小程序的审核是一件很麻烦的事情,因此有了将其 APP 化的想法。 自从去年 Flutter 横空出世后,我便一直关注它的发展,时隔一年后重新拾起,发现它的生态已经初具规模,于是决定采用 Flutter 重做一个「开挂 Lite」。后期我也会不定时更新一些和 Flutter 有关的文章,希望大家可以多多支持。本文记录的便是我利用 Flutter 实现文件下载功能的过程。 完整源码可在公众号:「01 二进制」后台回复:「Flutter 文件下载」获取 开始 我们先看一下实现的效果: iOS Android 本 demo 的实现效果非常简单,就是点击一个按钮,然后下载文件,完成后提示用户是否打开文件。 准备工作 在本 demo 中使用的 IDE 为 Android Studio,同时使用到了以下几个库: flutter_downloader: ^1.1.7 path_provider: 1.1.2 permission_handler: ^3.1.0 progress_dialog: ^1.1.0+1 toast: ^0.1.4 我们先新建一个空项目,然后将上述依赖添加到项目的pubspec.yaml文件,添加位置如下: 接下来我们可以在 Terminal 中输入flutter packages get或者点击 IDE 左上角的Packages get字样安装依赖。 然后将初始项目中的多余代码删除,并在中间添加一个按钮。 body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ RaisedButton( child: Text("点我下载文件"), onPressed: () { // 执行下载操作 _doDownloadOperation(); }, ), ], ), ), 其中_doDownloadOperation()便是我们执行下载操作的方法,至此,前期准备工作结束。 逻辑分析 虽然整个下载演示的过程非常简单,但还是有必要来分析整个下载的流程,如下图所示: ...

Flutter数据存储之shared_preferences

前言 做过android开发的人都知道,可以利用SharedPreferences这个轻量级的存储类来保存键值对信息,在Flutter中,我们可以使用shared_preferences库来同时支持Android和ios平台。 参考: 《Flutter中的本地存储》 《Flutter知识点:数据存储之SharedPreferences》 shared_preferences 0.4.2 使用介绍 在pubspec.yaml文件中添加依赖 shared_preferences: "^0.4.2" 添加的位置如图所示: ...