为什么要学习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相关的所有技术.png

对于打算学习Flutter的同学提供一个初步学习的路线:

  1. 学习dart语言的基本语法
  2. 学习大部分基础Widget的使用方式
  3. 学习各种Key的使用以及作用
  4. 了解Naviagator的实现方式
  5. 了解setState的过程
  6. 了解Dart的异步机制
  7. 了解Redux,Bloc等架构

1.3 Dart语言简介

  • Dart在静态语法方面和Java非常相似,如类型定义、函数声明、泛型等,而在动态特性方面又和JavaScript很像,如函数式特性、异步支持等。除了融合Java和JavaScript语言之所长之外,Dart也具有一些其它具有表现力的语法,如可选命名参数、..(级联运算符)和?.(条件成员访问运算符)以及??(判空赋值运算符)。
  • Dart类库有非常多的返回Future或者Stream对象的函数。 这些函数被称为异步函数:它们只会在设置好一些耗时操作之后返回,比如像 IO操作。而不是等到这个操作完成。 asyncawait关键词支持了异步编程,允许您写出和同步代码很像的异步代码

二、遇到的环境相关问题

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

![命令创建项目成功]命令创建项目成功.png

2.3.vscode中创建项目

创建项目.png

出现问题记录:

用命令或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.replaceNavigator.popUntil等,详情请参考API文档或SDK源码注释,在此不再赘述。下面我们还需要介绍一下路由相关的另一个概念“命名路由”。

MaterialPageRoute`:

MaterialPageRoute继承自PageRoute类,PageRoute类是一个抽象类,表示占有整个屏幕空间的一个模态路由页面,它还定义了路由构建及切换时过渡动画的相关接口及属性。MaterialPageRoute 是Material组件库提供的组件,它可以针对不同平台,实现与平台页面切换动画风格一致的路由切换动画:

五、项目开发实践

学习了B站的入门教学视频:两小时掌握Flutter移动App开发

这个上手很快,快速学习一下即可。可能快速的了解一个项目的快速搭建到打包发布的流程。

六 、开发知识点小结

dio网络请求方案总结

git上的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实例即可。

参考资料:

Flutter中文网


小羊子说
13 声望6 粉丝

Android开发一枚,专注分享工作中用到的技术总结,欢迎交流。