Flutter学习总结1:flutter的特点
学习Flutter也差不多两个月了,工作中一直没用上,不过还是挤时间做了个简陋的App练手。
项目是jf_reader,一个小说阅读器,代码开源在github。用户可以输入一个TXT文件或Zip文件的链接,应用会去下载并解析;也可以输入小说名进行搜索,应用会去一些第三方小说网站抓取。
整个项目dart代码量在1w行出头,算是对Flutter的开发有了基本的了解,这里对Flutter涉及的知识体系做个梳理。
对大部分尤其是国内的客户端开发者而言,dart的异步编程、并发编程,Flutter的声明式UI都是比较新颖的编程范式/理念,需要适应一下。其它的大部分都是API的区别,对稍有经验的开发者不算太大问题。
异步编程
原始的异步编程常用方法是回调函数,近几年来async/await这样的协程语法逐渐流行起来,dart也用了async/await。
前端开发者对此应该很熟悉了,但我发现很多优秀的客户端开发者对此心存疑虑难以接受,这里我要强调一点,async/await是目前为止毫无疑问的异步编程最佳语法,必须要学的,越早接受越好。
给个例子:
Future fetchPost() async {
return http.get('https://jsonplaceholder.typicode.com/posts/1');
}
Future loadData() async {
Response response = await fetchPost();
return response.body;
}
具体内容不展开了,推荐几篇文章作参考:
Asynchronous programming: futures, async, await
并发编程
dart使用的并发机制称为isolate。isolate是跟线程类似的运行实体,但isolate间不能共享内存,只通过消息通信,这就避免了多线程编程中令人痛苦的线程安全问题。而代价是,为了保证不共享内存,isolate间的消息是经过深拷贝的。抽象点讲,也就是通过增加一定的冗余来降低耦合。
消息和共享内存一直是并发编程的两大流派,基于共享内存的多线程派一直以来更加广泛,但近几年基于消息的并发编程范式发展非常迅速,如go/dart等比较新的语言都采用了这种方式(js的webworker其实也是)。
给个例子:
void main() async{
final ReceivePort resultPort = ReceivePort();
await Isolate.spawn(isolateTask,resultPort.sendPort)
resultPort.listen((data){
print("$data,time:${DateTime.now()}");
resultPort.close();
});
print("start isolate task");
}
void isolateTask(SendPort port) async {
await Future.delayed(Duration(seconds:5));
port.send("isolate task done");
}
可以看到通过消息进行通信还是很麻烦的,因此flutter在此之上封装了一个compute方法,看个例子:
static Future>> parseWithContent (String content) async {
List
这里parseContentLogic是个耗时操作,content是要传进去的参数,compute就会开个isolate去执行并返回一个future对象了。
推荐几篇文章:
如何理解 Golang 中“不要通过共享内存来通信,而应该通过通信来共享内存”?
声明式UI
UI开发其实经历了纯命令式到半声明式到完全声明式的发展过程。
纯命令式就是UI的构建和修改都是命令式的,目前大多iOS开发都是这类;
半声明式是HTML或安卓的XML这种,UI的构建是依赖于描述语言声明的,但UI的修改仍是命令式的。
而近几年逐渐流行的声明式,但不是XML/HTML这种单独搞出个DSL去描述UI,而是在原本的语言中通过一个数据结构去描述,修改UI时也不是拿到那个View去命令式的修改,而是重新生成这个描述结构。
从命令式迁移到声明式必然有一段适应期,但是你要相信声明式UI确实是比命令式好的。
不谈具体API,其发展过程就是明证:声明式UI从web端的React开始流行,目前web端框架基本上都是声明式的;再扩散到RN/Weex这类跨平台框架,以及小程序框架,更后面的Flutter和SwiftUI也是走了这条路。基本上可以说,未来的UI都会是声明式的了。
看一下代码吧:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
MyHomePage是个StatefulWidget,有状态的组件,我们关注下build
方法中的部分,可以看到build方法返回了一个嵌套的组件结构,UI就是根据这个结构进行构造的,当我们想要刷新UI时,看下面的onPressed,调用了_incrementCounter
,触发了setState方法,这个方法在更新数据后会重新触发build,重新生成Widget的声明式树形结构。