开启你的flutter 应用
一,选择flutter 的原因
Flutter 是 Google推出并开源的移动应用开发框架,主打跨平台、高保真、高性能。开发者可以通过 Dart语言开发 App,一套代码同时运行在 iOS 和 Android平台。 Flutter提供了丰富的组件、接口,开发者可以很快地为 Flutter添加 native扩展。同时 Flutter还使用 Native引擎渲染视图,这无疑能为用户提供良好的体验。
跨平台自绘引擎
Flutter与用于构建移动应用程序的其它大多数框架不同,因为Flutter既不使用WebView,也不使用操作系统的原生控件。 相反,Flutter使用自己的高性能渲染引擎来绘制widget。这样不仅可以保证在Android和iOS上UI的一致性,而且也可以避免对原生控件依赖而带来的限制及高昂的维护成本。
Flutter使用Skia作为其2D渲染引擎,Skia是Google的一个2D图形处理函数库,包含字型、坐标转换,以及点阵图都有高效能且简洁的表现,Skia是跨平台的,并提供了非常友好的API,目前Google Chrome浏览器和Android均采用Skia作为其绘图引擎。
目前Flutter默认支持iOS、Android、Fuchsia(Google新的自研操作系统)三个移动平台。但Flutter亦可支持Web开发(Flutter for web)和PC开发,本书的示例和介绍主要是基于iOS和Android平台的,其它平台读者可以自行了解。
高性能
Flutter高性能主要靠两点来保证,首先,Flutter APP采用Dart语言开发。Dart在 JIT(即时编译)模式下,速度与 JavaScript基本持平。但是 Dart支持 AOT,当以 AOT模式运行时,JavaScript便远远追不上了。速度的提升对高帧率下的视图数据计算很有帮助。其次,Flutter使用自己的渲染引擎来绘制UI,布局数据等由Dart语言直接控制,所以在布局过程中不需要像RN那样要在JavaScript和Native之间通信,这在一些滑动和拖动的场景下具有明显优势,因为在滑动和拖动过程往往都会引起布局发生变化,所以JavaScript需要和Native之间不停的同步布局信息,这和在浏览器中要JavaScript频繁操作DOM所带来的问题是相同的,都会带来比较可观的性能开销。
采用Dart语言开发(基本类似TypeScript)
这是一个很有意思,但也很有争议的问题,在了解Flutter为什么选择了 Dart而不是 JavaScript之前我们先来介绍两个概念:JIT和AOT。
目前,程序主要有两种运行方式:静态编译与动态解释。静态编译的程序在执行前全部被翻译为机器码,通常将这种类型称为AOT (Ahead of time)即 “提前编译”;而解释执行的则是一句一句边翻译边运行,通常将这种类型称为JIT(Just-in-time)即“即时编译”。AOT程序的典型代表是用C/C++开发的应用,它们必须在执行前编译成机器码,而JIT的代表则非常多,如JavaScript、python等,事实上,所有脚本语言都支持JIT模式。但需要注意的是JIT和AOT指的是程序运行方式,和编程语言并非强关联的,有些语言既可以以JIT方式运行也可以以AOT方式运行,如Java、Python,它们可以在第一次执行时编译成中间字节码、然后在之后执行时可以直接执行字节码,也许有人会说,中间字节码并非机器码,在程序执行时仍然需要动态将字节码转为机器码,是的,这没有错,不过通常我们区分是否为AOT的标准就是看代码在执行之前是否需要编译,只要需要编译,无论其编译产物是字节码还是机器码,都属于AOT。在此,读者不必纠结于概念,概念就是为了传达精神而发明的,只要读者能够理解其原理即可,得其神忘其形。
现在我们看看Flutter为什么选择Dart语言?笔者根据官方解释以及自己对Flutter的理解总结了以下几条(由于其它跨平台框架都将JavaScript作为其开发语言,所以主要将Dart和JavaScript做一个对比):
- 开发效率高 Dart运行时和编译器支持Flutter的两个关键特性的组合: 基于JIT的快速开发周期:Flutter在开发阶段采用,采用JIT模式,这样就避免了每次改动都要进行编译,极大的节省了开发时间; 基于AOT的发布包: Flutter在发布时可以通过AOT生成高效的ARM代码以保证应用性能。而JavaScript则不具有这个能力。
- 高性能 Flutter旨在提供流畅、高保真的的UI体验。为了实现这一点,Flutter中需要能够在每个动画帧中运行大量的代码。这意味着需要一种既能提供高性能的语言,而不会出现会丢帧的周期性暂停,而Dart支持AOT,在这一点上可以做的比JavaScript更好。
- 快速内存分配 Flutter框架使用函数式流,这使得它在很大程度上依赖于底层的内存分配器。因此,拥有一个能够有效地处理琐碎任务的内存分配器将显得十分重要,在缺乏此功能的语言中,Flutter将无法有效地工作。当然Chrome V8的JavaScript引擎在内存分配上也已经做的很好,事实上Dart开发团队的很多成员都是来自Chrome团队的,所以在内存分配上Dart并不能作为超越JavaScript的优势,而对于Flutter来说,它需要这样的特性,而Dart也正好满足而已。
- 类型安全 由于Dart是类型安全的语言,支持静态类型检测,所以可以在编译前发现一些类型的错误,并排除潜在问题,这一点对于前端开发者来说可能会更具有吸引力。与之不同的,JavaScript是一个弱类型语言,也因此前端社区出现了很多给JavaScript代码添加静态类型检测的扩展语言和工具,如:微软的TypeScript以及Facebook的Flow。相比之下,Dart本身就支持静态类型,这是它的一个重要优势。
- Dart团队就在你身边 看似不起眼,实则举足轻重。由于有Dart团队的积极投入,Flutter团队可以获得更多、更方便的支持,正如Flutter官网所述“我们正与Dart社区进行密切合作,以改进Dart在Flutter中的使用。例如,当我们最初采用Dart时,该语言并没有提供生成原生二进制文件的工具链(这对于实现可预测的高性能具有很大的帮助),但是现在它实现了,因为Dart团队专门为Flutter构建了它。同样,Dart VM之前已经针对吞吐量进行了优化,但团队现在正在优化VM的延迟时间,这对于Flutter的工作负载更为重要。”
总结
本节主要介绍了一下Flutter的特点,如果你感到有些点还不是很好理解,不用着急,随着日后对Flutter细节的了解,再回过头来看,相信你会有更深的体会。
二,flutter 开发的关键开发点
1.网络请求(dio 库)
参考: pub.flutter-io.cn/packages/di…
import 'dart:io';
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
import 'package:flutter/services.dart';
import 'package:xbb_crm/config/global_config.dart';
import 'package:xbb_crm/model/common_model.dart';
class DioUtils {
static DioUtils instance; //单例实例
Dio _dio;
BaseOptions options;
CancelToken cancelToken = new CancelToken();
static DioUtils getInstance() {
if (null == instance) instance = new DioUtils();
return instance;
}
DioUtils() {
//BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数
options = new BaseOptions(
method: "post",
//请求基地址,可以包含子路径
baseUrl: '${GlobalConfig.httpServerIp}:${GlobalConfig.httpPort}${GlobalConfig.apiPrefix}',
//连接服务器超时时间,单位是毫秒.
connectTimeout: 10000,
//响应流上前后两次接受到数据的间隔,单位为毫秒。
receiveTimeout: 10000,
//Http请求头.
headers: {
//do something
"version": "1.0.0",
"sign": 123
},
//请求的Content-Type,默认值是[ContentType.json]. 也可以用ContentType.parse("application/x-www-form-urlencoded")
contentType: ContentType.json,
//表示期望以那种格式(方式)接受响应数据。接受4种类型 `json`, `stream`, `plain`, `bytes`. 默认值是 `json`,
responseType: ResponseType.json,
);
// 构建实例
_dio = new Dio(options);
//Cookie管理
_dio.interceptors.add(CookieManager(CookieJar()));
//添加拦截器
_dio.interceptors.add(InterceptorsWrapper(
onRequest: _beforeRequestHandler,
onResponse: _afterResponse,
onError: (DioError e) {
print("错误之前");
// Do something with response error
return e; //continue
})
);
}
// 响应前处理
_beforeRequestHandler(RequestOptions options) {
var data = options.data?? {};
data['corpid'] = '1';
data['userId'] = '1';
data['platform'] = 'dingtalk';
data['frontDev'] = '1';
print("请求之前");
options.data = data;
return options;
}
// 响应后处理
_afterResponse(Response<dynamic> response) {
XbbResult xbbResult = XbbResult.fromJson(response.data);
int code = xbbResult.code;
// code 校验
if (code != 1) {
return _dio.reject(xbbResult);
} else {
}
return xbbResult;
}
get(url, {data, options, cancelToken}) async {
Response response;
try {
response = await _dio.get(url,
queryParameters: data,
options: options,
cancelToken: cancelToken
);
print('get success---------${response.data}');
} on DioError catch (e) {
print('get error---------$e');
formatError(e);
}
return response.data;
}
post(url, {data, options, cancelToken}) async {
Response<XbbResult> response;
try {
response = await _dio.post(url,
queryParameters: data,
options: options,
cancelToken: cancelToken
);
print('post success---------${response.data}');
return response.data;
} on DioError catch (e) {
print('post error---------$e');
formatError(e);
}
}
request(String path,{
dynamic data,
Map<String, dynamic> queryParameters,
CancelToken cancelToken,
Options options,
void Function(int, int) onSendProgress,
void Function(int, int) onReceiveProgress}) async {
Response response;
try {
Options _options = Options(method:"POST");
response = await _dio.request<XbbResult>(path,
data: data,
queryParameters: queryParameters,
cancelToken: cancelToken,
options: options??_options,
onSendProgress: onSendProgress,
onReceiveProgress: onReceiveProgress
);
print('post success---------${response.data}');
} on DioError catch (e) {
print('post error---------$e');
formatError(e);
throw e;
}
return response.data;
}
// 下载文件
downloadFile(urlPath, savePath) async {
Response response;
try {
response = await _dio.download(urlPath, savePath,
onReceiveProgress: (int count, int total) {
//进度
print("$count $total");
});
print('downloadFile success---------${response.data}');
} on DioError catch (e) {
print('downloadFile error---------$e');
formatError(e);
}
return response.data;
}
// error统一处理
void formatError(DioError e) {
if (e.type == DioErrorType.CONNECT_TIMEOUT) {
print("连接超时");
} else if (e.type == DioErrorType.SEND_TIMEOUT) {
print("请求超时");
} else if (e.type == DioErrorType.RECEIVE_TIMEOUT) {
print("响应超时");
} else if (e.type == DioErrorType.RESPONSE) {
print("出现异常");
} else if (e.type == DioErrorType.CANCEL) {
print("请求取消");
} else {
print("未知错误");
throw e;
}
}
/*
* 取消请求
*
* 同一个cancel token 可以用于多个请求,当一个cancel token取消时,所有使用该cancel token的请求都会被取消。
* 所以参数可选
*/
void cancelRequests(CancelToken token) {
token.cancel("cancelled");
}
}
api
import 'package:xbb_crm/model/common_model.dart';
import '../dio_util.dart';
Future<XbbResult> getHomeMenuList() async {
return await DioUtils.getInstance().request('/menu/home/list');
}
Future<XbbResult> test({String id}) async {
return await DioUtils.getInstance().request('/menu/home/list',{
data:{
id:id
}
});
}
2.状态管理(provider)谷歌官方维护
参考: pub.flutter-io.cn/packages/pr…
使用方式
2.1.创造数据模型
import 'package:flutter/material.dart';
class CurrentIndexProvide with ChangeNotifier{
int currentIndex = 0;
void changeCurrentIndex(int newIndex){
currentIndex = newIndex;
notifyListeners(); // 通知
}
}
2.2注册
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CurrentIndexProvide()),
]
)
2.3.使用
int currentIndex = Provider.of<CurrentIndexProvide>(context).currentIndex;
3.国际化(手机一般都是调用自己手机中系统设置的地区就能自动变换)
3.1条件依赖
flutter_localizations:
sdk: flutter
3.2注册委托
MaterialApp(
localizationsDelegates: [
// ... app-specific localization delegate[s] here
AppLocalizationsDelegate(),
GlobalCupertinoLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalEasyRefreshLocalizations.delegate,
],
)
3.3 自定义国际化比较多的内容demo中已经实现,后续再讲
4.路由管理(fluro)
4.1构建routes 管理类
import 'package:fluro/fluro.dart';
import 'package:flutter/material.dart';
import 'router_handler.dart';
class Routes{
static String root = '/';
static String testPage = '/test';
static void configureRoutes(Router router){
router.notFoundHandler = Handler(
handlerFunc: (BuildContext context,Map<String,List<String>> params){
print('error::: router 没有找到');
}
);
router.define(testPage, handler: testPageHandler);
}
}
4.2 分发管理handler
import 'package:fluro/fluro.dart';
import 'package:flutter/material.dart';
Handler testPageHandler = Handler(
handlerFunc: (BuildContext context,Map<String,List<String>> params){
String goodsId = params['id'].first;
return Text('测试路由');
}
);
//.....
4.3 挂载
import 'package:fluro/fluro.dart';
class Application{
static Router router;
}
final router = Router();
Routes.configureRoutes(router);
Application.router = router;
4.4 使用
Application.router.navigateTo(context, "/test");