|闲鱼对 Flutter-Native 混合工程解耦的探索
1. 闲鱼Flutter现状
闲鱼是第一个使用Flutter混合开发的大型应用 , 但闲鱼客户端开发最深入体会的痛点就是编译时长影响开发体验 。 在Flutter+Native这种开发模式下 , Native编译速度慢 , 模块开发无法突破 。 闲鱼集成了集团众多中间件 , 很多功能无法通过flutter直接调用 , 需使用各种channel到native去调用对应功能 。 总而言之 , 闲鱼目前Flutter开发面临如下几个痛点:
- Flutter侧混合编译速度慢 , Android首次编译10min+ , iOS首次编译20min+;
- 混合栈编程中历史包袱导致IOS/Android双端返回给Flutter侧的数据可能存在不一致性;
- 集成模块开发效率相比模块开发较低 , 单模块页面测试性能数据无法展开;
2.1方案概述
此项目从立项至今已经很长一段时间 , 由于业务迭代快 , native插件满天飞情况下 , 想要做到工程模块化拆分难度可想而知;如下图是项目立项为模块化拆分 , 业务方需要将各个业务拆分解耦合 , 拆分集团中间件 , 业务封装组件 , Native业务代码 , Flutter桥代码 , Flutter组件库 , Flutter侧业务代码等多个模块;项目初衷就是整理代码 , 提供一个Flutter可运行的干净环境 , 同时需要让flutter可以获取到native几乎所有能力 , 但是编译开发调试时候有想要速度快 , 效率高 。 能想到的最直接解决方案就是拆包 , 从0-1建立一个最小壳工程 , 然后拆分集团基本中间件 , 封装业务组件 , Flutter插件等 , 如下是整个项目架构:
本文插图
日常模块化单页面级需要使用最小壳工程 , 其内部又channel的声明和实现 , 通过运行最小壳工程运行得到结果 , Flutter侧模块开发通过IOC调用到最小壳工程的channel得到返回结果 , 最后将模块化开发以一种pub或者git依赖方式集成到闲鱼FWN主工程即可;
2.2 阶段性产出
业务模块化拆分从来都是一种吃力不讨好的活 , 明知道拆出来有收益 , 但是投入产出比不足 , 因此历史包袱代码越来越厚重 , 以至于下一个接收的人都不敢轻易修改代码;在模块化拆分时候 , 开始项目时候提出过新起一个干净的工程 , 然后一步步拆分集团中间件 , 期间拆出了Mtop/Login/FlutterBoost/UI Plugin , 耗时3周/2人 , 得到部分结果就是新业务 , 新界面开发满足基本快速迭代开发 , 缺点也很明显如下所示:
- 拆分梳理Native的中间件繁琐 , 工作量巨大 , 最小化壳工程耗时3周/2人
- 推动业务方拆分基础组件库更难 , 目前项目进展不顺
- 维护成本高 , 拆分壳工程运行结果和主工程可能不一致
- 业务迫切其结果 , 但投入产出比不足 , 比如Flutter单页面性能测试 , Flutter侧模块化拆分 , Fass工程一体基石
3.1 换位思考
(1)若自己是业务方 , 需要为Flutter侧去拆分包 , 去构建一个最小化壳工程 , 其成本是巨大的 。 (2)Fass工程一体化依赖一个最小化壳工程的Native运行环境去运行Flutter侧代码 , 可是并非所有的业务方都会提供一个最小化壳工程去运行Fass , 那么Fass工程一体化/模块开发如果在集团其他运行环境下进展?(3)最小化壳工程运行环境无法紧跟Native侧的各种版本 , 会导致运行结果不一致情况下也不敢随便使用;
如果解决此问题呢?个人提出过跨进程实现方式 , 在Android端侧跨进程调用实现方式一直很常见的场景 , client访问server得结果 , 而Flutter侧和Native侧不就是client和server双端么?如下图所示 , 其实Flutter获取数据就是通过MethodChannel/EventChannel获取 , 因此可以换一种方式思考?
本文插图
【|闲鱼对 Flutter-Native 混合工程解耦的探索】
3.2 IPC跨进程通信 , Android Binder
期间在Android侧我使用过Android Binder去实现 , 新起一个APP做为壳工程 , 其内部实现了各种插件去访问主工程服务 , 获取结果然后返回给壳工程的Flutter调用 , 但是维护成本依然在;同时iOS侧没有对应的实现机制 , 因此此方式被抛弃;
3.3 具体方案:Hook代理+Socket服务
Android开发应该都熟悉hook和插件化技术 , 其实从之前的Flutter到Native的Chanel架构就可以想到一种思路 , 既然解决不了Native问题 , 那就解决Channel的问题吧 , Native端侧的IPC方式无法实现 , 换到Flutter侧和Native侧的Channel通信侧去实现IPC吧 。 参考业务对于插件化hook机制/IPC机制的理解 , 结合自身对于flutter channel的理解 , 可以实现一种利用socket服务去hook method channel和event channel实现方式 , 去代理客户端的method channel和event channel , 将处理结果通过socket交给服务端去处理拿到服务端真正的method channel和event channel数据即可 , 这才是我心中想要的实现方式就是如此 , 整个架构图如下:
本文插图
客户端是一台手机 , 服务端也是一台手机 , 服务端跑闲鱼FWN主工程 , 客户端跑一个干净的Flutter工程;客户端先通过Flutter侧代码去找使用本端有对应的Channel , 如果有则使用返回结果 , 如果没有则通过Socket请求结果到服务端主工程上 , 主工程根据Socket定义的协议字段去解析然后发起一个channel拿结果 , 之后通过socket将解决返回给客户端 , 客户端拿到了socket结果数据后执行想要的渲染方式即可;
或许你有质疑点:比如为什么要用2台手机 , 使用一台不可以么?这里我推荐使用2台手机有如下2个原因:
(1)一台手机运行2个APP , 如果server在后台可能会导致进程资源被回收 , Socket通信中断;(2)使用2台手机有一个极大好处是 , 你运行Android的Flutter侧Client代码 , 但是往往你需要验证Native侧双端Server代码数据 , 如果客户端手机/服务端手机是2台 , 只需要改下客户端的IP地址去请求Android手机的Server还是IOS手机的Server就可以验证结果;
3.4 尝试验证
比如如下的method channel代码如下:
Future invokeMethod(String method, [ dynamic arguments ]) async { assert(method != null); final ByteData result = await binaryMessenger.send( name, codec.encodeMethodCall(MethodCall(method, arguments)), ); if (result == null) { throw MissingPluginException('No implementation found for method $method on channel $name'); } final T typedResult = codec.decodeEnvelope(result); return typedResult; }修复result == null的场景 , 如果是我们指定的客户端 , 则通过socket去拿server数据,重点理解Fish MOD:START到Fish MOD:END代码思想就理解了;
Future invokeMethod(String method, [dynamic arguments]) async { assert(method != null); final ByteData result = await binaryMessenger.send( name, codec.encodeMethodCall(MethodCall(method, arguments)), ); if (result == null) { //Fish MOD:START //throw MissingPluginException( //'No implementation found for method $method on channel $name'); //socket从服务端手机获取值 final dynamic serverData = http://news.hoteastday.com/a/await SocketClient.methodDataForClient(clientParams); //Fish MOD:END } final T typedResult = codec.decodeEnvelope(result); return typedResult; }最后通过此中方式验证了MethodChannel/EventChannel数据正常收发的可行性 , 后续还需要在业务场景具体实验耕田;
4.结果对比和展望
结果对比:
本文插图
无法方案1和方案2最终都可以解决编译运行时长的问题 , 但方案1在拆分模块和维护模块时候都有很高的成本 , 运行时长虽然降低了 , 但是模块化工作量却加大很多 , 方案2可以完美解决拆分成本和维护成本 , 但是不足之处就是运行环境苛刻 , 可操作性不足 , 其需要2部手机作为运行环境 , 另针对于一些页面跳转逻辑 , 可能客户端手机A触发到服务端手机B上 , 操作性不在同一台手机上;当然方案二虽然有一定缺陷 , 却可以解决很多问题 , 因此后续在闲鱼模块化拆分落地项目中 , 在思考是否有更加完美的解决方法 。
作者:祈晴
本文为阿里云原创内容 , 未经允许不得转载 。
推荐阅读
- 番茄炒鸡蛋先炒番茄还是先炒鸡蛋?其实都不对,正确方法送给你
- 做蛋炒饭,先炒蛋呢还是先炒饭呢很多人做的不对,难怪不好吃
- 埃隆·马斯克|马斯克薛其坤对谈:火星地球各有生存之道
- 绝对超级下饭的家常菜,烹制简单,香味浓郁特别解馋,百吃不厌!
- 银屑病|对牛皮癣恢复有利的生活方式有哪些?
- 蔬菜皇后洋葱,对人体有哪些好处呢?生活中怎么吃才对呢?
- 地球|地球是一颗多大的行星?看看这10张太阳系天体对比图
- 果园|果园植被多样性对虫害的控制
- 四川军屯锅魁,在家自制,绝对耐心的挑战
- 鸡蛋和牛奶是天生一对,隔三差五给孩子吃,香甜嫩滑,营养易吸收
