@IT·互联网

Flutter多引擎路由开发实践

2024-09-02  本文已影响0人  希尔罗斯沃德_董

一、引言

背景

Flutter 多引擎模式允许在一个应用中同时运行多个 Flutter 引擎实例。这种模式特别适用于一些复杂的应用场景,如跨平台应用和需要独立状态管理的子模块。

在Flutter跨平台开发中,难免会遇到Flutter和原生交互的问题,特别是Flutter和原生页面切换问题。在单引擎方案中(常见的如咸鱼的FlutterBoost),管理Flutter与原生页面之间的路由可能会比较复杂,特别是在需要在原生和Flutter页面之间频繁切换的情况下。多引擎方案允许为不同的页面或模块创建独立的引擎,使得路由管理更为直观和灵活。

在跨平台应用中,可能会有多个独立开发的模块,它们各自有自己的状态管理需求。通过多引擎模式,每个模块可以运行在独立的 Flutter 引擎上,确保它们之间的状态不会互相干扰。此外,某些大型应用可能需要将不同的功能模块分开处理,以实现更好的模块化和独立性,这时多引擎模式也是一个理想的解决方案。

多引擎路由方案是为了解决如何在多个 Flutter 引擎之间高效地管理页面导航和状态的问题。随着应用复杂性的增加,传统的单引擎路由方式可能无法满足需求,比如在独立的模块间实现无缝导航等。因此,设计一个合适的多引擎路由方案,可以帮助开发者更好地管理应用的整体结构,并提升用户体验。

目标

在设计Flutter多引擎页面路由方案时,主要的目标和解决的问题包括以下几个方面:

  1. 性能优化
  1. 页面状态管理
  1. 路由的灵活性与扩展性
  1. 模块化设计
  1. 原生与Flutter的无缝集成
  1. 导航历史的管理

这些目标和问题构成了设计Flutter多引擎页面路由方案时的核心关注点,通过有效的解决这些问题,能够实现一个高效、灵活、可扩展的多引擎路由方案。

二、基础概念与原理

2.1 Flutter 多引擎模式

Flutter 多引擎模式是一种在应用中同时使用多个Flutter引擎的架构方案,允许在一个应用中同时运行多个Flutter引擎实例。每个引擎实例可以独立管理其各自的路由、页面和状态,彼此之间互不干扰。这种模式在复杂应用中,特别是需要在Flutter页面和原生页面之间频繁切换时,提供了更大的灵活性。

2.2 为什么使用多引擎模式?

2.3 Flutter 页面路由机制

Flutter 提供了一套完整的路由机制来管理页面导航,主要包括 NavigatorRoute 这两个核心概念。

Navigator:

Route:

管理页面路由:

2.4 单引擎情况下原生页面和 Flutter 页面之间的路由

在单引擎的情况下,原生页面和 Flutter 页面之间的路由跳转会遇到以下挑战:

状态管理的复杂性:

动画和体验不一致:

深度链接和全局导航的处理:

性能问题:

通过回顾这些问题,我们可以更清晰地理解为什么在复杂应用中需要引入多引擎模式,以及多引擎页面路由方案所要解决的核心问题。

三、多引擎页面路由的挑战

在多引擎模式下,每个 Flutter 引擎实例都是独立的,这意味着它们各自维护独立的内存空间、状态和生命周期。因此,如何在多个引擎之间共享状态以及处理相关的挑战,成为了一个关键问题。

3.1 基础性的问题

一致性和同步问题

性能问题

状态的生命周期管理

安全性问题

3.2 多引擎状态共享

使用平台通道(Platform Channels):

通过共享存储(Shared Storage):

引入中间件或事件总线(Event Bus):

3.3 路由一致性

在多引擎模式下,不同的引擎可能负责不同的功能模块,这样的设计虽然能够有效地隔离模块、提升性能,但是在管理路由时,会带来一定的复杂性。

在多引擎模式下,确保路由一致性需要综合考虑不同的同步机制和设计模式。通过统一的路由管理服务、原生平台的中介作用、共享的路由栈或者事件总线,能够有效地管理多个引擎之间的路由状态同步,从而提升应用的可靠性和用户体验。

3.4 资源管理

在多引擎模式下,资源管理变得尤为重要,因为每个引擎独立运行,可能导致资源的重复加载和浪费。要在这种情况下高效地管理资源,需要采取一系列措施,确保资源的合理使用与优化。

3.4.1 共享资源管理层

建立一个共享的资源管理层,用于管理各个引擎之间的资源访问与分配。这个层可以作为一个全局服务,通过依赖注入或单例模式进行共享。

实现思路

3.4.2 统一的网络连接管理

在多引擎模式下,多个引擎可能会同时发起网络请求。为了避免重复请求和网络连接浪费,建议建立统一的网络连接管理机制。

在多引擎模式下,高效的资源管理至关重要。通过共享资源管理层、统一的网络连接管理、资源复用与懒加载、引擎生命周期管理以及异步任务与后台处理等方法,可以有效减少资源浪费,提升应用性能和用户体验。针对具体的应用需求,可以灵活组合以上策略,确保资源的高效使用。

实现思路

3.4.3 资源复用与懒加载

资源复用与懒加载是有效的资源管理策略,特别是在多引擎模式下。可以通过懒加载减少初始资源消耗,通过资源复用避免重复加载。

实现思路

3.4.4 引擎生命周期管理

管理各个引擎的生命周期,合理地分配和释放资源。未使用的引擎应该及时销毁或休眠,以释放资源。

实现思路

3.4.5 异步任务与后台处理

对于耗时的资源操作,可以采用异步任务或后台处理,避免阻塞主线程,提升应用的整体性能。

实现思路

3.5 第三方库的支持

在使用多引擎替代单引擎时,确实需要特别注意一些第三方库和插件的兼容性问题。以下是一些关键点和注意事项:

3.5.1 Channel 管理

问题:在多引擎环境中,每个引擎都可能需要独立的 Channel 实例。这意味着原生与 Flutter 的通信通道需要在每个引擎之间正确管理。

注意事项

3.5.2 状态管理

问题:某些第三方库可能假设只有一个 Flutter 引擎在运行,这可能导致在多引擎环境下出现状态同步或管理问题。

注意事项

3.5.3 资源共享

问题:在多引擎环境中,多个引擎之间的资源(如 GPU 上下文、字体资源等)需要有效共享,以避免不必要的内存开销和性能问题。

注意事项

3.5.4 插件兼容性

问题:一些插件可能依赖于单引擎的特性或行为,在多引擎环境下可能不兼容或表现异常。

注意事项

3.5.5 解决方案

在进行多引擎替代单引擎的过程中,保持对第三方库和插件兼容性的关注,并且通过详细的测试来确保系统的稳定性和性能。

在多引擎环境中遇到第三方库不支持的情况时,可以通过以下两种主要解决方案来解决这些问题:

本地化第三方库,自行维护 Channel
自行实现替代插件

通过本地化第三方库和自行实现替代插件,能够有效应对多引擎环境中第三方库不兼容的问题。这些解决方案可以确保项目在复杂环境中的稳定性和兼容性,同时提升系统的性能和可靠性。实施这些策略时,务必进行充分的测试和验证,以确保所有修改和插件都能符合项目的需求和标准。

四、多引擎路由方案设计

4.1 设计原则

在设计多引擎路由方案时,需要遵循以下核心原则:

4.2 架构概览

以下提供方案的总体架构图,展示插件的各个层级和模块的基本构成。

4.2.1 多引擎插件(Flutter Meteor)架构

截屏2024-09-03 17.44.46.png

4.2.2 多引擎插件(Flutter Meteor)架构简介

Flutter层:

Channel层:

Native层:

4.3 核心组件

五、实现细节

5.1 引擎管理 (EngineManager)

如何在 Flutter 应用中初始化多个引擎,并且如何管理它们的生命周期?

利用FlutterEngineGroup来创建和管理多个引擎,这样引擎的生命周期由FlutterEngineGroup来自动维护。在 Flutter 应用中使用 FlutterEngineGroup 可以方便地创建和管理多个 FlutterEngine 实例。FlutterEngineGroup 允许多个引擎共享 Dart VM 的资源,从而减少内存占用和启动时间。

摘自multiple-flutters

在 Android 和 iOS 上添加多个 Flutter 实例的主要 API 基于新的 FlutterEngineGroup 类(Android API,iOS API)来构建 FlutterEngines,而不是以前使用的 FlutterEngine 构造器。

尽管 FlutterEngine API 是直接的且更容易使用,但从相同的 FlutterEngineGroup 中产生的 FlutterEngine 具有性能优势,可以共享许多常见的、可重复使用的资源,例如 GPU 上下文、字体度量和隔离组快照,从而实现更快的初始渲染延迟和更低的内存占用。

FlutterEngineGroup 中产生的 FlutterEngines 可以像通常构建的缓存 FlutterEngines 一样连接到 UI 类,如 FlutterActivityFlutterViewController

FlutterEngineGroup 创建的第一个 FlutterEngine 不需要继续存在,只要至少有一个 FlutterEngine 存活,后续的 FlutterEngines 仍可以共享资源。

FlutterEngineGroup 创建第一个 FlutterEngine 的性能特性与以前使用构造器构建 FlutterEngine 相同。

FlutterEngineGroup 中的所有 FlutterEngines 都被销毁后,下一次创建的 FlutterEngine 具有与第一次引擎相同的性能特性。

FlutterEngineGroup 本身不需要在所有生成的引擎之后继续存在。销毁 FlutterEngineGroup 不会影响现有的生成引擎,但会移除生成其他共享资源的 FlutterEngines 的能力。

代码示例(以iOS为例)

class MeteorEngineManager: NSObject {

    private static let channelProviderList = MeteorWeakArray<FlutterMeteorChannelProvider>()

    // FlutterEngineGroup 用于管理所有引擎
    private static let flutterEngineGroup = FlutterEngineGroup(name: "itbox.meteor.flutterEnginGroup", project: nil)

    public static func createFlutterEngine(options: MeteorEngineGroupOptions? = nil) -> FlutterEngine  {

        var arguments: Dictionary<String, Any> = Dictionary<String, Any>.init()

        let initialRoute = options?.initialRoute
        let entrypointArgs = options?.entrypointArgs
        if(initialRoute != nil) {
            arguments["initialRoute"] = initialRoute
        }
        if(initialRoute != nil) {
            arguments["routeArguments"] = entrypointArgs
        }

        var entrypointArgList:Array<String> = Array<String>.init()
        do {
            let jsonData = try JSONSerialization.data(withJSONObject: arguments, options: .prettyPrinted)
            if let jsonString = String(data: jsonData, encoding: .utf8) {
                entrypointArgList.append(jsonString)
            }
        } catch {
            print("Error converting dictionary to JSON")
        }

        // 创建新引擎
        let engineGroupOptions = FlutterEngineGroupOptions.init()
        engineGroupOptions.entrypoint = options?.entrypoint
        engineGroupOptions.initialRoute = initialRoute
        engineGroupOptions.entrypointArgs = entrypointArgList
        engineGroupOptions.libraryURI = options?.libraryURI
        let flutterEngine: FlutterEngine = flutterEngineGroup.makeEngine(with: engineGroupOptions)
//        engineCache[flutterEngine.binaryMessenger] = flutterEngine
        return flutterEngine
    }

    /// 第一个引擎的Channel
    public static func firstEngineChannelProvider() -> FlutterMeteorChannelProvider? {
        return channelProviderList.allObjects.first
    }

    /// 最后一个引擎的Channel
    public static func lastEngineChannelProvider() -> FlutterMeteorChannelProvider? {
        return channelProviderList.allObjects.last
    }

    /// 所有引擎的Channel
    public static func allEngineChannelProvider() -> [FlutterMeteorChannelProvider] {
        return channelProviderList.allObjects
    }

    /// 保存Channel
    public static func saveEngineChannelProvider(provider: FlutterMeteorChannelProvider) {
        if !channelProviderList.contains(provider) {
            channelProviderList.add(provider)
        }
    }
}

代码中通过弱引用列表channelProviderList来维护各个引擎的methodChannel, 使得原生和Flutter或者Flutter引擎之间可以通过MethodChannel相互传递数据、同步状态和路由跳转等。

5.2 路由导航(Navigator)

在多引擎模式下,不同的引擎可能负责不同的功能模块,这样的设计虽然能够有效地隔离模块、提升性能,但是在管理全局路由时,会带来一定的复杂性。为了确保全局路由的一致性,需要有专门的路由管理机制。

5.2.1 页面路由管理

5.2.2 导航栈管理

Flutter和Native各自维护各自的路由栈,提供全局接口,通过MethodChannel 可以动态获取当前整个应用的路由栈,;利用这个全局路由栈可以使得原生也能像纯Flutter页面跳转一样实现pushReplacementNamed、pushNamedAndRemoveUntil、popUntil等操作,保证跳转方法的一致性;

导航栈结构示例:

截屏2024-09-03 17.45.04.png

FlutterContainer在原生对应的是iOS-FlutterViewController, Android-FlutterActivity;

NativeContainer在原生对应的是iOS-UIViewViewController, Android-Activity;

示例中的导航栈:FlutterPage1->...->FlutterPage5->NativeContainerB->FlutterPage6->...->FlutterPage8->FlutterPage9->...->FlutterPage12->NativeContainerE

在页面的导航过程中也是按着这个顺序push和pop操作,原生和Flutter的Navigator相互配合,在适当的时机切换页面。

5.2.3 Navigator工作流程

push跳转逻辑示例:

截屏2024-09-03 17.45.15.png

5.3 事件总线(EventBus)

5.3.1 EventBus工作流程

截屏2024-09-03 17.45.24.png

5.3.2 Flutter代码示例

import 'package:flutter/services.dart';
import 'package:flutter_meteor/engine/engine.dart';
import 'package:hz_tools/hz_tools.dart';

typedef MeteorEventBusListener = void Function(dynamic arguments);

class MeteorEventBusListenerItem {
  final String? listenerId;
  final MeteorEventBusListener listener;

  MeteorEventBusListenerItem(this.listenerId, this.listener);
}

class MeteorEventBus {
  final BasicMessageChannel methodChannel =
      const BasicMessageChannel('itbox.meteor.multiEnginEventChannel', StandardMessageCodec());

  // 工厂方法构造函数 - 通过MeteorEventBus()获取对象
  factory MeteorEventBus() => _getInstance();

  // instance的getter方法 - 通过MeteorEventBus.instance获取对象2
  static MeteorEventBus get instance => _getInstance();

  // 静态变量_instance,存储唯一对象
  static MeteorEventBus? _instance;

  // 获取唯一对象
  static MeteorEventBus _getInstance() {
    _instance ??= MeteorEventBus._internal();
    return _instance!;
  }

  //初始化...
  MeteorEventBus._internal() {
    //初始化其他操作...
    // _pluginPlatform = MeteorMethodChannel();

    methodChannel.setMessageHandler(
      (message) async {
        HzLog.d('收到来自原生的通知message:$message');
        receiveEvent(message);
      },
    );
  }

  final Map<String, List<MeteorEventBusListenerItem>> _listenerMap = {};

  /// 添加订阅者-接收事件
  static void addListener(
      {required String eventName, String? listenerId, required MeteorEventBusListener listener}) {
    HzLog.t(
        'MeteorEventBus addListener isMain:${MeteorEngine.isMain} eventName:$eventName, listener:$listener');
    var list = instance._listenerMap[eventName];
    list ??= <MeteorEventBusListenerItem>[];
    list.add(MeteorEventBusListenerItem(listenerId, listener));
    instance._listenerMap[eventName] = list;
  }

  /// 移除订阅者-结束事件
  /// 当listener 为空时会移除eventName的所有listener,因此慎用
  static void removeListener(
      {required String eventName, String? listenerId, MeteorEventBusListener? listener}) {
    HzLog.t(
        'MeteorEventBus removeListener isMain:${MeteorEngine.isMain} eventName:$eventName, listener:$listener');
    var list = instance._listenerMap[eventName];
    if (eventName.isEmpty || list == null) return;
    if (listenerId != null) {
      list.removeWhere(
        (element) => element.listenerId == listenerId,
      );
    } else if (listener != null) {
      list.removeWhere(
        (element) => element.listener == listener,
      );
    } else {
      list.clear();
    }
    instance._listenerMap[eventName] = list;
  }

  /// 已加订阅者-发送事件
  /// eventName 事件名称
  /// withMultiEngine 是否发送多引擎,默认true表示支持多引擎
  /// data 传送的数据
  static void commit({required String eventName, bool? withMultiEngine = true, dynamic data}) {
    HzLog.t(
        'MeteorEventBus commit isMain:${MeteorEngine.isMain} eventName:$eventName, data:$data, withMultiEngine:$withMultiEngine');
    if (withMultiEngine == true) {
      /// 多引擎则交给原生处理
      commitToMultiEngine(eventName: eventName, data: data);
    } else {
      /// 如果只支持当前引擎则直接调用
      commitToCurrentEngine(eventName: eventName, data: data);
    }
  }

  static void commitToCurrentEngine({required String eventName, dynamic data}) {
    var list = instance._listenerMap[eventName];
    HzLog.t(
        'MeteorEventBus commitToCurrentEngine isMain:${MeteorEngine.isMain} eventName:$eventName, data:$data, listeners:$list');
    if (list == null) return;
    int len = list.length - 1;
    //反向遍历,防止在订阅者在回调中移除自身带来的下标错位
    if (list.isNotEmpty) {
      for (var i = len; i > -1; --i) {
        MeteorEventBusListener listener = list[i].listener;
        listener.call(data);
      }
    }
  }

  static Future<dynamic> commitToMultiEngine({required String eventName, dynamic data}) async {
    HzLog.d(
        'MeteorEventBus commitToMultiEngine isMain:${MeteorEngine.isMain} eventName:$eventName, data:$data');

    /// 如果支持多引擎则交给原生处理
    Map<String, dynamic> methodArguments = {};
    methodArguments['eventName'] = eventName;
    methodArguments['data'] = data;
    // final result = await instance.methodChannel
    //     .invokeMethod(MeteorChannelMethod.multiEngineEventCallMethod, methodArguments);
    final result = await instance.methodChannel.send(methodArguments);
    HzLog.t('MeteorEventBus commitToMultiEngine isMain:${MeteorEngine.isMain} result:$result');
    return result;
  }

  static void receiveEvent(dynamic event) {
    if (event is Map) {
      final eventMap = event;
      final eventName = event['eventName'];
      HzLog.t('MeteorEventBus isMain:${MeteorEngine.isMain} allListeners:${instance._listenerMap}');
      var list = instance._listenerMap[eventName];
      list?.forEach((listenerItem) {
        listenerItem.listener.call(eventMap['data']);
      });
      HzLog.d('MeteorEventBus isMain:${MeteorEngine.isMain} eventName:$eventName, listeners $list');
    }
  }
}

5.4 共享缓存(SharedMemoryCache)

共享缓存用于在多个Flutter引擎之间或Flutter与原生之间共享状态信息。共享缓存可以分为内存缓存和磁盘缓存两种方式。

为了使得各个Flutter引擎以及Flutter与原生之间都能够共享缓存,缓存的实际存储应放置在原生端。需要共享的状态会被写入同一个共享缓存中,各个引擎通过各自的Channel与原生端通信,从而实现状态的存取和同步。这样不仅能够确保状态的统一管理,还可以避免因多个引擎独立运行而带来的数据不一致问题。

5.4.1 缓存结构示例图

截屏2024-09-03 17.45.48.png

如图所示缓存在原生端实现,每个引擎都有对应的Channel和原生进行通信,通过Channel存取共享状态。

5.4.2 Flutter代码示例

// Autogenerated from Pigeon (v21.1.0), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers

import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;

import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';

PlatformException _createConnectionError(String channelName) {
  return PlatformException(
    code: 'channel-error',
    message: 'Unable to establish connection on channel: "$channelName".',
  );
}

class _PigeonCodec extends StandardMessageCodec {
  const _PigeonCodec();
}

class SharedCacheApi {
  /// Constructor for [SharedCacheApi].  The [binaryMessenger] named argument is
  /// available for dependency injection.  If it is left null, the default
  /// BinaryMessenger will be used which routes to the host platform.
  SharedCacheApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
      : __pigeon_binaryMessenger = binaryMessenger,
        __pigeon_messageChannelSuffix =
            messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
  final BinaryMessenger? __pigeon_binaryMessenger;

  static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();

  final String __pigeon_messageChannelSuffix;

  Future<void> setString(String key, String? value) async {
    final String __pigeon_channelName =
        'cn.itbox.flutter_meteor.CacheApi.setString$__pigeon_messageChannelSuffix';
    final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
      __pigeon_channelName,
      pigeonChannelCodec,
      binaryMessenger: __pigeon_binaryMessenger,
    );
    final List<Object?>? __pigeon_replyList =
        await __pigeon_channel.send(<Object?>[key, value]) as List<Object?>?;
    if (__pigeon_replyList == null) {
      throw _createConnectionError(__pigeon_channelName);
    } else if (__pigeon_replyList.length > 1) {
      throw PlatformException(
        code: __pigeon_replyList[0]! as String,
        message: __pigeon_replyList[1] as String?,
        details: __pigeon_replyList[2],
      );
    } else {
      return;
    }
  }

  Future<String?> getString(String key) async {
    final String __pigeon_channelName =
        'cn.itbox.flutter_meteor.CacheApi.getString$__pigeon_messageChannelSuffix';
    final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
      __pigeon_channelName,
      pigeonChannelCodec,
      binaryMessenger: __pigeon_binaryMessenger,
    );
    final List<Object?>? __pigeon_replyList =
        await __pigeon_channel.send(<Object?>[key]) as List<Object?>?;
    if (__pigeon_replyList == null) {
      throw _createConnectionError(__pigeon_channelName);
    } else if (__pigeon_replyList.length > 1) {
      throw PlatformException(
        code: __pigeon_replyList[0]! as String,
        message: __pigeon_replyList[1] as String?,
        details: __pigeon_replyList[2],
      );
    } else {
      return (__pigeon_replyList[0] as String?);
    }
  }

  Future<void> setBool(String key, bool? value) async {
    final String __pigeon_channelName =
        'cn.itbox.flutter_meteor.CacheApi.setBool$__pigeon_messageChannelSuffix';
    final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
      __pigeon_channelName,
      pigeonChannelCodec,
      binaryMessenger: __pigeon_binaryMessenger,
    );
    final List<Object?>? __pigeon_replyList =
        await __pigeon_channel.send(<Object?>[key, value]) as List<Object?>?;
    if (__pigeon_replyList == null) {
      throw _createConnectionError(__pigeon_channelName);
    } else if (__pigeon_replyList.length > 1) {
      throw PlatformException(
        code: __pigeon_replyList[0]! as String,
        message: __pigeon_replyList[1] as String?,
        details: __pigeon_replyList[2],
      );
    } else {
      return;
    }
  }

  Future<bool?> getBool(String key) async {
    final String __pigeon_channelName =
        'cn.itbox.flutter_meteor.CacheApi.getBool$__pigeon_messageChannelSuffix';
    final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
      __pigeon_channelName,
      pigeonChannelCodec,
      binaryMessenger: __pigeon_binaryMessenger,
    );
    final List<Object?>? __pigeon_replyList =
        await __pigeon_channel.send(<Object?>[key]) as List<Object?>?;
    if (__pigeon_replyList == null) {
      throw _createConnectionError(__pigeon_channelName);
    } else if (__pigeon_replyList.length > 1) {
      throw PlatformException(
        code: __pigeon_replyList[0]! as String,
        message: __pigeon_replyList[1] as String?,
        details: __pigeon_replyList[2],
      );
    } else {
      return (__pigeon_replyList[0] as bool?);
    }
  }

  Future<void> setInt(String key, int? value) async {
    final String __pigeon_channelName =
        'cn.itbox.flutter_meteor.CacheApi.setInt$__pigeon_messageChannelSuffix';
    final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
      __pigeon_channelName,
      pigeonChannelCodec,
      binaryMessenger: __pigeon_binaryMessenger,
    );
    final List<Object?>? __pigeon_replyList =
        await __pigeon_channel.send(<Object?>[key, value]) as List<Object?>?;
    if (__pigeon_replyList == null) {
      throw _createConnectionError(__pigeon_channelName);
    } else if (__pigeon_replyList.length > 1) {
      throw PlatformException(
        code: __pigeon_replyList[0]! as String,
        message: __pigeon_replyList[1] as String?,
        details: __pigeon_replyList[2],
      );
    } else {
      return;
    }
  }

  Future<int?> getInt(String key) async {
    final String __pigeon_channelName =
        'cn.itbox.flutter_meteor.CacheApi.getInt$__pigeon_messageChannelSuffix';
    final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
      __pigeon_channelName,
      pigeonChannelCodec,
      binaryMessenger: __pigeon_binaryMessenger,
    );
    final List<Object?>? __pigeon_replyList =
        await __pigeon_channel.send(<Object?>[key]) as List<Object?>?;
    if (__pigeon_replyList == null) {
      throw _createConnectionError(__pigeon_channelName);
    } else if (__pigeon_replyList.length > 1) {
      throw PlatformException(
        code: __pigeon_replyList[0]! as String,
        message: __pigeon_replyList[1] as String?,
        details: __pigeon_replyList[2],
      );
    } else {
      return (__pigeon_replyList[0] as int?);
    }
  }

  Future<void> setDouble(String key, double? value) async {
    final String __pigeon_channelName =
        'cn.itbox.flutter_meteor.CacheApi.setDouble$__pigeon_messageChannelSuffix';
    final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
      __pigeon_channelName,
      pigeonChannelCodec,
      binaryMessenger: __pigeon_binaryMessenger,
    );
    final List<Object?>? __pigeon_replyList =
        await __pigeon_channel.send(<Object?>[key, value]) as List<Object?>?;
    if (__pigeon_replyList == null) {
      throw _createConnectionError(__pigeon_channelName);
    } else if (__pigeon_replyList.length > 1) {
      throw PlatformException(
        code: __pigeon_replyList[0]! as String,
        message: __pigeon_replyList[1] as String?,
        details: __pigeon_replyList[2],
      );
    } else {
      return;
    }
  }

  Future<double?> getDouble(String key) async {
    final String __pigeon_channelName =
        'cn.itbox.flutter_meteor.CacheApi.getDouble$__pigeon_messageChannelSuffix';
    final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
      __pigeon_channelName,
      pigeonChannelCodec,
      binaryMessenger: __pigeon_binaryMessenger,
    );
    final List<Object?>? __pigeon_replyList =
        await __pigeon_channel.send(<Object?>[key]) as List<Object?>?;
    if (__pigeon_replyList == null) {
      throw _createConnectionError(__pigeon_channelName);
    } else if (__pigeon_replyList.length > 1) {
      throw PlatformException(
        code: __pigeon_replyList[0]! as String,
        message: __pigeon_replyList[1] as String?,
        details: __pigeon_replyList[2],
      );
    } else {
      return (__pigeon_replyList[0] as double?);
    }
  }

  Future<void> setList(String key, List<Object?>? value) async {
    final String __pigeon_channelName =
        'cn.itbox.flutter_meteor.CacheApi.setList$__pigeon_messageChannelSuffix';
    final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
      __pigeon_channelName,
      pigeonChannelCodec,
      binaryMessenger: __pigeon_binaryMessenger,
    );
    final List<Object?>? __pigeon_replyList =
        await __pigeon_channel.send(<Object?>[key, value]) as List<Object?>?;
    if (__pigeon_replyList == null) {
      throw _createConnectionError(__pigeon_channelName);
    } else if (__pigeon_replyList.length > 1) {
      throw PlatformException(
        code: __pigeon_replyList[0]! as String,
        message: __pigeon_replyList[1] as String?,
        details: __pigeon_replyList[2],
      );
    } else {
      return;
    }
  }

  Future<List<Object?>?> getList(String key) async {
    final String __pigeon_channelName =
        'cn.itbox.flutter_meteor.CacheApi.getList$__pigeon_messageChannelSuffix';
    final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
      __pigeon_channelName,
      pigeonChannelCodec,
      binaryMessenger: __pigeon_binaryMessenger,
    );
    final List<Object?>? __pigeon_replyList =
        await __pigeon_channel.send(<Object?>[key]) as List<Object?>?;
    if (__pigeon_replyList == null) {
      throw _createConnectionError(__pigeon_channelName);
    } else if (__pigeon_replyList.length > 1) {
      throw PlatformException(
        code: __pigeon_replyList[0]! as String,
        message: __pigeon_replyList[1] as String?,
        details: __pigeon_replyList[2],
      );
    } else {
      return (__pigeon_replyList[0] as List<Object?>?)?.cast<Object?>();
    }
  }

  Future<void> setMap(String key, Map<String?, Object?>? value) async {
    final String __pigeon_channelName =
        'cn.itbox.flutter_meteor.CacheApi.setMap$__pigeon_messageChannelSuffix';
    final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
      __pigeon_channelName,
      pigeonChannelCodec,
      binaryMessenger: __pigeon_binaryMessenger,
    );
    final List<Object?>? __pigeon_replyList =
        await __pigeon_channel.send(<Object?>[key, value]) as List<Object?>?;
    if (__pigeon_replyList == null) {
      throw _createConnectionError(__pigeon_channelName);
    } else if (__pigeon_replyList.length > 1) {
      throw PlatformException(
        code: __pigeon_replyList[0]! as String,
        message: __pigeon_replyList[1] as String?,
        details: __pigeon_replyList[2],
      );
    } else {
      return;
    }
  }

  Future<Map<String?, Object?>?> getMap(String key) async {
    final String __pigeon_channelName =
        'cn.itbox.flutter_meteor.CacheApi.getMap$__pigeon_messageChannelSuffix';
    final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
      __pigeon_channelName,
      pigeonChannelCodec,
      binaryMessenger: __pigeon_binaryMessenger,
    );
    final List<Object?>? __pigeon_replyList =
        await __pigeon_channel.send(<Object?>[key]) as List<Object?>?;
    if (__pigeon_replyList == null) {
      throw _createConnectionError(__pigeon_channelName);
    } else if (__pigeon_replyList.length > 1) {
      throw PlatformException(
        code: __pigeon_replyList[0]! as String,
        message: __pigeon_replyList[1] as String?,
        details: __pigeon_replyList[2],
      );
    } else {
      return (__pigeon_replyList[0] as Map<Object?, Object?>?)?.cast<String?, Object?>();
    }
  }

  Future<void> setBytes(String key, List<int?>? value) async {
    final String __pigeon_channelName =
        'cn.itbox.flutter_meteor.CacheApi.setBytes$__pigeon_messageChannelSuffix';
    final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
      __pigeon_channelName,
      pigeonChannelCodec,
      binaryMessenger: __pigeon_binaryMessenger,
    );
    final List<Object?>? __pigeon_replyList =
        await __pigeon_channel.send(<Object?>[key, value]) as List<Object?>?;
    if (__pigeon_replyList == null) {
      throw _createConnectionError(__pigeon_channelName);
    } else if (__pigeon_replyList.length > 1) {
      throw PlatformException(
        code: __pigeon_replyList[0]! as String,
        message: __pigeon_replyList[1] as String?,
        details: __pigeon_replyList[2],
      );
    } else {
      return;
    }
  }

  Future<List<int?>?> getBytes(String key) async {
    final String __pigeon_channelName =
        'cn.itbox.flutter_meteor.CacheApi.getBytes$__pigeon_messageChannelSuffix';
    final BasicMessageChannel<Object?> __pigeon_channel = BasicMessageChannel<Object?>(
      __pigeon_channelName,
      pigeonChannelCodec,
      binaryMessenger: __pigeon_binaryMessenger,
    );
    final List<Object?>? __pigeon_replyList =
        await __pigeon_channel.send(<Object?>[key]) as List<Object?>?;
    if (__pigeon_replyList == null) {
      throw _createConnectionError(__pigeon_channelName);
    } else if (__pigeon_replyList.length > 1) {
      throw PlatformException(
        code: __pigeon_replyList[0]! as String,
        message: __pigeon_replyList[1] as String?,
        details: __pigeon_replyList[2],
      );
    } else {
      return (__pigeon_replyList[0] as List<Object?>?)?.cast<int?>();
    }
  }
}

5.6 状态共享(SharedState)

利用 EventBusSharedMemoryCacheChangeNotifier 的组合,实现跨平台和跨引擎之间的状态同步机制。

业务端还可以结合 Provider 框架,通过注入ChangeNotifier来实现Widget的状态同步。Provider能够简化状态管理,使得跨引擎和跨平台的状态同步变得更加直观和高效,同时确保UI在状态变化时能够保持一致性和实时性。

5.6.1 实现流程

截屏2024-09-03 17.49.57.png

5.6.2 Flutter代码示例

import 'package:flutter/cupertino.dart';
import 'package:flutter_meteor/flutter_meteor.dart';

class GlobalStateService extends ChangeNotifier with GlobalSharedStateMixin {
  String eventName = 'GlobalStateChanged';
  GlobalStateService() {
    fetchState();
    MeteorEventBus.addListener(
      eventName: eventName,
      listener: (dynamic data) {
        if (data is String) {
          _updateCurrentEngineState(data);
        }
      },
    );
  }
  String _sharedState = "Initial State";
  String get sharedState => _sharedState;

  // Fetch state from native platform
  Future<void> fetchState() async {
    final String? state = await getString('state');
    _updateCurrentEngineState(state ?? 'Initial State');
  }

  // Update state on both Flutter and native platform
  Future<void> updateState(String newState) async {
    // _updateCurrentEngineState(newState);
    await setString('state', newState);
    MeteorEventBus.commit(eventName: eventName, data: newState);
  }

  void _updateCurrentEngineState(String newState) {
    _sharedState = newState;
    notifyListeners();
  }
}

六、实践案例

案例分析
demo示例

七、测试与调试

为多引擎路由方案测试时,你需要考虑如何模拟多引擎场景、如何测试路由逻辑的正确性、以及如何确保各个引擎之间的状态一致性。

多引擎测试的问题

在多引擎场景下,测试主要关注以下几个方面:

测试策略

  1. 模拟多引擎场景

  2. 测试引擎之间的状态共享

  3. 测试资源管理

八、总结与展望

8.1 总结

使用 Flutter 多引擎方案有以下几个主要优点:

8.1.1 性能提升

8.1.2 更好的原生集成

8.1.3 提高稳定性

8.1.4 适应复杂场景

8.2 展望

8.2.1 进一步提升性能:

8.2.2 扩展功能与兼容性:

8.2.3 体验优化:

8.2.4 模块化开发:

随着多引擎应用场景的拓展和技术的不断发展,未来的优化方向和扩展功能将更加注重性能提升、兼容性扩展、开发者体验优化。通过这些优化措施,多引擎页面路由方案将更加成熟,能够更好地满足复杂应用的需求,并为业务提供更稳健的保障。

九、参考文献与资料

上一篇 下一篇

猜你喜欢

热点阅读