作者:马昆乐(吴昆)
Flutter自2015年出道以来,经过多年的发展已经相当成熟,在阿里、美团、拼多多等互联网公司得到了广泛应用。ICBU阿里卖家上90+%的新业务都是由Flutter开发的,ICBU客户端开发团队有很多Flutter开发者。
Flutter for Web (FFW)的早期试用版于2019年发布。当时很多感兴趣的同学都调查过。当时因为刚发布的问题很多,不适合在生产环境中使用。今年(2021年)3月,Flutter 2.0发布,FFW正式进入稳定分支。
阿里卖家外贸资讯版块主要由Flutter开发。在本财年的目标中,外贸资讯的app外推广是开源引流的重要一环。App外信息推广需要一个承载内容的网页,对这个网页的要求如下:
由于缺乏前端学生的支持,App端的学生只能投资这个页面。经过一番考虑,我们选择了FFW,原因如下:
经过以上思考,FFW填坑之旅正式开始。
Demo目前,阿里卖家FFW的相关页面已经上线。自FFW发布以来,js文件大的问题就一直存在,理论上会极大影响页面加载体验。在实际测试中,观察到PC和移动设备上的加载体验是可以接受的,并且运行流畅。相关演示如下:
创建FFW项目相对简单。Flutter切换到稳定版本,然后运行命令flutter create xxxProject并单击Run a Demo project来运行它。要将FFW应用到实际项目中,需要考虑工程上的问题,以及如何融入阿里的体系,比如如何发布,如何控制开发过程,如何请求接口等。,总结如下:
以上是阿里卖家在FFW开源引流最小闭环实践中遇到的问题。此外,FFW需要解决的问题包括:
接下来说明了最小闭合环实践中工程基础问题的成因和解决方法。
环境和再利用
参考Flutter在App端的开发,FFW首先要考虑选择哪个版本的Flutter,然后再考虑如何复用现有的Flutter代码。
1颤振版本选择
版本选择的问题是由于FFW和Flutter for App (FFA)的Flutter版本无法统一造成的。FFW要求的Flutter版本是2.0+,但目前我们App里的Flutter版本是1。X+,升级到2.0+还需要不确定的时间。经过一些考虑,我们目前选择以下版本的FFW和FFA:
FFA:Hummer/release/v 1 . 22 . 6 . 3-UC的Hummer分支 FFW:origin/flutter-2.8-候选-官方分支FFW不使用稳定版是因为iOS 15最近发布的webGL问题会导致FFW页面卡顿,目前已经采用了该问题的解决方案。(目前最新的稳定版本是2.10.0,问题已经解决)
2代码重用
在FFW重复使用FFA代码时,需要考虑两个问题:
Dart代码重用
FFW需要与dart版本2.12相对应的Flutter 2.0+版本。这个版本的dart引入了空声音空安全。FFA上使用的颤振版本是1。版本+对应的dart还没有引入空安全。同时,Flutter中新旧版本的dart库代码不能混合编译,所以目前无法无缝复用现有的App端代码库,只能通过修改现有代码来复用。代码修改的要点是:
用户?nullableUser在操作可以空的变量时使用?或者!
nullableUser?。toString();// 空安全。如果是空,NPE nullableUser就不会出现了!。toString();//强制指定非空。如果是空,则会报错。可选参数@required annotation由必需的保留字替换。
///旧版本 用户({ @必选this.name, @必选this.age, }); //新版本 用户({ 要求this.name, 要求this.age, });经过这三步修改,低版本代码基本可以在新版本中编译通过了。另外,有些API会因为版本升级而改变,也需要相应的修改,比如:
///旧版本 Typedef ValueWidgetBuilder < T & gt; = Widget函数(BuildContext上下文,T值,Widget子); //新版本 typedef值小部件生成器< T & gt = Widget函数(BuildContext上下文,T值,Widget?子);这类问题在API改动中占了大多数,修改起来相对简单。还有另一种变化。例如,在抽象类TextSelectionControls中,方法参数(如handleCut)的数量发生了变化:
///旧版本 Void句柄剪切(TextSelectionDelegate委托){..} //新版本 Void Handle Cut(TextSelectionDelegate Delegate, Clipboardstatusnotifier?剪贴板状态){...........................................................................................................................................................................
平台相关插件
平台相关的插件将调用本机功能。要在FFW上使用FFA中的插件,需要在Web平台上为插件实现相应的能力,这将在下面的js调用部分解释。如果使用的是pub.dev中的库,并且库满足以下条件,可以直接使用相应的版本:
|
|
在成功创建并运行本地演示项目之后,应该考虑以下问题:
对于开发到发布过程的管控,参考前端使用DEF平台(阿里内部前端开发发布平台)通过创建WebApp进行管理,此处不再赘述。页面发布涉及的内容如下:
1工程建设
FFW有两种构建方式,并不是所有的产品在应用中都需要简化。此外,要在DEF平台上发布产品,还需要一些额外的处理。在建设过程中,我们主要考虑如何建设。FFW编译和构建可选命令如下:
///canvaskit for rendering Flutter Build we B- we B- renderer画布工具包 //HTML for rendering Flutter Build we B- we B- renderer HTML。这两个命令的区别在于目标页面的呈现方式。Flutter官方对这两种方法的区别解释如下
总结起来就是这样:
在空项目中,两种资源加载方法的比较如下。基于对页面大小和页面性能的考虑,我们选择使用html。
|
|
对于新创建的项目,编译后的产品位于。/build/web目录,结构如下:
build └──[384]web ├──[224]assets │├──[2]asset manifest . JSON │├──在实际版本中,只需要index.html和main.dart.js。对于每次迭代,当不涉及“外壳工程”变更时,只需要main.dart.js。font manifest . JSON │├──[740k]notices ┖──[ 96]fonts ┖┖──[ 1.2m]material icons-regular . OTF ┤─[910k]
[82]
在选择了需要的产品之后,在DEF平台发布之前需要处理这两个文件:
3页面发布
在DEF平台上,产品文件处理完成后,js和html文件会发布到相应的cdn,html会部署到特定的地址:
前进:
在线:
对于在线环境,index.html的内容如下:
& lt!DOCTYPE html & gt & lt;html & gt & lt;head & gt & lt;!-发布到域名的二级目录时使用->: & lt;base href = & # 34/content _ page/& # 34;/& gt; & lt;/head & gt; & lt;body & gt & lt;!-替换为main.dart.js对应的cdn地址-->: & lt;脚本类型= & # 34;文本/JavaScript & # 34;src = & # 34https://g . alicdn . com/algernon/alisupplier _ content _ web/1 . 0 . 10/main . dart . js & # 34;& gt& lt/script & gt; & lt;/body & gt; & lt;/html & gt;此时,我们可以通过使用页面部署地址来访问我们的目标页面。如果页面一次性打开,内部不需要多页跳转,那么发布工作就由这一步完成。如果涉及多页跳转,需要将相关内容发布到自己的域名。更简单的方法是配置重定向,除此之外,还可以直接引用产品:
代码调试
基本环节贯穿之后,就可以开发需求了。开发过程中的重要环境是代码的调试。FFW可以在Chrome中以类似App的方式调试,体验良好。在调试环境下的IDE中设置断点后,可以在IDE中调试断点,也可以在Chrome中查看断点,甚至可以在Chrome中看到dart代码。以VSCode为例。调试过程和经验如下:
开始颤振调试
VSCode和Chrome中可见的断点
能力支持进入实际开发后,需要路由和接口请求等能力的支持,首先是页面路由和地址。
寻呼路由和地址
当FFW应用中存在多个页面,或者需要通过Http link传递参数时,需要相应的路由配置。和FFA类似,可以在root MaterialApp中配置相应的路由,然后使用Navigator.push跳转或者直接通过页面地址打开页面。下面的代码可以实现命名跳转和页面地址跳转:
/// MaterialApp配置 类myapp扩展无状态widget { @ override widget build(build context context){ Return Material App( debugShowCheckedModeBanner:false, ongenerate route:route configuration . ongenerate route, onGenerateInitialRoutes:(settings){ Return[route configuration . ongenerate route(routes settings(name:settings))]; }, ); } }//命名路由配置 classroute配置{ 静态映射< String,RouteWidgetBuilder?& gtbuilders = { //Page A & # 39;/page _ a & # 39;:(context,params){ return PageA(title:params?['标题& # 39;]); }, //第B页 & # 39;/page _ b & # 39;:(context,params){ return PageB(param 1:params?['param1 & # 39],param2: params?['param2 & # 39]); }, }; 静态路由& lt动态& gtongenerate route(routes settings设置){ return NoAnimationMaterialPageRoute( 设置:设置, 生成器:(上下文){ var uri = uri . parse(settings . name?''); var route = builders[uri . path]; if(路由!= null) { 返回路由(context,uri . query parameters); } else { return CommonPageNotFound(routes settings:settings); } }, ); } }配置完成后可以在页面跳转或者直接通过欧冠浏览器跳转地址:
/// Navigator跳转到B页 Navigator.of(上下文)。restorablepushnamed(& # 39;/page_b?param1 = 123 m2 = abc & # 39);地址跳转:在浏览器的地址栏中输入页面的地址,跳转到该页面。
///页面b访问地址 https://xxx.xx/#/page_b?Param 1 = 123m2 = ABC注:上述地址跳转方法要求FFW的UrlStrategy为hash标签法(默认UrlStrategy)。
web平台的Native-JS调用
通过使用pub.dev等仓库,您可以轻松地使用FFW中的各种功能。有必要考虑扩展仓库中不可用的功能。在FFA上可以通过插件使用native的能力,在FFW上可以扩展js的能力。通过调用js的能力,可以将前端的海量技术积累应用到FFW。
FFW的Dart最终会被编译成js,js在FFW应该会自然使用。为了支持dart中js的调用,dart官方发布了js库。利用库中的注释,在dart中调用js非常方便。
例如,当您需要调用alert方法时,将其定义如下:
///File:js _ interface . dart //用于调用js方法的工具类库。需要将JS库 @ JS() library lib . content引入依赖关系; 导入& # 39;包:js/js . dart & # 39;; //预警弹窗 @ JS(& # 39;alert & # 39) 外部void jsAlert(String msg);然后在需要预警的地方引入js_interface.dart,调用jsAlert方法:
进口& # 39;包:mtest/js _ interface . dart & # 39;; ... jsAlert(& # 39;测试消息& # 39;); ...更多用法参见pub.dev中对js库的描述:https://pub.dev/packages/js.在打通了js的能力之后,下面的很多问题都解决了。
Mtop接口
鉴于现有Mtop(阿里App使用的一个网关)在App端的构建,如果现有Mtop能在FFW调用,会减少很多工作量。因此,有必要为FFW增加Mtop调用的能力。要完成这项工作,有两个部分:
1FFW支持Mtop
mtop的能力可以通过调用mtop.js的能力引入FFW,整体流程如下:
1.将mtop.js引入index.html
& lt脚本src = & # 34//g . alicdn . com/MTB/lib-mtop/2 . 6 . 1/mtop . js & # 34;& gt& lt/script & gt;2.定义接口文件js _ mtop _ interface.dart。
@ JS() 库lib.mtop 导入& # 39;包:js/js . dart & # 39;; 导入& # 39;包:js/js _ util . dart & # 39;; 导入& # 39;镖:转换& # 39;; 导入& # 39;dart:js & # 39;; //mtop请求的参数 @ anonymous @ js () 类mtop params { 外部字符串get API 外部字符串get v; 外部动态获取数据; 外部字符串get ecode 外部字符串获取数据类型; 外部字符串获取类型; 外部工厂MtopParams({ 必需的字符串api, 必需的字符串v, 必需的动态数据, 必需的字符串ecode, 必需的字符串数据类型, 必需的字符串类型, }); } //lib.mtop请求函数 @ JS(& # 39;lib . mtop . request & # 39;) external dynamic _ jsMtopRequest(MtopParams params); //Dartmap到js的对象 objectmaptojsobj (map
进口& # 39;包:mtest/mtop/js _ mtop _ interface . dart & # 39;; ... try { var RES = await MTO prequest(API name,params,version,method); 打印(& # 39;res $ res & # 39); } catch(err){ data = stringify(err); }4。解析结果:接口请求返回的结果是一个jsObject,通过json.stringify方法转换成JSON后可以在dart级别使用。
string JSON str = stringify(RES);2服务器H5 Mtop配置
在FFW访问mtop.js后,目标mtop接口需要进行相应的处理才能被调用:
打点
通过mtop请求,可以将黄金方向的js库导入FFW进行dot,流程如下:
1.index.html介绍了js文件。
& lt脚本类型= & # 34;文本/JavaScript & # 34;src = & # 34https://g.alicdn.com/alilog/mlog/aplus_v2.js" & gt;& lt/script & gt;2.定义接口文件JS _ goldlog _ interface.dart。
@ JS() 库lib.goldlog 导入& # 39;包:js/js . dart & # 39;; //记录函数 @ JS(& # 39;goldlog.record & # 39) external dynamic _ jsGoldlogRecord(字符串t,字符串e,字符串n,字符串o,字符串a); void goldlog record(String logkey,String eventType,String queryParams){ _ jsGoldlogRecord(logkey,eventType,query params,& # 39;获取& # 39;, ''); }3。打几个电话。
进口& # 39;包:mtest/track/js _ gold log _ interface . dart & # 39;; ... goldlogRecord(logkey,eventType,query params); ...然后,在日志平台上进行相应的点配置。
监控

监控能力的接入比较简单,所以选择arms(应用实时监控服务),直接将arms引入index.html。流程如下:
1.将相关图书馆引入index.html。
& lt脚本类型= & # 34;文本/JavaScript & # 34;& gt var trace = window。TraceSdk({ PID:& # 39;as-content-web & # 39;, 插件:[ [窗口。TraceApiPlugin,{ sampling: 1 }], [window。traceperplugin], [window。traceresourcerorplugin] ], }); //启动trace监听全局JS异常,在debug trace.install()时临时记下问题; trace . logpv(); & lt;/script & gt;2.在arms平台上进行相关配置。
注意:trace.install()将导致页面不在调试环境中显示,并且可以在调试环境中禁用。
优化和兼容完成上述基础能力建设后,FFW基本可以满足简单的发展要求。除了开发需求,还应该考虑体验和兼容性。
负载优化
《FFW》发行以来遇到的一个问题是数据包的大小。对于一个空 helloworld项目,单个js包的大小为1.2 MB(压缩前),在移动设备上网络不好的情况下可能需要几秒钟才能加载。为了改善页面加载的体验,请考虑以下事项:
1等待流程优化
FFW页面在js加载完成之前都是白屏,给人一种页面卡顿的感觉。所以在js加载完成之前添加加载动画是很有必要的,以免让页面一直白屏。参考应用程序中使用的做法,您可以在数据加载之间插入框架屏幕,如下所示:
& ltiframe src = & # 34https://g . alicdn . com/algernon/alisupplier _ content _ web/0 . 9 . 1/skeleton/index . html & # 34;id = & # 34iFrame & # 34frameborder = & # 340"滚动= & # 34;不& # 34;& gt& lt/iframe & gt; & lt;脚本& gt function setIframeSize(){ & lt;!-骨骼屏幕大小设置,占据全屏-& gt; } function remove iframe(){ var iframe = document . getelementbyid(& # 34;iFrame & # 34); iframe . parent node . remove child(iframe); } setIframeSize(); & lt;/script & gt; & lt;!-加载完成后移除骨屏-->: & lt;脚本类型= & # 34;文本/JavaScript & # 34;src = & # 34https://g . alicdn . com/algernon/alisupplier _ content _ web/1 . 0 . 10/main . dart . js & # 34; onload = & # 34;remove iframe()& # 34;& gt& lt/script & gt;2TODO JS解包和优化
等待流程的优化可以在一定程度上改善等待体验,只能治标不治本。如果想快速加载,就必须让加载的资源变小。对于多页面应用,可以将整个main.dart.js拆分成几个小的包,在使用过程中逐步加载。目前我们知道美团有相应的技术,但实现细节未知,需要研究。参考https://github.com/flutter/flutter/issues/46589.
兼容性问题
类似的应用在不同的设备上会有体验问题,FFW在不同的H5容器上会有兼容性问题。在我们的实践中,不同H5集装箱的踩踏记录如下:
H5集装箱的白屏问题:
微信H5容器白屏问题:
页面卡在iOS 15上:
IOS兼容性问题:
其他问题
除了H5容器的兼容性问题,FFW本身在实践中也存在一些问题,记录如下:
提供程序库问题:
JsonConverter问题:
目前只完成了一个可供业务使用的小闭环建设,FFW还有很多TODO内容,具体如下:
建筑:
基本功能:
兼容性和优化
想象一下:
关注【阿里巴巴移动科技】微信微信官方账号,每周写3篇移动科技实务&干货供你思考!








