开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第13天,点击查看活动详情
前文传送门
《从零开始|构建 Flutter 多引擎渲染组件:先导篇》 《从零开始|构建 Flutter 多引擎渲染组件:Flutter 工程篇》 《从零开始|构建 Flutter 多引擎渲染组件:Flutter 代码篇》
(本文代码较多,分为 iOS、Android 两部分)
前文讲了如何一步步构建 Flutter 侧多引擎渲染组件框架,本文则会深入探讨下在 iOS、Android 源生侧我们是怎样思考实现调用端代码。
实现目标上着重这两个方向:
抹平:像使用原生组件一样的去使用 Flutter 组件,减少 Native 开发同学对 Flutter 代码的感知。
灾备:一旦 Flutter 组件出现异常,也可以内部实现改为原生替代,无需更改外部调用。
iOS
思考
虽然我们构建的 Flutter 多引擎渲染组件是很独立的,但对于 Flutter 整体架构,在 iOS 上, Flutter 打包成 framework 后,有且只有一个 main.dart 入口。所以业务代码和组件代码只能打包在一起(如果有可以分开打包实现不同的 App.xcframework 的方式,请评论告知,感激~)。
在多业务多项目的实际情况下,依赖关系就变得十分逆向,组件调用层代码去依赖业务层混合的 Flutter Framework,这在工程层面是不允许出现的。
那我们有什么方式可以解开吗?不然整个方案就没有什么意义。
好在 iOS 是运行时语言,可以用动态反射来做很多事。
整体结构
FGUIComponentAPI 是我们独立的组件调用层库。可以看到它的组成也是跟前文组件建设一致,分为 fgui、part_home、part_video 这几部分。
可以看它的 podspec 文件:
除了依赖一个项目基础库 GDFoudation 外,不再依赖任何库。看上去跟 Flutter 一点关系也没有。
common
仔细的同学可能看到了我们多了一个 common group, 这里面存放的是一些基类,用于简化整个反射流程:
简单画一个他们的依赖关系图:
FGUIComponentReflector
FGUIComponentReflector 只是一个声明头文件,把 Flutter 用到的类型都声明一遍,更优雅的使用反射 ~
/// 映射 FlutterEngine 及其内部属性方法
@protocol FGUIComponentFlutterEngine <NSObject>
@property (nonatomic, strong, nullable) id binaryMessenger;
@property (nonatomic) NSDictionary *registrars;
- (NSObject<FGUIComponentFlutterPluginRegistrar> *)registrarForPlugin:(NSString *)pluginKey;
- (void)destroyContext;
@end
/// 映射 FlutterEngineGroup 及其内部属性方法
@protocol FGUIComponentFlutterEngineGroup <NSObject>
- (instancetype)initWithName:(NSString *)labelPrefix project:(nullable id)project;
@optional
- (id)makeEngineWithEntrypoint:(nullable NSString *)entrypoint
libraryURI:(nullable NSString *)libraryURI;
@end
FGUIComponentManager
提供多引擎组件管理,直接看代码。
FGUIComponentManager.h
// 伪造一个 FlutterEngineGroup,让外部优雅的使用
@interface FGUIComponentFlutterEngineGroup : NSObject <FGUIComponentFlutterEngineGroup>
@end
// 提供给外部使用的多引擎管理类
@interface FGUIComponentManager : NSObject
// 声明是一个单例
AS_SINGLETON(GDFlutterComponentManager)
// 声明一个只读的 FlutterEngineGroup
@property (nonatomic, strong, readonly) FGUIComponentFlutterEngineGroup *engineGroup;
// 生产 FlutterEngine 实例
- (id<FGUIComponentFlutterEngine>)makeEngineWithEntrypoint:(nullable NSString *)entrypoint
libraryURI:(nullable NSString *)libraryURI;
@end
FGUIComponentManager.m
@implementation FGUIComponentFlutterEngineGroup
// 这里通过类名反射来初始化真实的 FlutterEngineGroup
- (nonnull instancetype)initWithName:(nonnull NSString *)labelPrefix project:(nullable id)project {
Class class = NSClassFromString(@"FlutterEngineGroup");
GDAssert(NSStringFromClass(class).length, @"FlutterEngineGroup is not found");
// 这里 self = FlutterEngineGroup 的真实实例,而不是 FGUIComponentFlutterEngineGroup 这个空壳类
self = [[class alloc] initWithName:labelPrefix project:project];
GDAssert(self, @"FlutterEngineGroup is not initialized");
return self;
}
@end
@interface FGUIComponentManager ()
/// flutter 多引擎
@property (nonatomic, strong, readwrite) id<FGUIComponentFlutterEngineGroup> engineGroup;
@end
@implementation FGUIComponentManager
DEF_SINGLETON(GDFlutterComponentManager) // 单例实现,宏具体在 `GDFoundation` 中,这里不体现
- (instancetype)init {
// 这里定义一个 components 作为模块名,让所有多引擎组件都使用同一组线程,方便调试定位问题。
return [self initWithModuleName:@"components"];
}
- (instancetype)initWithModuleName:(NSString *)moduleName {
self = [super init];
if (self) {
// 通过 FGUIComponentFlutterEngineGroup 构造一个 FlutterEngineGroup 实例
self.engineGroup = [[FGUIComponentFlutterEngineGroup alloc] initWithName:moduleName project:nil];
}
return self;
}
- (id<FGUIComponentFlutterEngine>)makeEngineWithEntrypoint:(NSString *)entrypoint libraryURI:(NSString *)libraryURI {
// 构造一个 FlutterEngine
id engine = [self.engineGroup makeEngineWithEntrypoint:entrypoint libraryURI:libraryURI];
return engine;
}
@end
FGUIComponentViewController
这一个 FlutterViewController 的控制类,本来试了也用 FGUIComponentFlutterEngineGroup 的方式做个伪装,但在 View 上不好处理,毕竟我们需要在 View 上加点料,最后是用组合的方式来实现。
FGUIComponentViewController.h
@interface FGUIComponentViewController : NSObject
// 返回真实的 FlutterViewController
@property (nonatomic, strong, readonly) UIViewController *flutterViewController;
// 使用 FlutterEngine 来初始化
- (instancetype)initWithEngine:(id<FGUIComponentFlutterEngine>)engine;
// 从内存中清理
- (void)clear;
@end
FGUIComponentViewController.m
// 伪造 FlutterViewController
@protocol FGUIComponentViewController <NSObject>
// 提供初始化方法
@optional
- (instancetype)initWithEngine:(id<FGUIComponentFlutterEngine>)engine
nibName:(nullable NSString*)nibName
bundle:(nullable NSBundle*)nibBundle;
@end
@interface FGUIComponentViewController () <FGUIComponentViewController>
@property (nonatomic, strong, readwrite) UIViewController *flutterViewController;
// 弱引用 FlutterEngine
@property (nonatomic, weak) id<FGUIComponentFlutterEngine> engine;
@end
@implementation FGUIComponentViewController
- (instancetype)initWithEngine:(id<FGUIComponentFlutterEngine>)engine {
self = [super init];
if (self) {
self.engine = engine;
[self setupFlutterView];
[self setupGestureRecognizer];
}
return self;
}
- (void)setupFlutterView {
Class class = NSClassFromString(@"FlutterViewController");
GDAssert(NSStringFromClass(class).length, @"Flutter.framework is not dependent");
// viewController 即是一个 FlutterViewController
UIViewController *viewController = [[class alloc] initWithEngine:self.engine nibName:nil bundle:nil];
GDAssert(viewController, @"flutterVC is not initialized");
[self warmUpEngine:viewController];
viewController.view.backgroundColor = UIColor.clearColor;
self.flutterViewController = viewController;
}
// 增加手势竞争,防止被 Native 代码拦截
- (void)setupGestureRecognizer {
UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] init];
panGestureRecognizer.cancelsTouchesInView = NO;
[self.flutterViewController.view addGestureRecognizer:panGestureRecognizer];
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] init];
tapGestureRecognizer.cancelsTouchesInView = NO;
[self.flutterViewController.view addGestureRecognizer:tapGestureRecognizer];
}
// 强制清理
- (void)clear {
[self.flutterViewController.view removeFromSuperview];
[self.flutterViewController removeFromParentViewController];
self.flutterViewController = nil;
}
/// 构建即预热引擎
- (void)warmUpEngine:(UIViewController *)viewController {
[viewController beginAppearanceTransition:YES animated:NO];
[viewController endAppearanceTransition];
[viewController beginAppearanceTransition:NO animated:NO];
[viewController endAppearanceTransition];
}
@end
FGUIComponentImage
这是 UIImage 外接纹理实现,不在这里做代码详述,有兴趣的同学看这篇,传送门 《Flutter 多引擎渲染,外接纹理实践》
FGUIComponent
FGUIComponent 组合以上的反射使用类,提供给外部一个封装的入口。
FGUIComponent.h
// 提供一些通用的参数,本本不涉及,忽略
@protocol FGUIComponentFuncProtocol <NSObject>
...
@end
@interface FGUIComponent : NSObject <FGUIComponentFuncProtocol>
/// 当前宿主 VC(只读)
@property (nonatomic, weak, readonly) UIViewController *hostVC;
/// 当前 FlutterVC (只读)
@property (nonatomic, strong, readonly) FGUIComponentViewController *flutterVC;
/// 提供初始化方法
- (instancetype)initWithHostVC:(UIViewController *)hostVC
flutterClass:(Class)flutterClass
entrypoint:(NSString *)entrypoint
libraryURI:(NSString *)libraryURI;
/// 返回 FlutterView 视图
- (UIView *)view;
/// 清理
- (void)clear;
// 禁用无参初始化方法
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
@end
FGUIComponent.m
@interface FGUIComponent ()
...
@end
@implementation FGUIComponent
- (instancetype)initWithHostVC:(UIViewController *)hostVC
flutterClass:(Class)flutterClass
entrypoint:(NSString *)entrypoint
libraryURI:(NSString *)libraryURI {
self = [super init];
if (self) {
...
self.hostVC = hostVC;
self.engine = [FGUIComponentManager.sharedInstance makeEngineWithEntrypoint:entrypoint libraryURI:libraryURI]; // 构建 FlutterEngine
[self registrarPlugins]; // 插件注册
self.flutterVC = [[flutterClass alloc] initWithEngine:self.engine]; // 构建 FlutterVC
// 宿主 VC 用 addChild 的方式把 FlutterViewController 加入到页面上
[hostVC addChildViewController:self.flutterVC.flutterViewController];
}
return self;
}
- (void)clear {
...
[self.flutterVC clear];
self.flutterVC = nil;
self.engine = nil;
}
- (void)registrarPlugins {
[NSClassFromString(@"FLTPathProviderPlugin") registerWithRegistrar:[self.engine registrarForPlugin:@"FLTPathProviderPlugin"]];
[NSClassFromString(@"SqflitePlugin") registerWithRegistrar:[self.engine registrarForPlugin:@"SqflitePlugin"]];
}
/// 返回 FlutterViewController.view
- (UIView *)view {
return self.flutterVC.flutterViewController.view;
}
@end
里面有一个插件注册的过程,这里也不做详述,有兴趣的同学看这篇,传送门《Flutter 多引擎渲染,组件支持 FlutterPlugin》
示例
还是以前文的 alert_dialog 组件为例,介绍一下我们最终暴露给组件使用方是什么样的。
构造
FGUIAlertDialog.h
@interface FGUIAlertDialogInitConfig : NSObject
/// 对话框标题
@property(nonatomic, nullable, copy) NSString *title;
/// 对话框内容
@property(nonatomic, nullable, copy) NSString *content;
/// 确认按钮文案
@property(nonatomic, nullable, copy) NSString *confirmText;
/// 是否显示取消
@property(nonatomic, assign) BOOL showCancel;
/// 取消按钮文案
@property(nonatomic, nullable, copy) NSString *cancelText;
@end
/// [Flutter]: 可选对话框
@interface FGUIAlertDialog : FGUIComponent
// 对外提供唯一的初始化方法
// 通过参数工厂 FGUIAlertDialogInitConfig 注入初始化参数
// 必须传入宿主 VC,可以是某个页面的 VC,也可以是全局 window 的根 VC
- (instancetype)initWithMaker:(void(^)(FGUIAlertDialogInitConfig *make))block hostVC:(UIViewController *)hostVC;
/// 点击确定
@property (nonatomic, copy) void(^onClickConfirmBlock)(void);
/// 点击取消
@property (nonatomic, copy) void(^onClickCancelBlock)(void);
// MARK: - Public Methods
/// 关闭弹窗
- (void)dismiss;
@end
可以看到,对外提供的是一个 FGUIAlertDialog 对象,它继承于 FGUIComponent,这样它也具备了 - (UIView *)view。
FGUIAlertDialog.m
(m 文件比较多,我们一部分一部分的讲一下)
@implementation FGUIAlertDialogInitConfig
- (instancetype)init {
self = [super init];
if (self) {
self.title = @"";
self.content = @"";
self.align = 2;
self.confirmText = @"";
self.showCancel = false;
self.cancelText = @"";
}
return self;
}
@end
FGUIAlertDialogInitConfig 工厂参数的实现,并且设置默认值,外部使用的时候就可以可选传参。
// 映射 FGUIAlertDialogConfig
@protocol FGUIAlertDialogConfigProtocol <NSObject>
/// 对话框标题
@property(nonatomic, copy) NSString *title;
/// 对话框内容
@property(nonatomic, copy) NSString *content;
/// 确认按钮文案
@property(nonatomic, copy) NSString *confirmText;
/// 是否显示取消
@property(nonatomic, strong) NSNumber *showCancel;
/// 取消按钮文案
@property(nonatomic, copy) NSString *cancelText;
@end
// 映射 FGUIAlertDialogFlutterAPI
@protocol FGUIAlertDialogFlutterAPIProtocol <NSObject>
- (instancetype)initWithBinaryMessenger:(id)binaryMessenger;
- (void)configMaker:(id)maker completion:(void(^)(NSError *error))completion;
- (void)dismissWithCompletion:(void(^)(NSError *error))completion;
@end
映射前文讲到的,Flutter pigeon 生成的 iOS 代码,FGUIAlertDialogConfig 和 FGUIAlertDialogFlutterAPI。
// 实现 FGUIAlertDialog 回调
@interface FGUIAlertDialogCallBack : NSObject
/// 点击确定
@property (nonatomic, copy) void(^onClickConfirmBlock)(void);
/// 点击取消
@property (nonatomic, copy) void(^onClickCancelBlock)(void);
@end
@implementation FGUIAlertDialogCallBack
- (instancetype)initWithEngine:(id<FGUIComponentFlutterEngine>)engine {
self = [super init];
if (self) {
// 注意这里的反射形式,反射获得类方法
void (*FGUIAlertDialogHostAPISetup)(id, id) = dlsym(RTLD_DEFAULT, "FGUIAlertDialogHostAPISetup");
// 调用 setup 类方法
FGUIAlertDialogHostAPISetup(engine.binaryMessenger, self);
}
return self;
}
// MARK: - FGUIAlertDialogHostAPI
// 实现 onClickConfirm
- (BOOL)onClickConfirmWithError:(NSError *_Nullable *_Nonnull)error {
GDBlockCallInMain(self.onClickConfirmBlock);
return YES;
}
// 实现 onClickCancel
- (BOOL)onClickCancelWithError:(NSError *_Nullable *_Nonnull)error {
GDBlockCallInMain(self.onClickCancelBlock);
return YES;
}
@end
这里是实现 FGUIAlertDialogHostAPISetup 类方法中必须重写的方法,再回调给 FGUIAlertDialog。
@interface FGUIAlertDialogViewController : FGUIComponentViewController
@property (nonatomic, strong) id<FGUIAlertDialogFlutterAPIProtocol> api;
@property (nonatomic, strong) FGUIAlertDialogCallBack *cb;
@end
@implementation FGUIAlertDialogViewController
- (instancetype)initWithEngine:(id<FGUIComponentFlutterEngine>)engine {
self = [super initWithEngine:engine];
if (self) {
// 实现 FGUIAlertDialogCallBack
self.cb = [[FGUIAlertDialogCallBack alloc] initWithEngine:engine];
// 反射获取 FGUIAlertDialogFlutterAPI 类名,这是由 pigeon 生成的,在 Flutter framework 里
Class class = NSClassFromString(@"FGUIAlertDialogFlutterAPI");
GDAssert(NSStringFromClass(class).length, @"FGUIAlertDialogFlutterAPI is not found");
self.api = [[class alloc] initWithBinaryMessenger:engine.binaryMessenger];
}
return self;
}
@end
上面代码就是实现 FGUIAlertDialog 的 FGUIComponentViewController
@interface FGUIAlertDialog ()
@property (nonatomic, strong) FGUIAlertDialogInitConfig *maker;
@end
@implementation FGUIAlertDialog
- (instancetype)initWithMaker:(void(^)(FGUIAlertDialogInitConfig *make))block hostVC:(UIViewController *)hostVC {
// 这里初始化传入 componentAlertDialog 终节点
self = [super initWithHostVC:hostVC
flutterClass:FGUIAlertDialogViewController.class
entrypoint:@"componentAlertDialog"
libraryURI:@"package:fgui/ui_components.dart"];
if (self) {
self.maker = [[FGUIAlertDialogInitConfig alloc] init];
block(self.maker);
// 初始化 maker 入参
id<FGUIAlertDialogConfigProtocol> maker = [[NSClassFromString(@"FGUIAlertDialogConfig") alloc] init];
maker.title = self.maker.title;
maker.content = self.maker.content;
maker.confirmText = self.maker.confirmText;
maker.showCancel = @(self.maker.showCancel);
maker.cancelText = self.maker.cancelText;
// 调用初始化方法
[self.flutterController.api configMaker:maker completion:^(NSError *error) {}];
__weak typeof(self)weakSelf = self;
// 实现 onClick 回调
[self.flutterController.cb setOnClickConfirmBlock:^() {
__strong typeof(weakSelf)strongSelf = weakSelf;
GDBlockCallInMain(strongSelf.onClickConfirmBlock);
}];
// 实现 onCancel 回调
[self.flutterController.cb setOnClickCancelBlock:^() {
__strong typeof(weakSelf)strongSelf = weakSelf;
GDBlockCallInMain(strongSelf.onClickCancelBlock);
}];
}
return self;
}
- (FGUIAlertDialogViewController *)flutterController {
return (FGUIAlertDialogViewController *)self.flutterVC;
}
- (void)dismiss {
// 调用 api 的 dismiss
[self.flutterController.api dismissWithCompletion:^(NSError *error) {}];
}
@end
以上就是整个 alert_dialog 组件的构造全过程了,代码比较多,需要耐心的看一下 ~
当然这实在是太多代码了,在下一篇“跨端工具链”中我们会把上面整个过程都自动生成。
调用
列一下关键代码,因为 Alert 比较特殊,可以封装成一个类方法来直接调用。
@implementation GUIAlert
...
+ (instancetype)showWithTitle:(nullable NSString *)title
content:(nullable NSString *)content
buttonText:(nullable NSString *)buttonText
showCancel:(BOOL)showCancel
cancelButtonText:(nullable NSString *)cancelButtonText
linkBlock:(nullable void(^)(NSString *url))linkBlock
cancelBlock:(nullable void(^)(void))cancelBlock
completeBlock:(void(^)(void))completeBlock {
GUIAlert *instance = [[GUIAlert alloc] initWithMaker:^(FGUIAlertDialogInitConfig *make) {
make.title = title ?: @"";
make.content = content ?: @"";
make.confirmText = buttonText ?: @"确定";
make.cancelText = cancelButtonText ? : @"取消";
make.showCancel = showCancel;
}];
instance.cancelBlock = cancelBlock;
instance.completeBlock = completeBlock;
[instance show];
return instance;
}
// MARK: - Private
- (instancetype)initWithMaker:(void(^)(FGUIAlertDialogInitConfig *make))maker {
self = [super init];
if (self) {
// getCurrentVC 为当前 VC
self.alertDialog = [[FGUIAlertDialog alloc] initWithMaker:maker hostVC:[self getCurrentVC]];
GDWeakify(self);
[self.alertDialog setOnClickConfirmBlock:^(void) {
GDStrongify(self);
[self dismiss:^(void) {
GDBlockCallInMain(self.completeBlock);
}];
}];
[self.alertDialog setOnClickCancelBlock:^(void) {
GDStrongify(self);
[self dismiss:^(void) {
GDBlockCallInMain(self.cancelBlock);
}];
}];
}
return self;
}
// 显示
- (void)show {
self.backgroundColor = UIColor.clearColor;
[self.alertDialog.hostVC.view addSubview:self];
self.frame = self.alertDialog.hostVC.view.bounds;
[self addSubview:self.alertDialog.view];
self.alertDialog.view.frame = self.frame;
// 背景蒙层显示动画
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.6];
} completion:nil];
}
// 隐藏
- (void)dismiss:(void(^)(void))completeBlock {
[self.alertDialog dismiss];
// 背景蒙层消失动画
[UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.backgroundColor = UIColor.clearColor;
[self layoutIfNeeded];
} completion:^(BOOL finished) {
[self.alertDialog clear];
[self removeFromSuperview];
GDBlockCallInMain(completeBlock);
}];
}
那使用上,即可 [GUIAlert showWithTile:] 来直接显示警告框。
Android
实现方式
Android 实现上比较简单,最初也是采用反射的形式来规避依赖的问题。但是有制约性,因为 Android 没办法像 iOS 用 interface 完全当成 protocol。interface 必须提供实现,而实现就必须产生依赖。那基于 Model 的入参就没办法处理。
幸运的是,Flutter 在 Android 上打包是根据插件分包的,所以我们可以直接依赖所需要的包即可。
build.gradle
dependencies {
...
api "com.gaoding.part_home:part_home_${rootProject.ext.flutter_env}:${rootProject.ext.flutter_aar_version}"
api "com.gaoding.fgui:fgui_${rootProject.ext.flutter_env}:${rootProject.ext.flutter_aar_version}"
api "com.gaoding.part_video:part_video_${rootProject.ext.flutter_env}:${rootProject.ext.flutter_aar_version}"
}
构造
那可以直接依赖,就不需要反射代码了,整体相对 iOS 精简很多。对外提供的是 FrameLayout 方便使用 xml 布局(这里据 Android 同事讲,FrameLayout 的生命周期上在一些情况下会有问题)。
FGUIEngine
还是要构造一个单例 FlutterEngineGroup 用于统一管理。
class FGUIEngine {
companion object {
val group: FlutterEngineGroup = FlutterEngineGroup(GaodingApplication.getContext())
}
}
FGUIAlertDialogBinding
跟 Flutter Demo 工程保持一致,提供一个 binding 类来处理跟 FlutterEngine 的交互。
FGUIAlertDialogBinding.kt
internal class FGUIAlertDialogBinding(
// 上下文
context: Context,
// 终节点
entrypoint: String,
// 工厂入参
private val config: AlertDialogConfig
) {
var mEngine: FlutterEngine
private set
var mFlutterApi: AlertDialogFlutterAPI? = null
private set
var mCallback: AlertDialogHostAPI? = null
init {
// FlutterEngineGroup
val group = FGUIEngine.group
// 获取终节点
val dartEntrypoint =
DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
"package:fgui/ui_components.dart", // 注意要写入口
entrypoint
)
// 构建 FlutterEngine
mEngine = group.createAndRunEngine(context, dartEntrypoint)
}
fun attach() {
val binaryMessenger = mEngine.dartExecutor.binaryMessenger
// 拿到 AlertDialogFlutterAPI
mFlutterApi = AlertDialogFlutterAPI(binaryMessenger)
// 调用初始化 config
mFlutterApi?.config(config, mReplyCallback);
// 绑定回调
AlertDialogHostAPI.setup(binaryMessenger, mCallback)
}
fun detach() {
// 销毁引擎
mEngine.destroy()
}
fun dismiss() {
mFlutterApi?.dismiss(mReplyCallback)
}
// 这个是 pigeon 要求的必须有 callback,可以提供一个通用的即可。
private val mReplyCallback by lazy {
AlertDialogFlutterAPI.Reply<Void> { }
}
}
FGUIAlertDialog
使用上面的 FGUIAlertDialogBinding 构造一个 FrameLayout 给 XML 布局。
/**
* 可选对话框
*/
class FGUIAlertDialog : FrameLayout {
private var mFragmentManager: FragmentManager? = null
private val mEngineId = UUID.randomUUID().toString()
private var mEngineBinding: FGUIAlertDialogBinding? = null
private val entryPoint = "componentAlertDialog"
var mCallback: Callback? = null
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
/**
* 初始化
* @param title 对话框标题
* @param content 对话框内容
* @param confirmText 确认按钮文案
* @param showCancel 是否显示取消
* @param cancelText 取消按钮文案
*/
fun init(
fragmentManager: FragmentManager,
title: String = "",
content: String = "",
confirmText: String = "",
showCancel: Boolean = false,
cancelText: String = "",
) {
if (mFragmentManager != null) {
return
}
mFragmentManager = fragmentManager
// 构建配置
val config = initConfig(title, content, confirmText, showCancel, cancelText) ?: return
// 构建 bindings
val engineBindings =
FGUIAlertDialogBinding(context = context, entrypoint = entryPoint, config)
mEngineBinding = engineBindings
val invokeHandler = AlertDialogHostAPIHandler()
engineBindings.mCallback = invokeHandler
// engine
val engine = engineBindings.mEngine
FlutterEngineCache.getInstance().put(mEngineId, engine)
// 添加 fragment
val flutterFragment = FlutterFragment.withCachedEngine(mEngineId).renderMode(RenderMode.texture).build<FlutterFragment>()
flutterFragment.lifecycle.addObserver(object : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
FlutterEngineCache.getInstance().remove(mEngineId)
mEngineBinding?.detach()
}
}
})
fragmentManager.beginTransaction().add(id, flutterFragment).commitAllowingStateLoss()
// attach bindings
engineBindings.attach()
}
...
interface Callback {
fun onClickConfirm() {}
fun onClickCancel() {}
}
/**
* 关闭弹窗
*/
fun dismiss() {
mEngineBinding?.dismiss()
}
inner class AlertDialogHostAPIHandler: AlertDialogHostAPI {
override fun onClickConfirm() {
mCallback?.onClickConfirm()
}
override fun onClickCancel() {
mCallback?.onClickCancel()
}
}
总结
调用端代码并不难理解,就是十分的繁琐,构建出一个 Flutter 多引擎渲染组件后,要考虑的就是如何批量生产多个组件,减少开发投入的时间。下一篇就着重于如何优雅的建设跨端工具链,让组件开发者只关注 Flutter UI 代码。
预告
《从零开始|构建 Flutter 多引擎渲染组件:跨端工具链篇》
感谢阅读,如果对你有用请点个赞 ❤️