从零开始|构建 Flutter 多引擎渲染组件:Native 调用篇

639 阅读11分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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 是运行时语言,可以用动态反射来做很多事。

整体结构

image.png

FGUIComponentAPI 是我们独立的组件调用层库。可以看到它的组成也是跟前文组件建设一致,分为 fguipart_homepart_video 这几部分。

可以看它的 podspec 文件:

image.png

除了依赖一个项目基础库 GDFoudation 外,不再依赖任何库。看上去跟 Flutter 一点关系也没有。

common

仔细的同学可能看到了我们多了一个 common group, 这里面存放的是一些基类,用于简化整个反射流程:

image.png

简单画一个他们的依赖关系图:

image.png

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 代码,FGUIAlertDialogConfigFGUIAlertDialogFlutterAPI


// 实现 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

上面代码就是实现 FGUIAlertDialogFGUIComponentViewController

@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 完全当成 protocolinterface 必须提供实现,而实现就必须产生依赖。那基于 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 的生命周期上在一些情况下会有问题)。

image.png

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 多引擎渲染组件:跨端工具链篇》


感谢阅读,如果对你有用请点个赞 ❤️

中秋节GIF动图引导在看提示.gif