大背景
Flutter是Google面世的跨网络平台、高效能合作开发架构,采用Skia做为图形发动机,不采用网络平台命令行,确保Android和iOS上UI连续性。采用Flutter合作开发,Android、iOS采用两套Dart标识符,能节约合作投资生产成本。
一般来说具备很大体量的App都有两套成形通用型的此基础库,因此倚赖子公司管理体系内的许多此基础库。采用Flutter再次合作开发天数和同时实现生产成本都极高。因此在Native App中内嵌Flutter机能的混和合作开发商业模式是应用领域Flutter控制技术的平稳型改建形式。
公寓楼PMS是这款给公寓楼保镳提供更多商品房管理工作的APP,后期机能已采用Native合作开发上架。他们在该工程项目中采用了Flutter合作开发,须要同时实现以下机能:将Flutter软件系统到已近Native工程项目中;同时实现Flutter与Native网页混和管理工作;同时实现Flutter与Native通讯,F83E43Se已近Native天然资源;同时实现Dart侧标识符合作开发架构。
Flutter发动机如是说
1. Flutter构架
具体来说看下Flutter构架图:
图1
Flutter的构架主要就分成3层:Framework,Engine,Embedder。
Framework采用dart同时实现,主要就包括Material Design和Cupertino艺术风格的Widgets,文档/相片/按键等此基础Widgets、图形、动画电影、表情符号等。
Engine采用C++同时实现,主要就主要就包括:Skia,Dart和Text。Skia是开放源码的三维程序库,提供更多了适用于于多种不同硬件网络平台的通用型API。在标识符初始化 dart:ui库时,初始化最后会走到Engine层,接着同时实现或者说的绘出方法论。
Embedder是一个内嵌层,是将Flutter引擎移殖到各网络平台的第二层标识符,主要就主要就包括图形Surface增设,缓存增设,和应用领域程序等。
2.Flutter缓存模型
Flutter Engine自己不创建管理工作缓存。Flutter Engine缓存的创建和管理是由Embedder负责的。Flutter Engine要求Embedder提供更多四个Task Runner。尽管Flutter Engine不在乎Runner具体跑在哪个缓存,但是它须要缓存配置在整一个生命周期里面保持稳定。也就是说一个Runner最好始终保持在同一缓存运行。这四个主要就 的Task Runner主要就包括:
Platform Task Runner
Flutter Engine的主Task Runner,运行Platform Task Runner的缓存能理解为是主缓存。类似于Android Main Thread或者iOS的Main Thread。
UI Task Runner Thread(Dart Runner)
UI Task Runner被Flutter Engine用于执行Dart root isolate标识符。
GPU Task Runner
GPU Task Runner被用于执行设备GPU的相关初始化。
IO Task Runner
IO Runner的主要就机能是从相片存储(比如磁盘)中读取压缩的相片格式,将相片数据进行处理为GPU Runner 的图形做好准备。
前面他们提到Engine Runner的缓存能按照实际情况进行配置,各网络平台目前有自己的同时实现策略。Android和iOS网络平台上面每一个Engine实例启动的时候会为UI,GPU,IO Runner各自创建一个新的缓存。所有Engine实例共享同一个Platform Runner和缓存。
Flutter官方默认混和方案
多发动机商业模式
在混和方案中解决的主要就问题是如何去处理交替出现的Flutter和Native网页。Flutter官方给出了一个Keep It Simple的方案:对于连续的Flutter网页(Widget)只须要在当前FlutterActivity打开即可,对于间隔的Flutter网页初始化新的发动机。网页示意如下图所示:
图2
这个方案的好处就是简单易懂,容易采用,但是存在比较严重的问题。如果Native网页与Flutter网页交替出现,Flutter Engine的数量会线性增加,多发动机商业模式会造成下列问题:
内存问题。多发动机商业模式下每个发动机之间的Isolate是相互独立的,因此每一个发动机底层都维护了相片缓存等比较消耗内存的对象。冗余天然资源问题。通过前文能知道,发动机在Android和iOS的同时实现中,每一个Flutter实例会新启动三个缓存(IO,GPU和UI),从而带来了额外的天然资源采用。网页间通讯复杂。每一个Flutter网页在一个隔离的isolate中,网页间通讯将会变得非常复杂。 应用领域程序的注册问题。应用领域程序倚赖Messenger传递消息,而Messenger由FlutterNativeView同时实现。多发动机形式使得应用领域程序的注册和通讯将会变得混乱且难以维护。综上,由于多发动机混和方案存在比较多的问题,因此工程项目中没有采用此方案。
Flutter Boost同时实现方案
通过调研发现,阿里闲鱼面世了Flutter Boost解决方案,该方案采用的是多个Flutter网页共享发动机的同时实现形式,示意图如下所示:
图3
所有的Flutter网页共享一个Flutter实例(FlutterView),这种形式能够有效避免多发动机形式带来的各种问题,但是单例的同时实现也使网页的管理工作变得更加复杂。为此Flutter Boost提供更多了两套完整的解决方案。
下面看下Flutter Boost的整体构架图:
图4
方案同时实现分成Native部分与Dart部分:
Native部分概念
Container:Native容器,Fragment(Android),ViewController(iOS)Container Manager:Native容器管理工作器Messaging:基于Message Channel的消息通道Dart部分概念
Container:Flutter Widget的容器,Flutter NavigatorContainer Manager:Flutter 容器管理工作器 Coordinator: 协调器,接受Messaging消息,负责初始化Container Manager的状态管理工作。Native容器与Flutter容器(Navigator)是一一对应的,生命周期也是同步的。当一个Native容器被创建的时候,Flutter对应的容器也被创建,它们通过相同的唯一id关联起来。当Native的容器被销毁的时候,Flutter的容器也被销毁。Flutter容器的状态是跟随Native容器,这也就是Native驱动。由Manager统一管理工作切换当前在屏幕上展示的容器。
性能对比
下图对官方默认多发动机混和方案和Flutter Boost方案进行了性能对比:
图5 默认多发动机形式网页内存图
图6 Flutter Boost网页内存图
从上述对比图能看出,当连续打开多个Flutter网页时,默认多发动机形式网页的内存呈线性增长,而Flutter Boost网页内存保持在一个比较稳定的范围。因此他们的工程项目中选用了Flutter Boost方案。
公寓楼PMS进入Flutter Boost
1.Dart工程部分
在Dart工程的pubspec.yaml中引入Flutter Boost:
flutter_boost: git: url: https://github.com/alibaba/flutter_boost.git ref: 0.0.4102. Native工程部分(Android)
(1)在setting.gradle中倚赖Flutter工程:
setBinding(new Binding([gradle: this, mainModuleName:ApartmentClient])) evaluate(new File( settingsDir.parentFile, flutter_apartment/.android/include_flutter.groovy ))(2)在build.gradle中引入Flutter Boost的Native工程:
implementation project(:flutter) implementation project(:flutter_boost)至此就把Flutter Boost接入到公寓楼PMS工程里面了,但是要采用Flutter Boost,还须要下列工作要完成。
设计Flutter跳转协议,接入跳转架构Flutter Boost架构没有软件系统ARouter等路由跳转架构。因此他们须要结合自己的业务特点设计跳转协议。仿照WubaRN的设计思想,他们须要在Native端有一个Flutter通用型载体页,所有的路由跳转都经由Native侧跳转中心。跳转架构他们用的是58JumpCenterLib,[h1]跳转协议如下所示:
wbapartment://jump/house/flutter?params={“container_name”:“personalCenter”,“show_guide”:true}“flutter”:Native侧载体页网页类型
“params”:跳转协议参数,其中“container_name”是固定参数,标识Dart侧的具体显示网页(Navigator);“params”里面的所有参数都经由MessageChannel透传到Dart侧。
最后须要处理一下Dart侧传过来的跳转协议,标识符如下:
private void initFlutterBoost() { FlutterBoostPlugin.init(newIPlatform() { ……/** * 当Dart侧打开一个本地网页,将会回调这个方法,网页参数拼接在url中 * @param context * @paramurl *@param requestCode * @return */ @Override public boolean startActivity(Context context, String url,int requestCode) { returnPageTransferManager.jump(context, url); } }); }完善Native侧Flutter载体页由于在公寓楼PMS APP中,他们须要在首页TAB页中内嵌Flutter网页,还须要支持跳转协议的单独展示网页。因此他们的做法是基于Fragment进行封装,单独网页采用FragmentActivity/Fragment的形式。
通过完成以上工作,就能在公寓楼PMS工程项目中采用Flutter Boost架构了。
Flutter Boost的缺点及改进
Flutter Boost是从应用领域层出发,直接F83E43SeFlutterView从而共享Flutter Engine。Native侧同时实现时,须要共享FlutterView,不同Activity/ViewController切换时,须要将FlutterView从前网页的Activity/ViewController移除,接着添加到当前网页的Activity/ViewController。这个过程在Android上能够明显的感觉到网页的闪动。Flutter 1.12的发布完美的解决了这个问题,Flutter 1.12支持将Flutter Engine通过id缓存起来,接着启动网页时,能指定采用缓存中的Engine,从而彻底解决了混和合作开发共享发动机的问题。页面间采用缓存发动机方案,须要将Native侧网页和Dart侧网页一一对应。能采用Message Channel通讯,结合路由跳转中心,由Native网页驱动即可。
混和合作开发中遇到的问题
1. Dart侧网络请求问题
决办法是通过Message Channel将Native侧的header信息共享给Dart侧。
Native侧同时实现:
newMethodChannel(getBoostFlutterView(), METHOD_CHANNEL).setMethodCallHandler(new MethodChannel.MethodCallHandler() { @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { if (call.method.equals(“getHeader”)) { IHeadersIntegration commonHeaderUtils = CommonHeaderUtils.getInstance(MainTabActivity.sRef.get()); Map<String, String> headerMap = commonHeaderUtils.generateParamMap(MainTabActivity.sRef.get()); result.success(JsonUtils.hashMapToJson(headerMap)); }else{ result.notImplemented(); } } });Dart侧同时实现:
Map<String, dynamic> headers; try { final String headerString = awaitplatform.invokeMethod(getHeader); headers = jsonDecode(headerString); } on PlatformException catch (e) {} Map<String, String> params = new Map(); Response response = await dio.get( dataUrl, queryParameters: params,options: Options(headers: headers), );2. F83E43SeNative的天然资源相片问题
Flutter默认将所有的相片天然资源文件打包到assets目录下,但是他们并不是用Flutter全新合作开发的工程项目,相片天然资源放在Native侧的drawable目录下,即使是全新的Flutter网页也会有许多相片F83E43Se已近的Native侧相片,因此在assets目录下新增相片天然资源并不合适。但是Flutter官方并没有提供更多直接初始化drawable目录下的相片天然资源的途径。
通过调研,能通过下列形式同时实现Native侧的相片共享:
Message Channel形式
Dart侧通过Message Channel将天然资源文件名传递到Native侧;Native侧将对应名称的drawable以二进制格式传递到Dart侧;Dart侧接收到二进制格式相片后进行图形。
Native侧标识符:
BasicMessageChannel<Object> messageChannel = new BasicMessageChannel<>(getFlutterView(), “getPic”, StandardMessageCodec.INSTANCE); messageChannel.setMessageHandler(newBasicMessageChannel.MessageHandler<Object>() { @Override public void onMessage(Object o, BasicMessageChannel.Reply<Object> reply) { reply.reply(drawableToByte(getResources().getDrawable(getResId(o.toString())))); } });Dart侧标识符:
const _messageChannel = const BasicMessageChannel<Object>(“getPic”, StandardMessageCodec()); Future<Uint8List> getNativeImage(String name) async { Uint8List result = await _messageChannel.send(name); return result; }通过以上步骤,就能将Android Native侧drawable目录下侧天然资源相片共享给Dart侧命令行采用,从而避免了重复引入天然资源。
Dart侧合作开发架构采用
在采用Dart合作开发需求之初,为了快速同时实现机能,还有对Flutter特性不熟悉,他们没有采用合作开发架构,机能就是标识符的堆砌。但是,随着采用网页的增多,发现工程项目中业务标识符耦合严重,标识符可维护性很差。为此,他们进行了相关调研,发现闲鱼开放源码了这款Flutter应用领域架构——Fish-Redux。
1. Fish-Redux如是说
Fish-Redux是一个基于Redux数据管理工作的组装式Flutter应用领域架构,特别适用于于构建中大型的复杂应用领域。它的最大特点是配置式组装,它会非常干净,易编写、易维护、易协作。
下面看下Fish-Redux构架图:
图7
构架主要就分成3层,自下向上依次为:
Redux
Redux是一个用来做[可预测][集中式][易调试][灵活性]的数据管理工作的架构。所有对数据的增删改查等操作都由Redux来集中负责。
Fish-Redux通过Redux做集中化的可观察的数据状态管理工作。Fish-Redux在Flutter中对传统的Redux做了改良。一个组件须要定义一个数据(Struct)和一个Reducer。同时组件之间存在着父倚赖子的关系。通过这层倚赖关系,解决了【集中】和【分治】之间的矛盾,同时对Reducer的手动层层Combine变成由架构自动完成,简化了采用Redux的困难。
Component
Component是对局部的展示和机能的封装。基于Redux的原则,Fish-Redux对机能细分成修改数据的机能(Reducer)和非修改数据的机能(Effect)。组件是对视图的分治,也是对数据的分治。通过逐层分治,将复杂的网页和数据切分成相互独立的小模块,有利于团队内的协作合作开发。
Adapter
Adapter也是对局部的展示和机能的封装。它是Component同时实现上的一种变化,优化了Flutter在采用ListView场景下的性能问题。
综上所述,Fish-Redux不仅同时实现了Flutter网页的状态管理工作,更是两套完整的Flutter应用领域合作开发架构。下面如是说一下公寓楼PMS是如何采用Fish-Redux进行合作开发的。
2. Fish-Redux在公寓楼PMS的应用领域
Fish-Redux的接入非常简单,只需在Flutter工程项目中pubspec.yaml的dependencies模块增设fish-redux及倚赖版本,接着运行flutter packages get即可。
下面以公寓楼PMS中个人中心网页如是说:
下图是个人中心网页,
图8
该网页采用Flutter ListView命令行同时实现,主要就由6个item,5种item组合而成。
下面是个人中心的Page标识符:
class PersonalCenterPage extends Page<PersonalCenterPageState, Map<dynamic, dynamic>> { PersonalCenterPage(): super( initState: initState, effect: buildEffect(), view: buildView, dependencies: Dependencies<PersonalCenterPageState>( adapter: NoneConn<PersonalCenterPageState>() + PersonalCenterListAdapter()), ); }PersonalCenterPage由State,Effect,Viewng图,TitleBar等;Adapter里面定义了列表包含的Component等。下面着重看下Adapter同时实现:
class PersonalCenterListAdapter extends DynamicFlowAdapter<PersonalCenterPageState> { PersonalCenterListAdapter(): super( pool: <String, Component<Object>>{ NORMAL_ITEM: NormalItemComponent(), USER_INFO_ITEM: UserItemComponent(), LOGOUT_ITEM: LogoutItemComponent(), TODO_ITEM: TodoItemComponent(), CONTACT_ITEM: ContactItemComponent(), }, connector: _HouseListConnector(), reducer: buildReducer(), ); }在PageCenterListAdapter中,pool中注册了列表中所包含的Component及类型;connector是连接器,负责将网络请求返回的数据转化成Component图形时须要的数据;reducer里定义了修改网页数据的行为,当网络请求成功后,会初始化该action触发网页图形。
最后看下Component同时实现,以UserItemComponent为例:
class UserItemComponent extends Component<UserItemState> { UserItemComponent() : super( view: buildView ); }其中,UserItemState是该模块图形所需数据,view则是该模块UI方法论。
下面看下该网页整体的标识符结构图:
图9
从上图能看出,采用Fish-Redux合作开发会使标识符结构非常清晰,尤其是当网页方法论复杂的时候。Fish-Redux使Flutter合作开发变得简单,只要按照方法的要求传入对应的参数即可,同时实现了面向方法编程。
该同时实现中,将网页分解成Page->Adapter->Component的结构。当列表页中新增样式,只须要合作开发对应的Component并注册到Adapter中的pool即可。由于模块拆分到粒度比较细的业务单元,该页面中同时实现的Component也能F83E43Se到别的网页中,避免重复合作开发。
由于Fish-Redux中包含了Redux的机能,使得合作开发过程中的状态传递变得非常简单,只需注册Action,在接收Action的地方增设响应方法论,在触发的地方初始化dispatch(Action action)方法即可。
此外,Fish-Redux的好处是将方法论与视图隔离开,view只负责具体的网页图形;而方法论通过Effect和Reducer同时实现。因此有这样的公式Component = View + Effect(可选) + Reducer(可选) + Dependencies(可选)。这不仅很好的同时实现了标识符的解耦,也为以后同时实现UI标识符自动生成,合作开发人员只合作开发业务方法论标识符的合作开发商业模式提供更多了可能。
借鉴Flutter中面向函数编程,可插拔的网页组件化思想,他们目前正在对Native工程项目58APP租房网页进行重构,以同时实现标识符结构的统一,不同网页组件间的F83E43Se,并且网页能根据Server返回数据灵活组装。
总结
本文如是说了Flutter混和合作开发中遇到的问题及解决办法,和合作开发中应用领域Fish-Redux的课堂教学。Flutter混和合作开发,主要就的问题是共享Flutter发动机的同时实现。Flutter-Boost提供更多了共享FlutterView的同时实现形式。他们引入了Flutter-Boost,合作开发了Native侧载体页,设计了通用型Flutter跳转协议,结合58跳转中心解决了Flutter混和合作开发的问题。在业务合作开发过程中,随着合作开发的深入和业务方法论的复杂,通过调研,采用了Fish-Redux进行了标识符的重构,对复杂业务进行了细粒度的拆分,对方法论和试图进行隔离,优化了标识符结构。
参考文献
1、Flutter中文网,
https://flutterchina.club/2、深入理解Flutter发动机缓存商业模式,
https://mp.weixin.qq.com/s/hZ5PUvPpMlEYBAJggGnJsw3、已开放源码|码上用它开始Flutter混和合作开发——FlutterBoost,
https://mp.weixin.qq.com/s/v-wwruadJntX1n-YuMPC7g4、Fish-Redux如是说文档,
https://github.com/alibaba/fish-redux/tree/master/doc作者简介
万兵 :58同城房产技术部-Android合作开发工程师。主要就负责58和安居客APP租房和商业地产业务的合作开发和维护工作。