,在这么遥远的技术路线上,还是有相当多的读者和社区认可的。所以,笔者就为大家详细介绍一下这个方案的原理,让大家少走一些弯路。先说一下对Flutter开发架构的理解。大概有以下三种:以Flutter为主开发的APP。Flutter和Native容器的混合体,页面可以是Flutter也可以是Native,比如flutter_boost。FlutterEngineGroup多引擎渲染,容器由Native提供,Flutter只关心View部分。这里不是去比较各自的优缺点,只是在选择上选择最适合自己的方式。比如笔者所在的公司早期使用flutter_boost作为混合页面容器,但是现在架构的变化会逐渐减少Native的实现,变成跨端架构,而纯Flutter已经不满足我们的发展了,而且还是在代码量方面。改成基于Flutter的APP架构是不可能的。(老实说,Dart不是一种好的开发语言……)。基于这个前提,选择就很少了。Flutter多引擎是实现跨端UI最现实的方案。毕竟只有官方的demo,就连官方推荐的pigeonDemo[1]也没有链接到multiple_fluttersDemo[2]。至于为什么不继续使用容器混合开发?是不是觉得add_to_app这个方法的开发调试挺痛苦的,单元测试不好做。而且,在保持业务层不变的情况下,开发很多额外的插件来支持UI的成本还是很高的。实现及原理整个方案的实现是跨终端的UI组件化,如上图所示。跨端UI组件化优势:实现APP双端UI一致性,可部署为独立的WebDemo,提前进行UI走查。FlutterUI组件独立开发调试??,只关心API定义,不关心具体实现。解决开发使用痛点,降低开发难度曲线,自动生成并调用ComponentAPI到Native端,平滑开发使用成本。从开发过程的角度,一步步分析整个解决方案的关键点。定义UI组件组件定义采用YAML标准化语言定义RULE定义definitiondescriptionname组件名init初始化数据→List<{name(名称),type(类型),note(注释),default(默认值)}>options附加配置→{note(组件注解),autolayout(是否自动布局)}properties组件属性→List<{name(名称),type(类型),note(注释),default(默认值)}>方法提供外部methods→List<{name(名字),note(注释),inputs(引用列表)}>cb_methods提供回调方法→同上classes定义Class→ListTYPEsupportFlutter(definition)iOSAndroidStringNSStringStringint/long/doubleint/long/doubleInt/Long/DoubleboolBOOLBooleanMapNSDictionaryMapListNSArrayList<\Object>ListNSArray>ListImageUIImageBitmap有需要会继续添加:这样的asColor,ClassextendsClass、ClassuseClass*Example*ui_components.yaml*以一开始开发的Switch组件为例(后续章节会以它为例),定义如下:-name:Switchinit:-{name:title,type:String,note:title,default:--}-{name:textColor,type:String,note:default(off)文本颜色,default:"255,255,255,0.4"}-{name:textColorAtOn,type:String,note:whenonarts文字颜色,默认:"255,255,255,1"}options:note:GUI切换按钮组件autolayout:falseproperties:-{name:"on",type:bool,note:是否启用,default:false}FGUIComponentAPI生成Flutter开发包生成的调用类分为多个部分,.gitigore是自动生成的文件。文件结构如下:FlutterProject/#Flutter项目目录↓fgui/#FlutterGUIKit组件库↓ui_components.yaml#定义组件↓ui_components.dart#调用入口层(.gitigore)↓.api/#API索引,自动生成,在pigeon(.gitigore)中使用↓lib/#lib↓.caches/#组件基类和API实例,自动生成(.gitigore)↓switch.api.dart#pigeon生成类↓switch.base.dart#Component基类,用于封装api.dart↓{switch}/{switch.dart}#**进行组件开发**入口层(ui_components.dart)@pragma('vm:entry-point')voidcomponentSwitch()=>runApp(constfgui_switch.Switch());多个引擎的入口必须是根节点的方法,必须实现runAppAPIindex(.api/)//AUTOGENERATEAPI////注:自动生成,无需手动修改。import'package:pigeon/pigeon.dart';classSwitchConfig{///标题字符串?标题;///默认(关闭)文本颜色字符串?文本颜色;///开启时的文字颜色String?文本颜色AtOn;///“通用”当前环境语言Map?当前语言环境;///“常规”屏幕宽度(pt)加倍?屏幕宽度;///“通用”屏幕高度(pt)加倍?screenHeight;}@HostApi()abstractclassSwitchHostAPI{///触发跟踪voidwindTrack(StringeventName,inteventID,MapdetailInfo);///布局视图大小voidcontentSize(doublewidth,doubleheight);///更新-打开或不打开voidfUpdateOn(boolon);}@FlutterApi()抽象类SwitchFlutterAPI{///初始化配置voidconfig(SwitchConfigmaker);///是否开启voidon(bool?on);///"General"更新埋点补充信息voidupdateWindSupplementaryInfo(MapwindSupplementaryInfo);}以上代码是根据组件YAML定义的,通过FGUIComponentAPI生成,主要功能是为鸽子组件提供xx.api.dart代码生成。开发基类(xx.base.dart)的功能只是多端messageChannel的封装,其实离我们想要的组件基类还差得很远。你可以体验一下就知道了。所以调用基类的作用是进一步封装信鸽的api.dart,让开发者感知不到它是App的一个组件。只要调用/实现base.dart的方法,就可以独立调用add_to_app。如图所示基类重写了on属性的set/get。在设置上,如果独立使用,会使用widget.fUpdateOn(on)方法。如果是add_to_app方法,会调用api.dart中的host.fUpdateOn(on)通知Native,Native会通过messageChannerl接收消息。@protected方法是组件开发需要实现的方法。比如这里的Native需要横跨终端组件的宽度进行布局。开发端(xx.dart)///GUI开关按钮组件//////FIXEDLAYOUTclassSwitchextendsSwitchBase{constSwitch({Key?key,EventBus?eventBus}):super(key:key,eventBus:eventBus);@override_SwitchStatecreateState(){返回_SwitchState();}}class_SwitchStateextendsSwitchStateBase{@overrideWidgetbuild(BuildContextcontext){returnDirectionality(textDirection:TextDirection.ltr,child:Container(),//替换它!);}@overridevoidupdateCurrentLocale(Localelocale){setState((){});}}以上也是FGUIComponentAPI生成的初始代码,也是为了防止一些坑。比如最外层用Directionality包裹,因为multiple_flutters不能以MaterialApp为根,如果忽略Directionality,add_to_app的一些实现会报错,比如ListView,因为需要文本排序,很多人会忽略,因为main.dart基本上基于MaterialApp,它具有内置的Directionality实现。还有一个有趣的设计,因为Flutter被设计成状态驱动而不是方法驱动,所以在generation中也加入了最简单的EventBus方法,使得独立运行和add_to_app的实现统一起来。例如在测试demo中,直接通过UpdateBannersEvent修改组件数据,与App调用的updateBanners方法一致。当然测试工程也是自动生成的,填入关键代码即可。FGUIComponentAPI生成对iOS端的双端调用。从官方的例子我们可以知道一个FlutterEngineGroup包含了多个FlutterEngine实例。创建FlutterEngine实例时,需要指定一个Entrypoint。这就是上面入口层声明的componentSwitch。需要封装成View供iOS调用层使用。////FGUISwitch.h//AUTOGENERATEAPI////注:自动生成,无需手动修改。//#import"FGUIComponent.h"NS_ASSUME_NONNULL_BEGIN@interfaceFGUISwitchInitConfig:NSObject///Title@property(nonatomic,nullable,copy)NSString*title;///默认(关闭)文本颜色@property(nonatomic,nullable,copy)NSString*textColor;///开启时的文字颜色@property(nonatomic,nullable,copy)NSString*textColorAtOn;@end///[Flutter]:GUI开关按钮组件@interfaceFGUISwitch:FGUIComponent-(instancetype)initWithMaker:(void(^)(FGUISwitchInitConfig*make))blockhostVC:(UIViewController*)hostVCcontentSizeDidUpdateBlock:(void(^)(CGSizecontentSize))contentSizeDidUpdateBlock;//MARK:-ContentSize-(CGSize)intrinsicContentSize;//MARK:-Properties///@property(nonatomic,assign)BOOLon;@property(nonatomic,copy)void(^fUpdateOnBlock)(BOOLon);//MARK:-CustomerBlocks//MARK:-PublicMethods//MARK:-Creators@endNS_ASSUME_NONNULL_END以上为示例自动生成的callh文件。m文件太长,这里忽略显示。依赖和多项目使用通过反射生成调用代码。关键是需要从外部传入一个hostVC,内部通过addChild的形式将FlutterViewController添加到hostVC中。按照官方的例子,A??ndroid端是代码布局的形式,但是根据Android小伙伴的习惯,我们改成了支持xml布局的形式。/***AUTOGENERATEAPI*注意:自动生成,无需手动修改。*/.../***GUI开关按钮组件*/classFGUISwitch:FrameLayout{privatevarmFragmentManager:FragmentManager?=nullprivatevarmEngineBinding:FGUISwitchBinding?=nullprivatevalentryPoint="componentSwitch"privatevar_on:Boolean=false/***初始化*@paramtitle标题*@paramtextColor默认(关闭)文本颜色*@paramtextColorAtOn打开时的文本颜色*/funinit(fragmentManager:FragmentManager,title:String="--",textColor:String="255,255,255,0.4",textColorAtOn:String="255,255,255,1",){...}/***设置是否启用*/funsetOn(on:Boolean){_on=onmEngineBinding?.on(on)}/***是否启用*/fungetOn():Boolean{return_on}...}这里也简单的把生成的调用部分出来供大家参考。特别是因为Android无法模拟Interface形式的Class(这个OC真的很擅长反射),所以只能直接依赖Flutter包,不过好处是Android中的Flutter包是根据FlutterPlugin解包的,所以这不是一个大问题。样片效果说了很久的干货,我没有展示实际的样片效果给大家看。可以看到作者在Web上进行开发调试,开发简单、容易、清晰。因为也生成了VO(ViewModel)代码,所以也有很自然的VO/BO代码分离。它还补充了线下和线上的真实效果。参考资料[1]pigeonDemo:https://github.com/flutter/samples/tree/master/add_to_app/books[2]multiple_fluttersDemo:https://github.com/flutter/samples/tree/master/add_to_app/多重颤动