开启你的flutter 应用

353 阅读8分钟

开启你的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做一个对比):

  1. 开发效率高 Dart运行时和编译器支持Flutter的两个关键特性的组合: 基于JIT的快速开发周期:Flutter在开发阶段采用,采用JIT模式,这样就避免了每次改动都要进行编译,极大的节省了开发时间; 基于AOT的发布包: Flutter在发布时可以通过AOT生成高效的ARM代码以保证应用性能。而JavaScript则不具有这个能力。
  2. 高性能 Flutter旨在提供流畅、高保真的的UI体验。为了实现这一点,Flutter中需要能够在每个动画帧中运行大量的代码。这意味着需要一种既能提供高性能的语言,而不会出现会丢帧的周期性暂停,而Dart支持AOT,在这一点上可以做的比JavaScript更好。
  3. 快速内存分配 Flutter框架使用函数式流,这使得它在很大程度上依赖于底层的内存分配器。因此,拥有一个能够有效地处理琐碎任务的内存分配器将显得十分重要,在缺乏此功能的语言中,Flutter将无法有效地工作。当然Chrome V8的JavaScript引擎在内存分配上也已经做的很好,事实上Dart开发团队的很多成员都是来自Chrome团队的,所以在内存分配上Dart并不能作为超越JavaScript的优势,而对于Flutter来说,它需要这样的特性,而Dart也正好满足而已。
  4. 类型安全 由于Dart是类型安全的语言,支持静态类型检测,所以可以在编译前发现一些类型的错误,并排除潜在问题,这一点对于前端开发者来说可能会更具有吸引力。与之不同的,JavaScript是一个弱类型语言,也因此前端社区出现了很多给JavaScript代码添加静态类型检测的扩展语言和工具,如:微软的TypeScript以及Facebook的Flow。相比之下,Dart本身就支持静态类型,这是它的一个重要优势。
  5. 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");