为什么要学习Flutter?
作为一名Android开发人员,了解移动开发的技术栈是需要的。当年ReatNative 出来时出做的了一定的项目实践。移到开发的技术日新月异,学习新技术,特别是和前端有关的技术,我们才会更有竞争力。
最近趁项目开发开发之余,研究了一下Flutter。于是决定把这两天的研究笔记做一记录,方便今后跟踪继续学习。
一、移动开发技术对比
1.1 移动开发技术简介
目前移动开发中三种跨平台技术,现在我们从框架角度对比一下它们,如下表所示:
技术类型 | UI渲染方式 | 性能 | 开发效率 | 动态化 | 框架代表 |
---|---|---|---|---|---|
H5+原生 | WebView渲染 | 一般 | 高 | 支持 | Cordova、Ionic |
JavaScript+原生渲染 | 原生控件渲染 | 好 | 中 | 支持 | RN、Weex |
自绘UI+原生 | 调用系统API渲染 | 好 | Flutter高, QT低 | 默认不支持 | QT、Flutter |
上表中开发语言主要指UI的开发语言。而开发效率,是指整个开发周期的效率,包括编码时间、调试时间、以及排错、兼容时间。动态化主要指是否支持动态下发代码和是否支持热更新。值得注意的是Flutter的Release包默认是使用Dart AOT模式编译的,所以不支持动态化,但Dart还有JIT或snapshot运行方式,这些模式都是支持动态化的。
小结:原生开发地位永远不会没落,对性能要求要求高的应用,如游戏、视频等交互高的应用永远是原生开发体验好。针对前端以数据展示为主的应用,尝试跨平台开发是未来的趋势。想必未来的开发趋势会向大前端靠拢,而不在区分Android、iOS、H5端。
1.2 Flutter相关的所有技术:
对于打算学习Flutter的同学提供一个初步学习的路线:
- 学习dart语言的基本语法
- 学习大部分基础Widget的使用方式
- 学习各种Key的使用以及作用
- 了解Naviagator的实现方式
- 了解setState的过程
- 了解Dart的异步机制
- 了解Redux,Bloc等架构
1.3 Dart语言简介
- Dart在静态语法方面和Java非常相似,如类型定义、函数声明、泛型等,而在动态特性方面又和JavaScript很像,如函数式特性、异步支持等。除了融合Java和JavaScript语言之所长之外,Dart也具有一些其它具有表现力的语法,如可选命名参数、
..
(级联运算符)和?.
(条件成员访问运算符)以及??
(判空赋值运算符)。 - Dart类库有非常多的返回
Future
或者Stream
对象的函数。 这些函数被称为异步函数:它们只会在设置好一些耗时操作之后返回,比如像 IO操作。而不是等到这个操作完成。async
和await
关键词支持了异步编程,允许您写出和同步代码很像的异步代码
二、遇到的环境相关问题
2.1、安装环境遇到的问题
搭建开发环境 很简单,随便百度一篇教程都能很快搭建好。这里不在赘述。仅简单记录一下遇到的问题。
Android Studio也已经按照了flutter和dart插件,并且可以正常编译flutter项目,但就是不显示new Flutter Project菜单。
解决方法:在插件设置中Android ApkSupport选择勾上。之前为了提高加载效率而选择了禁用,现在打开即可。
参考链接:https://www.jianshu.com/p/eba...
2.2 不能创建项目成功
##flutter create flutter_app
Oops; flutter has exited unexpectedly.
Sending crash report to Google.
Failed to send crash report due to a network error: SocketException: OS Error: 信号灯超时时间已到
, errno = 121, address = clients2.google.com, port = 6192
网络问题 翻墙处理,再执行相同的命令 ,OK
![命令创建项目成功]
2.3.vscode中创建项目
出现问题记录:
用命令或Android Studio新建项目时出现崩溃,日志如下:
flutter --no-color create --template app --description A new Flutter application. --org com.florian test5
## exception
TemplateException: Value was missing for variable tag: macosIdentifier. (11:29)
PRODUCT_BUNDLE_IDENTIFIER = {{macosIdentifier}}
原因:flutter版本出现了问题,更新即可。git 更新或直接解压sdk安装包覆盖到之前的位置
问题解决方案来源:https://github.com/flutter/fl...
三、分析第一个Flutter应用
以 计数器Demo为例子:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
//程序应用
title: 'Flutter Demo',
theme: new ThemeData(
//蓝色主题
primarySwatch: Colors.blue,
),
//应用首页路由
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'You have pushed the button this many times:',
),
new Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
代码分析:
3.1 导入包
import 'package:flutter/material.dart';
此行代码作用是导入了Material UI组件库。Material是一种标准的移动端和web端的视觉设计语言, Flutter默认提供了一套丰富的Material风格的UI组件。
3.2 程序入口
void main()=>runApp(MyApp());
- Flutter 应用中
main
函数为应用程序的入口。main
函数中调用了runApp
方法,它的功能是启动Flutter应用。runApp
它接受一个Widget
参数,在本示例中它是一个MyApp
对象,MyApp()
是Flutter应用的根组件。 -
main
函数使用了(=>
)符号,这是Dart中单行函数或方法的简写。
3.3 应用结构
-
MyApp
类代表Flutter应用,它继承了StatelessWidget
类,这也就意味着应用本身也是一个widget。 - 在Flutter中,大多数东西都是widget(后同“组件”或“部件”),包括对齐(alignment)、填充(padding)和布局(layout)等,它们都是以widget的形式提供。
-
Scaffold
是 Material 库中提供的页面脚手架,它提供了默认的导航栏、标题和包含主屏幕widget树(后同“组件树”或“部件树”)的body
属性,组件树可以很复杂。本书后面示例中,路由默认都是通过Scaffold
创建 -
body
的组件树中包含了一个Center
组件,Center
可以将其子组件树对齐到屏幕中心。此例中,Center
子组件是一个Column
组件,Column
的作用是将其所有子组件沿屏幕垂直方向依次排列; 此例中Column
子组件是两个Text
,第一个Text
显示固定文本 “You have pushed the button this many times:”,第二个Text
显示_counter
状态的数值。 - Flutter在构建页面时,会调用组件的
build
方法,widget的主要工作是提供一个build()方法来描述如何构建UI界面(通常是通过组合、拼装其它基础widget)。 -
MaterialApp
是Material库中提供的Flutter APP框架,通过它可以设置应用的名称、主题、语言、首页及路由列表等。MaterialApp
也是一个widget。
3.4setState
方法作用:
通知Flutter框架,有状态发生了变化,Flutter框架收到通知后,会执行build
方法来根据新的状态重新构建界面, Flutter 对此方法做了优化,使重新执行变的很快,所以你可以重新构建任何需要更新的东西,而无需分别去修改各个widget。
3.5为什么要将build方法放在State中,而不是放在StatefulWidget中?
这主要是为了提高开发的灵活性。如果将build()
方法放在StatefulWidget
中则会有两个问题:
- 状态访问不便
- 继承
StatefulWidget
不便
四、路由管理
Navigator
Navigator
是一个路由管理的组件,它提供了打开和退出路由页方法。Navigator
通过一个栈来管理活动路由集合。通常当前屏幕显示的页面就是栈顶的路由。Navigator
提供了一系列方法来管理路由栈,在此我们只介绍其最常用的两个方法:
onPressed: () {
//导航到新路由
Navigator.push( context,
MaterialPageRoute(builder: (context) {
return NewRoute();
}));
},
- Future push(BuildContext context, Route route)
将给定的路由入栈(即打开新的页面),返回值是一个Future
对象,用以接收新路由出栈(即关闭)时的返回数据。
- bool pop(BuildContext context, [ result ])
将栈顶路由出栈,result
为页面关闭时返回给上一个页面的数据。
Navigator
还有很多其它方法,如Navigator.replace
、Navigator.popUntil
等,详情请参考API文档或SDK源码注释,在此不再赘述。下面我们还需要介绍一下路由相关的另一个概念“命名路由”。
MaterialPageRoute`:
MaterialPageRoute
继承自PageRoute
类,PageRoute
类是一个抽象类,表示占有整个屏幕空间的一个模态路由页面,它还定义了路由构建及切换时过渡动画的相关接口及属性。MaterialPageRoute
是Material组件库提供的组件,它可以针对不同平台,实现与平台页面切换动画风格一致的路由切换动画:
五、项目开发实践
学习了B站的入门教学视频:两小时掌握Flutter移动App开发
这个上手很快,快速学习一下即可。可能快速的了解一个项目的快速搭建到打包发布的流程。
六 、开发知识点小结
dio
网络请求方案总结
dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时等。
引入 ,在配置文件pubspec.yaml
中:添加依赖 (本文以版本1.0.9为例)
dependencies:
# 网络数据请求
dio: ^1.0.9
导入并创建dio实例:
import 'package:dio/dio.dart';
Dio dio = Dio();
一个dio实例可以发起多个http请求,一般来说,APP只有一个http数据源时,dio应该使用单例模式。
示例:
get请求:
官方Demo:
import 'package:dio/dio.dart';
void getHttp() async {
try {
Response response = await Dio().get("http://www.google.com");
print(response);
} catch (e) {
print(e);
}
}
//项目实践 请求豆瓣排行top250的第一页数据
Response response;
response=await dio.get("http://www.liulongbin.top:3005/api/v2/movie/top250?start=1&count=10")
print(response.data.toString());
post请求:
response=await dio.post("/test",data:{"id":12,"name":"wendu"})
发起多个并发请求:
response= await Future.wait([dio.post("/info"),dio.get("/token")]);
下载文件:
response=await dio.download("https://www.google.com/",_savePath);
发送 FormData:
FormData formData = new FormData.from({
"name": "wendux",
"age": 25,
});
response = await dio.post("/info", data: formData)
如果发送的数据是FormData,则dio会将请求header的contentType
设为“multipart/form-data”。
通过FormData上传多个文件:
FormData formData = new FormData.from({
"name": "wendux",
"age": 25,
"file1": new UploadFileInfo(new File("./upload.txt"), "upload1.txt"),
"file2": new UploadFileInfo(new File("./upload.txt"), "upload2.txt"),
// 支持文件数组上传
"files": [
new UploadFileInfo(new File("./example/upload.txt"), "upload.txt"),
new UploadFileInfo(new File("./example/upload.txt"), "upload.txt")
]
});
response = await dio.post("/info", data: formData)
值得一提的是,dio内部仍然使用HttpClient发起的请求,所以代理、请求认证、证书校验等和HttpClient是相同的,我们可以在onHttpClientCreate
回调中设置,例如:
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
//设置代理
client.findProxy = (uri) {
return "PROXY 192.168.1.2:8888";
};
//校验证书
httpClient.badCertificateCallback=(X509Certificate cert, String host, int port){
if(cert.pem==PEM){
return true; //证书一致,则允许发送数据
}
return false;
};
};
注意,onHttpClientCreate
会在当前dio实例内部需要创建HttpClient时调用,所以通过此回调配置HttpClient会对整个dio实例生效,如果你想针对某个应用请求单独的代理或证书校验策略,可以创建一个新的dio实例即可。
参考资料:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。