下一代移动端跨平台框架-Flutter大解密

下一代移动端跨平台框架-Flutter大解密

手机京东技术团队  2018-05-18

作者 熊文源

熊文源

 》京东Android客户端架构师

 》京东多端融合平台组核心工程师

 》参与Android端架构改造,多年Android系统底层设计经验,主导完成过系统核心App和Framework设计开发,以及性能内存优化.

  背景

Flutter :谷歌的移动端UI开源框架,2018年2月27日, Google发布了Flutter的第一个Beta版本。它是Google使用Dart语言开发的移动应用开发框架,使用Dart代码构建高性能、高保真的iOS和Android应用程序。Flutter的工具和库可以帮助开发者轻松地将自己的想法带到 iOS 和Android 设备上。如果没有任何移动开发经验,用Flutter来构建漂亮的移动应用是不错的选择。

现有移动平台 :苹果的iOS SDKs发布于2008年,谷歌的Android软件开发工具包发布于2009年,这两种sdk包是基于不同的编程语言的,分别是Objective-C和Java, 如下是大概的结构图:

所以带来的问题是大多数APP要针对不同系统提供不同版本的应用,因为系统提供的组件和语言不一样。随之而来的问题是如何只开发一套代码,两端都可以运行,也就有了如下的三代跨端平台的出现。

WebView

这是第一个跨平台的框架,基于JavaScript 和 WebView 例如 Cordova、PhoneGap、APPCan、Ionic等,应用程序可以编写成Html,并最终在移动平台的Webview中显示,并通过JavaScript interface和原生交互。

缺点:

●   加载性能慢,达不到原生UI的体验;

●  内存消耗比较大;

●   手势/动画和原生差距比较大;

●  相关原生功能支持有限。

响应式视图

原生体验的第二代跨平台框架,如ReactNative/Weex,生成虚拟DOM,并进一步对应生成原生的组件,让页面由原生组件组成,来到达原生的体验。JS代码和原生代码本身都是很快的,瓶颈经常发生在当我们视图从一边转向另一边时,所以构建高质量的应用程序时,我们必须将使用桥接的次数控制到最小。

缺点:

●   虚拟DOM和原生组件的沟通需要Bridge;

●   一些复杂的交互组件和动画性能上表现并达不到原生;

●  平台之间的兼容性问题。

Flutter

第三代跨平台框架,Flutter也提供响应式的视图,Flutter采用不同的方法避免由JavaScript桥接器引起的性能问题,即用名为Dart的程序语言来编译,Dart是用预编译的方式编译多个平台的原生代码,这允许Flutter直接与平台通信,同时使用Skia图形引擎来完成图形、文本、图像、动画等绘制,拥有自己独立的一套图形系统,不再依赖于原生。

缺点:

●  学习成本较高;

●  依赖库较大;

●  目前版本处于beta版。

  Flutter 大解密

框架架构

这种分层设计的目的是帮助开发者写更少的代码来实现功能。例如,Material层通过组合Widget层的控件来构建,而Widget层本身则依赖Rendering层来构建。上述分层为我们开发App提供了很多选择,首先可以使用Flutter提供的现有Widget来组合,或者创建自定义的Widget。

Widget

开发者通过实现一个build函数来定义一个Widget,这个build函数会返回一个Widget树,这棵Widget树更具体地描述了UI中的Widget层次。例如,一个ToolBar Widget的build函数可能会返回一个水平布局、一些文本和不同的按钮,框架会递归地调用每个Widget的build函数,直到所有的Widget都遍历完成,然后将这些Widget组合成一颗Widget树。

交互变化

如果Widget的特性基于用户交互或其他因素进行改变,则这个Widget是有状态的,Flutter提供两种类型的Widget, StatelessWidget 和 StatefulWidget,前者为状态不可变,后者可以通过setState()改变state来改变更新UI,开发者可以根据自己的实际情况使用 StatefulWidget:

StatelessWidget:

布局方式

在Android系统中,我们通常是使用xml来进行UI布局,同时可以通过addChild和removeChild添加或者移除视图。

但是在Flutter中,Widget 是不可变的,可以传入一个函数,该函数返回一个子Widget 给父 Widget。并在该函数中通过一个 bool 值来控制子 Widget 的创建。

事件监听

Flutter中有两种方式来处理touch:

一是直接传递一个处理事件的方法给Widget;

或者通过GestureDetector来实现事件监听与处理。

Tap(onTapDown/ onTapUp/ onTap/ onTapCancel)

Double tap(onDoubleTap)

Long press(onLongPress)

Vertical drag(onVerticalDragStart/ onVerticalDragUpdate/ onVerticalDragEnd)

Horizontal drag(onHorizontalDragStart/ onHorizontalDragUpdate/ onHorizontalDragEnd)

启动动画

在 Android 中我们能用View.animate()来启动一个动画,在Flutter中我们是把Widget包在Animation中。在 Flutter中,有AnimationController 和 Interpolator来控制动画的启动。

Canvas

在 Android 中我们能使用 Canvas 来画或者定制一些UI。在 Flutter中,有CustomPaint 和 CustomPainter 来提供一些算法完成绘画功能。

自定义Widget

在 Android 中我们通过继承实现 View/ViewGroup 等组件来完成一些自定义的组件,在 Flutter 中,不提供继承这种方式,而是通过一些小的组件组合。

页面切换

在 Android 中我们通过Intents的方式来启动页面或者service,在 Flutter中没有Intent, 我们就需要 route 和 Navigator 来管理页面了,Route 可以当作一个page 或者 activity,Navigator 是一个 Widget 用来管理 routes 的,能 push 或者 pop 页面。

生命周期

在 Android 中,页面的生命周期都在 activity 和 fragment 中控制,但是 Flutter只有一个 FlutterView,我们需要 hook WidgetsBinding 的observer并监听didChangeAppLifecycleState 的 event 消息。

参数传递

Flutter 页面切换是通过 Router 和 Navigator 完成的,同样可以通过 result back的方式获取你 push 的 router 返回的数据。

数据交互

Flutter 是支持原生页面和 Flutter 页面混合开发的,但是不支持原生组件在Flutter 中使用,原生端有 methodchannel 来支持 Flutter 对原生的一些API调用。

网络请求

在Andorid中,我们有很多类似 Okhttp 之类的网络使用,非常方便,在 Flutter中,我们使用 http package来简单的完成一个网络请求调用。

UI 组件

Flutter 内置有很多 Material Design 和 Cupertino(iOS 风格)的部件、丰富的手势 API、自然平滑的滑动动画

ListView/GridView/ViewPager/Card …

Row/Column/Stack等等的布局

import 'package:flutter/material.dart'

UI 页面

从 Android 端页面层次来看,主页面 MainActivity 是继承了 FlutterActivity,通过 FlutterActivityDelegate 控制一些初始化,包括 FlutterView,而整个 Flutter 展示的页面是绘制在 FlutterView 中的。

Flutter 跨平台的基础就是 UI 跨平台,UI 绘制不依赖系统组件, FlutterView 将 Dart 编写的界面封装成 Android 平台可以使用的组件,这个组件完成了 Android 应用界面的所有绘制工作,iOS 也有相同的一套机制,从而实现了不同平台共用一套绘制界面的代码。

FlutterView 继承了 SurfaceView,从 API 中可以看出 SurfaceView 属于 View 的子类,提供双缓存功能,它是专门为制作游戏而产生的,功能非常强大,最重要的是它支持 OpenGL ES 库,2D 和 3D 的效果,既然 Flutter 界面是直接继承于 SurfaceView 的,它的绘制过程就不再依赖于系统平台,解耦了系统控件的调用。

消息系统

采用 MethodChanel 实现7个消息模块管理:多语言、路由导航、按键消息、生命周期、系统信息、用户设置和平台插件,这几乎涵盖了不同平台所有差异化最大的功能,而且非常依赖原生系统。

MethodChanel:

下面我们以 keyevent 为例子简单说明事件传递机制:

FlutterView 类有两个处理按键事件的接口,一个是 onKeyUp(),一个是 onKeyDown(),分别对应按键松开和按下事件,两个方法流程一样,Flutter 再将平台的按键事件以消息方式拦截,具体步流程建议参考源码:

system_channels.dart

raw_keyboard.dart

raw_keyboard_listener.dart

其他功能

SharePreferences, 使用插件 Shared_Preferences

https://pub.dartlang.org/packages/shared_preferences

GPS,使用插件https://pub.dartlang.org/packages/location

Camera,使用插件https://pub.dartlang.org/packages/image_picker

Sqlite, 使用Sqlfile https://pub.dartlang.org/packages/sqflite

Notification, 使用Firebase_Messaging,

https://github.com/flutter/plugins/tree/master/packages/firebase_messaging

Flutter : ReactNative : 原生

UI 层次对比

可以看到 Flutter 架构里面只有一个 FlutterView 用来呈现所有的 Widget 的布局的,所以整个渲染过程是独立于原生 UI,而 ReactNative 和原生是比较类似的,会将 js 中的 UI 组件一一对应到原生组件。

Flutter UI层次

原生/ReactNative UI层次

内存/CPU/GPU对比

我们做了一个简单 demo,测试滑动一个有100条数据的 list,内存上 Flutter 和原生相差不大,比较稳定,ReactNative 内存波动比较大。CPU 方面 Flutter 比较原生高了接近两倍,相比 ReactNative 也明显要高。GPU 方面 Flutter 除了初始化时 GPU 比较高,应该是初始化 FlutterView,后面的拖动过程中看不到 GPU 的渲染,原因应该是独立的渲染体系,Android studio 提取不出来。所以整体而言原生的性能是最佳的。

Flutter性能

ReactNative性能

原生性能

APK大小对比

Flutter debug 包下,lib 库包含 86_64/x86/armeabi-v7a,接近 50M,考虑现在的手机支持 X86 比较少,在 release 包中只包含了 armeabi-v7a,lib 库大小压缩到了3.3M,整个APK压缩后将在7.5M左右,其他资源大部分是 Flutter 的代码,主要集中在 assets 下,而且有优化空间。

相比较于 ReactNative,本身的SDK库在3.5M左右,简单的页面对应的JS包大小在300K左右,占用空间能减少不少,但是 Flutter 是自己实现的一套 engine,完全独立于系统平台,占用比较大,其实也能理解。

Flutter APK 结构

ReactNative APK 结构

原生 APK 结构

性能对比

ReactNative 是基于前端思想开发的框架,对于原生复杂的 ListView/Gridview 等容器,没有办法直接实现,而且还有些复杂的 UI 依赖于 View 的嵌套叠加,在这种设计,相比较原生的设计,就会多出很多的 View,相比 native 实现来说会多很多 View 对象,造成了性能降低。也就是说复杂 UI 需求下,RN 对 UI 的表达效率远低于 native,造成性能低下。Flutter 是基于 Skia 自己实现的 UI 组件库,所以在布局和动画层次上有跟多的灵活性和性能优化空间,可以做到最优化。

开发语言上, js或dart都是一种声明式的写法,但js需要解释,dart是直接语言层面支持了node tree的书写,且对象创建成本低,可直接编译成native代码(AOT),VM效率更高,所以运行上dart效率高很多, 而且dart 是一种同时支持 JIT/AOT 编译的语言,JIT 开发模式时能快速编译生效,是Hot Reload体验的关键。

兼容性对比

Flutter 所提供的所有的 Widget/动画还有事件机制都是基于skia来实现的,与平台无关,所以有很高的跨平台的兼容性。但是独立的 UI 系统导致了,很多Android/iOS 对应的工具无法使用。

ReactNative 所有的组件都是依赖于原生的,而 Android/iOS 本身的组件和实现就不一样,导致了很多兼容性问题,不同平台要做适配和桥接,导致了很多的功能成本/开发成本和性能牺牲,比如动画/手势/数据容器等等。

适配性对比

Flutter 提供的 Widget 都是基于 skia 来实现和精心定制的,与具体平台没关,所以能保持很高的跨平台的兼容性,对未来平台的适应性上 Flutter 从更基础的层去抹平平台差异,Flutter 站在了更宽广、更可控的一个基础平台上去演变和发展。ReactNative 永远需要 follow native 开发的这套约束,桥接和抹平差异乃至应用层去适配的成本、面对具体场景去优化性能所需要的成本都是居高不下的。

Flutter引擎+Dart语言将很有可能成为 Google Fuchsia 系统主要的 UI 开发框架, 这将会将其跨平台的特性发挥的淋漓尽致。

  总结

Flutter 的优点:

●  热重载(Hot Reload),利用提供的IDE直接保存代码并重载,手机或者模拟器立马就可以看见效果,这一点调试起来很方便。

●  Widget的理念,对于Flutter来说,手机应用里的所有组件都是Widget,通过可组合的空间集合、丰富的动画库以及分层可扩展的架构实现了富有感染力的灵活界面设计。

●   借助GPU加速的渲染引擎以及高性能本地代码运行时以达到跨平台设备的高质量用户体验。

Flutter 的不足:

●  开发语言是基于Dart, 对开发者而言,增加了不少学习成本。

●  UI布局方面,层次不够明显,不如原生xml写法那么直接,复杂化了程序的可读性。

●  Flutter是一种新的框架,目前市面上应用和社区不太成熟,而且支持的库不如ReactNative及原生。

●  目前Dart代码会AOT编译到native,不像ReactNative,支持热更新起来会很难,但从API的结构设计上来看,后期应该很快会实现热更新。

●  不能支持原生组件在Flutter中显示,导致很多组件需要重新开发,不如ReactNative灵活。

总之从Flutter的设计理念来看,整体架构都是具有革命性的,相比于其他跨平台实现了真正意义的跨平台,各平台体验一致,而且让用户体验达到了最优,各种UI库和组件也在不断的增加,各种生态系统和社区在不断的完善,对于以后新的操作系统适配性会更强,如Fuchsia系统,非常值得大家了解和学习,相信不久的将来,会慢慢成熟起来,成为主流开发语言。

我们团队也在持续保持更新,未来会和JDReact引擎一起成为京东多端融合平台的双引擎。

 参考资料:https://flutter.io/docs/ 

长按识别图中二维码立即 关注

投诉

代码交流 2021