2023-03-17

跨平台技术方案选择Flutter总结

1、移动端的跨端技术选型

移动端的跨端技术方案,我们关注的点主要集中在这几个方面

  • 研发效率:降低开发成本,最大的代码复用,可实现版本的快速迭代。
  • 多端一致性:好的产品在多端UI上的整体风格往往都是趋近统一的,采用原生各自独立完成功能开发后,还有多端差异的适配工作量。
  • 性能体验:这是选择跨端技术方案的核心指标,一般来说,跨端技术方案拥有多重优势,但在性能方面相比原生差些。

2、跨平台技术划分与演进

  1. Web技术:采用WebView技术绘制界面的Hybrid混合开发技术,主要是通过JS Bridge将系统部分能力暴露给JS调用,缺点是功能受限、性能体验较差,而且不适合交互复杂的场景。
  2. 原生渲染:使用JavaScript作为编程语言,通过中间层转化为原生控件来渲染UI界面,相比WebView性能体验要更好,但相比原生还是有些差距,而且处理平台差异性比较耗费人力。
  3. 自渲染技术:自带渲染引擎,尽可能减少不同平台间的差异性,而且拥有媲美原生的高性能体验,因此Flutter出现之后就有着很高的关注度。

3、Flutter的优缺点

  1. 优点
    • 基于Dart语言编写代码,开发体验更接近客户端,一套代码适用于多个平台(Android、iOS、Web),以及高效的Hot Reload能快速辅助调试。
    • 实现UI像素级的控制,Flutter渲染引擎依靠跨平台Skia图形库来实现,仅依赖系统图形绘制相关的接口,可最大程度保证不同平台的体验一致性。
    • 渲染性能优于现有的各种跨平台框架,可媲美原生,Dart代码执行的效率比JS高,通过AOT编译成平台原生代码,渲染采用自渲染skia方案,既不需要JS Bridge桥接,也不需要ART虚拟机参与。
  2. 缺点
    • 对于复杂的场景脱离不开原生,目前来说更多的是一种UI框架,所以进行Flutter开发需要具备原生的基础开发能力。
    • 总体上生态还不够健全,另外就是还有包体积的问题。

4、Flutter 技术架构

flutterFrame.jpg

Flutter的技术结构跟Android原生技术架构类似,分为四层,从上往下依次是Dart APP,Dart Framework,C++ Engine和Platform。

  • Flutter Framework层:用Dart编写,封装整个Flutter架构的核心功能,包括Widget、动画、绘制和手势等功能,另外还有Material(Android)和Cupertino(iOS)的UI风格界面。
  • Flutter Engine层:用C++编写,实现了Flutter的核心库,当需要绘制一帧内容时,引擎将负责对需要合成的场景进行栅格化。它提供了 Flutter API 的底层实现,包括2D图形渲染库Skia、文本布局、文件及网络 IO、辅助功能支持、插件架构和 Dart 运行环境及编译环境的工具链,另外还有事件通知和平台通信通道等。

5、Flutter项目目录

  • android:Android 平台相关代码
  • ios:iOS 平台相关代码
  • lib:Flutter 相关代码,我们主要编写的代码就在这个文件夹
  • test:用于存放测试代码
  • pubspec.yaml:配置文件,一般存放一些第三方库的依赖,本地资源目录,依赖的SDK版本等

每一个 flutter 项目的 lib 目录里面都有一个main.dart文件, 这个文件就是 flutter 的入口文件,在main.dart里有个runApp 方法,这个是flutter 的入口方法。MyApp 是自定义的一个组件。

void main() { 
    runApp(MyApp());
}

6、Widget概述

Widget是Flutter应用程序的基石,在Flutter里可以说是一切皆Widget,Widget可以是一个按钮,一种字体或者一个布局属性等,常见的Widget子类为StatelessWidget和StatefulWidget。

  • StatelessWidget:内部没有保存状态,UI界面创建后不会发生改变
  • StatefulWidget:内部有保存状态,当状态发生改变,调用setState()方法会触发StatefulWidget的UI发生更新,对于自定义继承自StatefulWidget的子类,必须要重写createState()方法。

7、Flutter渲染原理

需要了解Flutter的渲染原理,那么首先需要知道Flutter的三棵树

  • Widget Tree:负责创建Element,为Element描述需要的配置,决定Element是否需要更新。Flutter Framework通过差分算法比对Widget树前后的变化,决定Element的State是否改变。当重建Widget树后并未发生改变, 则Element不会触发重绘。
  • Element Tree:Element表示Widget配置树的特定位置的一个实例,同时持有Widget和RenderObject,负责管理Widget配置和RenderObject渲染。Element状态由Flutter Framework管理, 开发人员只需更改Widget即可。
  • RenderObject Tree:RenderObject表示渲染树的一个对象,负责真正的渲染工作,比如测量大小、布局位置、绘制等都由RenderObject来完成。

Dart的UI采用Widget来实现,最终转换为RenderObject,界面的渲染过程:

  • 当应用创建和展示场景的时候,UI线程完成布局、绘制操作,生成Layer Tree,一个包含设备无关的渲染命令的轻量对象,然后将Layer Tree发送到GPU线程。
  • GPU线程执行合成并光栅化后交给GPU来处理,GPU线程拿到Layer Tree之后,通过skia向GPU硬件绘制一帧的数据,GPU将帧信息保存到FrameBuffer里面,然后视频控制器会根据VSync信号从FrameBuffer取帧数据传递给显示器,从而显示出最终的画面。

8、Flutter Channel渠道

Flutter channels:
  master
  dev
  beta
* stable
  • Stable:稳定版。
  • Beta:公测版,比较稳定,每隔一段时间选取的最好的Dev版本。
  • Dev:经过google测试后的,最新版本,包含新功能。
  • Master:最新的代码主分支。

9、Flutter的构建模式

Flutter 支持三种模式编译 app:debug、profile和release,使用哪种模式构建主要取决于处于哪个开发阶段

  • debug构建模式:开发过程中,需要使用热重载功能、断点的扩展服务的时候
  • profile构建模式:profile和release模式的编译和运行基本相同,但它的一些服务扩展是启用的,可以连接到调试工具中,一般用于分析性能的时候,比如分析渲染性能和内存等。
  • release构建模式:三者中最大的优化以及最小的占用空间,调试是禁用的,一般发布应用的时候使用release构建。

10、Flutter中的路由

在 Flutter中通过Navigator组件管理路由导航。并提供了管理堆栈的方法,如:Navigator.push 和 Navigator.pop,Flutter 中提供了两种配置路由跳转的方式:

  • 基本路由
Navigator.of(context).push(
        MaterialPageRoute(
            builder: (BuildContext context){
                // 跳转的路由页面
              return BlackListWidget(params: params);
            }
    ));
  • 命名路由
return MaterialApp(
  onGenerateRoute: onGenerateRoute,
  theme: ThemeData(
    scaffoldBackgroundColor: DwColor.lightF8Gray,
  ),
);

/// 全局"动态"路由表<br/>
/// 支持以下两种方式传参数:
/// * [settings].name 按标准URL查询参数形式传递参数,如:/your_route_name?param1=value1&param2=value2
/// * [settings].arguments 按[Map]<String, dynamic>格式传递参数,如:{'param1':'value1', 'param2':'value2'}
final RouteFactory onGenerateRoute = (RouteSettings settings) {
  LogUtil.i('onGenerateRoute, ${settings.name}, ${settings.arguments}');
  Map<String, dynamic>? params;
  if (settings.arguments == null) {
    params = {};
  } else {
    var arguments = settings.arguments;
    params = Map<String, dynamic>.from(arguments as Map);
  }
  late String finalRouteName;
  parseUri(settings.name, (routeName, args) {
    finalRouteName = routeName;
    if (finalRouteName != settings.name) {
      //如果从settings.name解释回来的路径变了,意味着settings.name有携带参数,则以解释回来的参数为准,以免路径与参数不匹配
      params = args;
    }
  });
  params ??= {};
  LogUtil.i('onGenerateRoute rst: finalRouteName=$finalRouteName, params=$params');
  //获取页面Widget的同时对 params补充页面事件id(部分页面分根据现有的params动态给定页面id)
  Widget home = _genPageWidgetByRoute(finalRouteName, params!);
  return MaterialPageRoute(
    builder: (context) => home,
    // 重新生成RouteSettings,一方面路由名有可能已经因去除了链接参数而发生了变化
    settings: RouteSettings(name: finalRouteName, arguments: params),
  );
};

///根据路径及参数信息返回对应的页面
Widget _genPageWidgetByRoute(String path, Map<String, dynamic> params) {
  Widget home;
  switch (path) {
    case RouteName.preWarm:
      home = PreWarmPage();
      break;
    case RouteName.voiceBollFloatSetting:
      home = VoiceBollFloatSettingWidget(params: params);
      break;
    case RouteName.blackList:
      home = BlackListWidget(params: params);
      break;
    default:
      home = VoiceBollFloatSettingWidget(params: params);
      break;
  }
  return home;
}
// 跳转的路由页面
Navigator.pushNamed(context, RouteName.blackList, arguments:params);

11、Flutter混合开发代码依赖配置

gradle.properties

#是否为Flutter开发人员
isFlutterDev=true
#Flutter路径
flutterModulePath=../gamevoice_flutter
flutter.hostAppProjectName = gamevoice_client
settings.gradle

if (isFlutterDev.toBoolean()) {
    //依赖本地Flutter模块
    setBinding(new Binding([gradle: this]))
    evaluate(new File(flutterModulePath + '/.android/include_flutter.groovy'))
    include ':flutter_project'
    project(':flutter_project').projectDir = new File(flutterModulePath)
}
build.gradle

if (!isFlutterDev.toBoolean()) {
    //关于Flutter模块aar依赖的配置
    implementation "com.gamevoice.gameshared.flutter:flutter:1.0.0-SNAPSHOT"
} else {
    //关于Flutter模块源码依赖的配置
    implementation project(':flutter')
}

12、Flutter引擎启动,跟native衔接

比如在Android中,FlutterApplication和FlutterActivity的onCreate()方法是连接Native和Flutter的枢纽。

  • FlutterApplication的onCreate方法主要完成初始化配置、加载引擎libflutter.so、注册JNI方法

  • FlutterActivity的onCreate方法,通过调用FlutterJNI来初始化引擎Engine、Dart虚拟机等对象。再经过层层处理最终调用main.dart中main()方法,执行runApp(Widget app)来处理整个Dart业务代码。

/**
 * 提前预热的FlutterEngine,用于加速原生跳转Flutter页面
 */
public void preWarmFlutterEngine() {
    MLog.info(TAG, "开始预热FlutterEngine");
    FlutterEngine preWarmedEngine = FlutterEngineCache.getInstance().get(FlutterBridgeActivity.ENGINE_CACHE_CAR_MODULE);
    //不确定之前预热的状态,当触发预热程序时,把之前的销毁
    if (preWarmedEngine != null) {
        try {
            preWarmedEngine.destroy();
        } catch (Exception e) {
            MLog.error(TAG, e.getMessage());
        }
    }

    preWarmedEngine = new FlutterEngine(YYMobileApp.gContext);
    // preWarmedEngine.getNavigationChannel().setInitialRoute(FlutterApiConstants.FLUTTER_ROUTE_PRE_WARM);
    preWarmedEngine.getDartExecutor().executeDartEntrypoint(
        DartExecutor.DartEntrypoint.createDefault()
    );
    // Cache the FlutterEngine to be used by FlutterActivity.
    FlutterEngineCache
        .getInstance()
        .put(FlutterBridgeActivity.ENGINE_CACHE_CAR_MODULE, preWarmedEngine);
    initWithFlutterEngine(preWarmedEngine);
}

13、platform channel

Flutter需要使用Camera等功能时需要原生平台的支持,Flutter通过提供platform channel来实现与原生平台之间的通信,这个过程的消息与响应是异步执行,不会阻塞用户界面,Flutter引擎框架已帮我们完成桥接的通道,主要有两种

  • Method Channel:可以简单理解为Flutter需要调用原生的某些功能。
  • Event Channel:可以简单理解为原生模块的一些改变需要通知Flutter模块做出相应处理。

14、总结:

从2018年12月Google 宣布 Flutter 1.0正式发布到现在不到3年的时间里,Flutter发展还是蛮快的,目前来说Flutter肯定是不够尽善尽美的,优点和缺点也是较为明显的,但是Flutter的想象空间还是蛮大的,方案的上限也比较高,如果要选一个跨平台方案的话,Flutter我觉得会是首选。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容

  • 原创:有趣知识点摸索型文章创作不易,请珍惜,之后会持续更新,不断完善个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔...
    时光啊混蛋_97boy阅读 2,159评论 0 1
  • 一、flutter与原生通信,三种通道的区别 1.1 MethodChannel Flutter与Native端相...
    耿宏达阅读 1,303评论 0 2
  • Flutter 1 Flutter是什么? Flutter是谷歌的移动UI框架,可以快速在iOS和Andro...
    江河_ios阅读 1,300评论 0 3
  • 前言 本文总结Flutter架构概览,包含其设计层面的核心原则以及概念。 Flutter是一个跨平台的UI工具集,...
    Beason阅读 1,460评论 0 1
  • 目录 一、Flutter 为何使用Dart开发语言二、Flutter的UI系统1.特点2.架构简介2.1 Flut...
    十拿九稳啦阅读 3,632评论 3 28