乐趣区

关于javascript:闲鱼对FlutterNative混合工程解耦的探索

简介: 离别高兴,祝你快乐~

作者:祈晴

  1. 闲鱼 Flutter 现状

===============

闲鱼是第一个应用 Flutter 混合开发的大型利用,但闲鱼客户端开发最深刻领会的痛点就是 编译时长影响开发体验。在 Flutter+Native 这种开发模式下,Native 编译速度慢,模块开发无奈冲破。闲鱼集成了团体泛滥中间件,很多性能无奈通过 flutter 间接调用,需应用各种 channel 到 native 去调用对应性能。总而言之,闲鱼目前 Flutter 开发面临如下几个痛点:

  • Flutter 侧混合编译速度慢,Android 首次编译10min+,iOS 首次编译20min+
  • 混合栈编程中历史包袱导致 IOS/Android 双端返回给 Flutter 侧的数据可能存在 不一致性
  • 集成模块 开发效率相比模块开发较低,单模块页面测试性能数据无奈开展;

2. 解决方案一

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. 解决方案二

3.1 换位思考

(1)若本人是业务方,须要为 Flutter 侧去拆分包,去构建一个最小化壳工程,其老本是微小的。
(2)Fass 工程一体化依赖一个最小化壳工程的 Native 运行环境去运行 Flutter 侧代码,可是并非所有的业务方都会提供一个最小化壳工程去运行 Fass,那么 Fass 工程一体化 / 模块开发如果在团体其余运行环境下停顿?
(3)最小化壳工程运行环境无奈紧跟 Native 侧的各种版本,会导致运行后果不统一状况下也不敢轻易应用;

如果解决此问题呢?集体提出过跨过程实现形式,在 Android 端侧跨过程调用实现形式始终很常见的场景,client 拜访 server 得后果,而 Flutter 侧和 Native 侧不就是 client 和 server 双端么?如下图所示,其实 Flutter 获取数据就是通过 MethodChannel/EventChannel 获取,因而能够换一种形式思考?

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<T> invokeMethod<T>(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<T> invokeMethod<T>(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 =
            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 上,操作性不在同一台手机上;当然计划二尽管有肯定缺点,却能够解决很多问题,因而后续在闲鱼模块化拆分落地我的项目中,在思考是否有更加完满的解决办法。

原文链接
本文为阿里云原创内容,未经容许不得转载。

退出移动版