30

Flutter在PLUS业务中的探索和实践

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzUyMDAxMjQ3Ng%3D%3D&%3Bmid=2247492384&%3Bidx=1&%3Bsn=7cf26c1d231dbabdf6746e207c218709
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

2016年京东为向核心客户提供更优质的购物体验,特别推出京东PLUS会员,旨在全方位提升和丰富用户网购体验,目前京东PLUS会员已成为电商行业付费人数最多的会员体系。作为PLUS的前端开发,我们思考最多的就是如何让页面更快更好的呈现在用户面前,如何用技术为用户提供最好的购物体验。

为什么会开始尝试Flutter

目前PLUS业务前端技术栈选择是React,虽然我们在React性能优化上做了很多工作,但是由于WebView本身的局限性,和原生相比H5页面的渲染效率和 JavaScript 的执行能力都差一些,所以页面白屏时间长,加载速度和用户体验都比不上原生,同时国内各大厂商机型适配工作浪费了太多开发者的精力。

然后我们尝试了Js2Native的解决方案——React Native(后面简称RN),RN相对于WebView来说,性能和渲染效率都得到了不小的提升,但由于 RN 代码是通过 JS 桥接的方式转换为原生的控件,所以受各个系统间的差异影响非常大,虽然可以开发一套代码,但对各个平台的适配却非常的繁琐和麻烦。

2018 年 12 月初,Google 正式发布了开源跨平台 UI 框架 Flutter 1.0 Release 版本,引起了行业巨大的关注,我们也积极调研在PLUS业务中使用Flutter的可能。2019年Google 开发者大会上,Flutter 1.9正式发布,官方宣布了 Flutter Web 合并到了 Flutter SDK 中,这意味着Flutter Web也越来越成熟。于是我们开始了Flutter在PLUS业务中的尝试。

Flutter比较吸引我们的一些原因:

1. Flutter有自己的渲染引擎Skia,页面渲染不再受限于Native,页面适配工作将减少。

2. Flutter支持 JIT 和 AOT 两种编译方式。JIT 编译方式使开发者在开发阶段使用热重载(HotReload),这样在开发时可以省去构建的过程,提高开发效率。而 Release包采用 AOT 的编译方式,使执行效率非常高,让 Release 版本发挥更好的性能。

3. Flutter Web越来越成熟。作为前端团队无法接受一个业务用Flutter开发一次,再用H5开发一次,Flutter Web让我们没有这个顾虑。

入坑Flutter需要探索和解决的问题

PLUS业务的用户量比较大,任何新技术的尝试都需要做足调研工作,我们主要从以下几个方面做的调研:

  • Flutter页面重写的成本

  • Flutter如何与H5或者Native进行互交

  • Flutter页面如何调试

  • Flutter的性能监控和异常上报如何实现

  • Flutter的降级如何实现

  • Flutter如何集成到现有的客户端中,是否可以随时发版

01

FLUTTER页面重写的成本

Flutter重写H5页面主要有两个成本——学习成本和开发成本。

学习成本主要就是Dart语言和Flutter Widget框架,作为前端开发如果习惯了原生JS面向过程的设计思维,那么学习使用Dart语言就需要有一个面向对象思维的转变,不过这对程序员来说基本不是事,毕竟我们TS写的也很多了。最大的学习成本在于熟悉Flutter Widget框架,Flutter应用里一切皆Widget,Flutter提供了大量的控件,每个控件又包含大量的属性,这可方便开发者通过组合嵌套的方式构建复杂界面,但是要完全搞明白一个控件也需要花更多的时间。Flutter Widget框架具体查看Widgets Catalog:https://flutter.dev/docs/development/ui/widgets。

开发成本成本主要在于Flutter重写原H5页和客户端的调试。Flutter重写原H5页时间基本可控,虽然刚开始写Flutter时会吐槽控件的嵌套层级太深,但是这个习惯就好了,但是和客户端调试时间就不受前端控制,发现问题只能乖乖等客户端排查解决。

0 2

FLUTTER如何与H5或者NATIVE进行互交

Flutter与H5或者Native进行互交主要包含以下几个方面:H5页面唤起Flutter页面、Flutter页面唤起H5页面、Flutter调用Native功能。

H5页面唤起Flutter页面目前的解决方案是用Scheme协议,Flutter页面唤起H5页面、Flutter调用Native功能都是用桥接的方法直接调用Native能力完成。Flutter提供了三种和Native通信的方式:

  • BasicMessageChannel 实现 Flutter 与 原生(Android 、iOS)双向通信

  • MethodChannel 实现 Flutter 与 原生原生(Android 、iOS)双向通信

  • EventChannel 实现 原生原生(Android 、iOS)向Flutter 发送消息

这个就不在这里讲了,有兴趣的同学可以自己去了解。

0 3

FLUTTER页面如何调试

对于前端同学来说,我们有很多调试H5页面的方法,比如 Chrome Dev Tools,我们可以用console.log打印变量,有很多栈信息让你来判断错误和debug,当然Flutter也提供了很多调试页面的方法。

Flutter提供的debugPrint 和 print将消息打印至控制台,不同的是debugPrint 提供了定制打印的能力,也就是说,我们可以向 debugPrint 函数,赋值一个函数声明来自定义打印行为。比如我们将生产环境的 debugPrint 定义为空实现,将开发环境的 debugPrint 定义为同步打印数据,就可以满足开发环境下打日志的需求,也可以保证生产环境下应用的执行信息不会被意外打印,如下所示:

// 将debugPrint指定为空的执行体, 所以它什么也不做

debugPrint = (String message, {int wrapWidth}) {};

// 将debugPrint指定为同步打印数据

debugPrint = (String message, {int wrapWidth}) => debugPrintSynchronously(message, wrapWidth: wrapWidth);

Flutter同时也支持断点调试,界面调试,错误堆栈信息输出。以VSCode为例,点击VSCode的断点调试按钮即可开启调试,调试界面如下:

QzYzyai.jpg!web

同时可以点调试工具栏最右边的按钮Open DevTools或者使用快捷键【command+shift+p】打开VSCode工具栏,然后输入Open DevTools打开调试窗口,可以看到Flutter Inspector 所展示的 Widget 树结构,与代码中实现的 Widget 层次是一一对应,如下图所示:

UvYVNve.jpg!web

在此界面除了进行布局调试外,还可以使用Flutter Inspector进行布局调优,同时也可以看Flutter页面的各种性能,内存占用情况等。

Flutter中,还有一个很有用的界面调试工具,那就是Debug Painting,即可以给界面绘制布局边界。在VSCode中,开启该绘制功能十分简单,只需要在Flutter App调试的过程中,打开命令面板(cmd+shift+p),输入Flutter Toggle Debug Painting即可启动,它可以很清晰的展示出每个元素的布局边界,迅速帮开发者找出布局出问题的地方,开启界面调试后APP界面展示效果如下图所示:

iEjUJ3e.jpg!web

0 4

FLUTTER的降级如何实现

降级是业务开发中不可缺少的一环,特别是对于Flutter这种刚刚开始使用的新技术,如果线上出现任何问题,都需要第一时间将Flutter页降级到可以正常使用的H5页或者兜底页,保证用户的使用体验。

Flutter页面有两种降级策略,一种是在Flutter页面入口处拉取配置接口,一种是在Flutter容器层拉取配置列表。在容器层做降级更合理一些,目前JDFlutter容器也已经支持各业务在容器层配置降级策略。PLUS业务有一套自己的降级,切量配置系统,可以运用在H5,RN,Flutter业务上,如下图所示:

JnArm2f.jpg!web

此系统可以设置页面的切量、配置白名单,头部内容里是配置的是首选的页面打开方式,以Flutter为例,头部内容配置打开Flutter页面的openapp协议时线上入口可以打开Flutter页面,配置H5链接时线上入口即可打开H5页面,达到降级的目的。

0 5

FLUTTER如何集成到现有的客户端中,是否可以随时发版

H5最大的优势就在于它在客户端集成简单,随时发版,对业务方需求特别友好。PLUS作为正在快速成长的业务,需求量相当大,而且相当多的紧急需求,使用Flutter就对业务在客户端集成和发版速度有了较高的要求。

目前JDFlutter的热更新机制还不完善,Flutter业务发版需要走客户端的发版机制,等待JDFlutter热更新机制成熟后,Flutter业务可以和H5一样随时发版,同时PLUS团队也在探索Flutter界面的动态更新,已经有了不错的方案。

Flutter业务在客户端的集成比较简单,目前因为Flutter自动集成客户端的平台没有做好,需要手动集成客户端。Flutter业务开发完成后使用编译命令:Android为 flutter build aar ,iOS为flutter build ios-framework,将最终生成产物交给客户端同学帮忙集成。整套流程如下图所示:

N3imYff.jpg!web

Flutter在PLUS业务中的实践

Flutter在PLUS业务中分三步走:引入期 → 规模化 → 一体化,我们目前还处于引入期到规模化的过渡阶段。我们是京东最早实践H5业务改版Flutter的团队,实践过程中踩了无数的坑,也积累了很多宝贵经验,在客户端同学的帮助下,我们已打通Flutter重构H5页面的整套流程。

01

PLUS FLUTTER工程化架构

在Flutter的实践过程中发现,前端在Flutter底层能做的事比较少,前端的优势在于提高工程化效率,PLUS Flutter工程化架构设计如下:

yqMbMvN.jpg!web

这套工程化体系将在未来很大程度提升我们Flutter业务的开发效率,这里重点介绍一下我们设计的Flutter MVVM开发架构。

02

PLUS FLUTTER业务开发架构

刚开始做Flutter业务开发时,大家可能写的很随意,什么东西都写一起,也不去考虑解耦等问题。但是实际生产开发是不能这样做,否则项目稍大就无法维护。

MVVM开发架构可以很好的解决这个问题。MVVM有三个角色需要扮演View - ViewModel - Model,View是界面,Model是界面数据模型,ViewModel是View 和Model的连接层,它实现了数据的双向绑定,Model中数据的更新会及时的反馈到View上,View上的更新也会及时的反馈给Model。我们的MVVM架构是以Redux为纽带建立的,架构图设计如下:

J3iEza3.jpg!web

Redux是一个优雅且实用的状态管理框架,前端同学可能对redux很熟悉,客户端同学可能还没用过,这里大概介绍一下Redux的几个概念:

  • Store,是保存数据的地方。整个应用只能有一个 Store 。Store 有十分重要的方法 dispatch(action) 来发送 Action。

  • State,是某个时间点的数据快照, 一个 State 对应一个 View。只要 State 相同,View 就相同。

  • Action,是 View 发出的通知,通过 Reducer 使 State 发生变化。

  • Reducer,是一个纯函数,接受 Action 和当前 State 作为参数,返回一个新的 State。

  • Middleware,中间件,它的操作在发出 Action 和执行 Reducer 这两步之间发生,用于增加额外功能,如处理异步操作或者打印日志功能等。

Redux 的工作流程如图所示,先用户发出 Action ,Store自动给 Middleware 进行处理,再传递给 Reducer , Reducer 会返回新的 State ,通过 Store 触发重新渲染 View。

uAzEzaJ.jpg!web

Flutter使用redux时需要引入redux库和flutter_redux库,flutter_redux可以看做是利用了 Stream 特性的 scope_model 升级版,通过 redux 设计模式来完成解耦和拓展。因为篇幅关系这里不讲flutter_redux具体实现原理,如有需要可以再写文章做详细介绍。这里介绍两个flutter_redux 中的概念:

  • StoreProvider ,一个基础 Widget ,一般在应用的入口处作为父布局使得,用于传递 Store。

  • StoreConnector ,一个可以获取 Store 的 Widget ,作用是响应 Store 发出的状态改变事件来重建 UI。

03

FLUTTER MVVM架构在PLUS业务中的实践

首先看一下整个PLUS业务开发工程的目录结构:

uAJ3eim.jpg!web

下面看一下整个工程如何建立和运行:

1.引入所需的第三方库

在pubspec.yaml文件里引入需要用到的第三方库,并执行flutter packages get进行安装:

dependencies:

redux: ^4.0.0

flutter_redux: ^0.6.0

json_annotation: ^2.0.0

dev_dependencies:

build_runner: ^1.1.3

json_serializable: ^2.0.0

这里redux、flutter_redux是flutter redux需要引入的库,json_annotation、json_serializable、build_runner是flutter JSON自动反序列化需要引入的库。

2.创建Model

每个接口请求都需要model类来对接口返回的数据进行的反序列化处理。京东APP Flutter网络库向服务器请求数据后,服务器会返回一段Json字符串,如果要想更加灵活的使用数据就需要把Json字符串转化成对象。由于Flutter只提供了Json to Map,所以我们需要对Json进行反序列化处理。目前Flutter反序列化使用比较多的是json_serializable。

rquAvuv.jpg!web

首先给Modle类添加注解@JsonSerializable(),并引入与本Model类相同文件名的.g.dart文件,然后执行flutter pub run build_runner build命令就可以自动生成Json序列化和反序列化的方法。

fIrauaF.jpg!web

3.创建store

Store 全局只有一个,这里封装一个创建 Store 的方法。创建Store时,传入 Reducer ,初始化的 State 和 Middleware 。

7vEZjq2.jpg!web

4.创建state

State 创建的时候可以创建一个 AppState,作为整个应用的 state,除此之外,根据不同的页面可以有不同的 State,比如我的页面有一个Notice模块 ,就可以有一个Notice模块的状态 noticeState ,并且 noticeState 会放在 AppState 中作为成员变量进行管理。

NZ36fqa.jpg!web

EfYN7bq.jpg!web

5.创建Action

Action 是 Widget 发出的消息。对于Notice模块,初始化的时候需求请求接口渲染界面,所以组件初始化的时候会发一个请求接口的Action,一个接口请求建议注册三个Action,分别是接口请求Action、接口请求成功Action、接口请求失败Action。

nE3uuyY.jpg!web

6.创建Middleware

Middleware是中间件,通过中间件机制,可以解耦业务逻辑,更加灵活的处理异步的操作。它会先于Reducer处理Action,然后将处理结果交给Reducer。页面每个模块都可以有自己的中间件,达到页面各模块解耦的目的,同时也可以引入第三方中间件,比如日志打印、异步处理等中间件。

FZb2Qvb.jpg!web

3Qfeeun.jpg!web           

7.创建Reducer

Reducer 是一个带 State 和 Action 两个参数的纯函数,主要的作用就是返回最新的State。每个Action都有与之对应的Reducer,每一个Middleware都有与之对应的Reducer,Action不一定都有对应Middleware(PS:Action可以不通过中间件直接到Reducer)。和Middleware一样,页面每个模块都可以有自己的Reducer。

aIFz6ff.jpg!web

qm6BFrf.jpg!web     

8.创建ViewModel

这里的viewmodel只是负责ViewModel层数据分发和事件传递的功能,每个view都有一个自己的viewmodel来管理view内的事件和数据。

7zQZnuA.jpg!web

9.创建View

准备工作做完就可以布局页面了,在项目入口要做的工作是创建 Store,和使用 StoreProider 作为根布局,这样整个Store就和页面关联到一起。

7rYbm2y.jpg!web

然后将页面各个模块拆出来建立单独的view,给其建立对应的State、Action、Middleware、Reducer、ViewModel,以此类推,一个页面,一个工程可以拆分成很多单独的模块来完成,各模块独立开发互不影响。下面是Notice模块的view,里面不涉及任何逻辑代码。

7FbIRrv.jpg!web

这里说一下StoreConnector控件,StoreConnector 中有两个标记为@required 的参数,一个是 converter , converter 用于将 store 中的 state 转化为 viewModel,另一个是 builder,builder 的作用是将 viewModel 进一步转化为 UI 布局。同时StoreConnector里也有一些控件生命周期的方法,比Flutter框架里更全,例如onInitialBuild()、onWillChange()、onDidChange()等,可以根据需要灵活使用。

另外,Flutter页面适配可以考虑flutter_rem,按照前端rem原理实现,可以帮助开发者精确还原设计稿。

10.整套工程如何运行

Flutter Redux在Flutter的工作流程如下图所示:

MfYfIfM.jpg!web

View发出dispatch action,这个Action可以是接口请求或者事件处理等类型,首先Middleware先响应到这个Action并在这里做各种处理,处理完成后将结果反馈到Reducer,Reducer根据反馈的结果更新State,ViewModel将最新的State分发到各个View,View更新完成。

04

FLUTTER WEB实践

可能有客户端同学会奇怪为什么需要Flutter Web,作为前端团队需要支持的端比较多,在人力资源有限的情况下,我们不可能一个业务用Flutter开发一遍,再用H5开发一遍,所以Flutter Web对前端团队来说特别重要。同时Flutter Web天生还具备容灾降级的使命,如果线上的Flutter页面出现异常,可以直接降级到Flutter Web页,保证用户的正常使用。

Flutter页面开发完成后运行flutter run -d chrome命令就可以在浏览器里看到相应的Flutter Web页面,使用flutter build web命令可以生成release版Flutter Web页面。下面是PLUS省钱月卡的错误兜底页转web后的效果:

RVf6Nru.jpg!web

总体还原度接近100%,页面有一部分Canvas 渲染,有一部分用 DOM 填充,性能还是不容乐观。总体实践来看Flutter Web需要解决的问题还有很多:

zEVV3mF.jpg!web

要在业务使用Flutter Web首先需要保证Flutter插件和Flutter组件支持Flutter Web,目前大多数Flutter插件不支持Flutter Web,希望京东开发者以后写Flutter插件时可以考虑兼容Flutter Web。

Dart 当年天然支持在 Chrome 中使用,并且长期以来一直支持转换为 JavaScript。因此,可以遇见的未来,随着 Flutter 的发展,Flutter Web支持也会越来越好。

05

FLUTTER页面和H5页面性能对比

通过选取十天H5和Flutter页面的最终渲染时长对比,可以明显的看出Flutter页面相比H5有很大提升的,Flutter是一个不错的选择。

7RfaArY.jpg!web

06

PLUS前端GIT提交信息规范

最后推广一下PLUS前端的Git提交信息规范。

提交的格式为:<type>: <subject>,type用于说明 commit 的类别,subject是 commit 原因的简短描述,type可以使用如下类别:

  • init:项目初始化

  • feat:增加新功能

  • fix:修补bug

  • doc:修改文档

  • change:不影响代码功能的变动

  • refactor:对某个功能进行了重构

  • test:增加测试

  • chore:构建过程或辅助工具的变动

比如我在Flutter工程里新增了一个页面,commit的格式就是 feat:新增XX页面

总结

PLUS团队为了给用户提供更好的使用体验,在PLUS业务中探索和实践Flutter。因为篇幅的关系,很多基础的东西并没有讲到,比如如何搭建Flutter环境、申请jdFlutter开发工程、flutter在客户端的集成、踩过的坑等等没有说到。前端业务改造Flutter的踩的坑太多,主要是和客户端互交方面,估计又能写一篇文章了,不过踩过的坑都已填平,大家应该不会遇到了。

文章主要分享了一下PLUS Flutter业务开发架构,此架构也可以应用在客户端和RN业务开发上,可以提高合作开发业务的效率。同时也讲了一下我们对Flutter Web研究的最新进展,虽然目前还不太乐观,但是我们会持续完善PLUS Flutter Web的生态,同时我们也在研究Flutter+Serverless的解决方案,希望能和一些正在研究 Flutter 的同学进行交流和学习。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK