Flutter 混合架构方案:多引擎

78 阅读5分钟

在 Flutter 中,单引擎和多引擎是两种不同的应用开发模式,它们在性能、资源管理和使用场景等方面存在差异。

单引擎模式

单引擎模式是指在一个 Flutter 应用中只使用一个 Flutter 引擎实例。这个引擎负责处理所有 Flutter 界面的渲染、动画、输入事件等。大多数简单的 Flutter 应用默认采用单引擎模式。

  • 优点

    • 资源占用少:由于只使用一个引擎实例,内存和 CPU 等系统资源的占用相对较少,有助于提高应用的性能和电池续航能力。

    • 状态管理简单:整个应用的状态管理相对简单,因为所有的 Flutter 界面都在同一个引擎上下文中运行,数据共享和状态同步更加容易。

    • 启动速度快:单引擎模式下,应用启动时只需初始化一个引擎实例,启动速度相对较快。

  • 缺点

    • 灵活性差:如果应用需要在不同的界面或模块之间进行复杂的隔离和交互,单引擎模式可能无法满足需求。

    • 稳定性受影响:一旦引擎出现问题,整个应用可能会受到影响。

多引擎模式

多引擎模式是指在一个应用中使用多个 Flutter 引擎实例。每个引擎实例可以独立运行,负责不同的 Flutter 界面或模块。多引擎模式通常用于复杂的应用场景,如在一个原生应用中嵌入多个独立的 Flutter 页面。

  • 优点

    • 隔离性好:不同的引擎实例之间相互隔离,一个引擎出现问题不会影响其他引擎的运行,提高了应用的稳定性。

    • 灵活性高:可以根据需要独立控制每个引擎的生命周期、状态和资源,实现更复杂的界面和交互逻辑。

    • 与原生集成更方便:在原生应用中嵌入多个独立的 Flutter 页面时,多引擎模式可以更好地与原生代码进行集成。

  • 缺点

    • 资源占用多:每个引擎实例都需要占用一定的系统资源,多个引擎实例会增加内存和 CPU 的负担,可能影响应用的性能和电池续航能力。

    • 状态管理复杂:不同引擎实例之间的数据共享和状态同步相对复杂,需要额外的机制来实现。

多引擎的典型场景

  1. 同一页面嵌入多个 Flutter 视图 例如:一个原生 Android/iOS 页面中同时显示两个独立的 Flutter 界面。
  2. 多模块隔离 不同 Flutter 模块需要独立的状态管理或插件环境。
  3. 混合栈管理 在原生和 Flutter 混合导航时,为不同页面分配独立引擎以避免状态冲突。

多引擎方案 FlutterEngineGroup

简单使用

1. Android 平台

使用 FlutterEngineGroup(推荐)或手动创建多个 FlutterEngine 实例。

// 使用 FlutterEngineGroup 创建引擎
val engineGroup = FlutterEngineGroup(context)
val engine1 = engineGroup.createAndRunEngine(
    context, DartExecutor.DartEntrypoint.createDefault()
)
val engine2 = engineGroup.createAndRunEngine(
    context, DartExecutor.DartEntrypoint.createDefault()
)

// 将引擎绑定到 FlutterView
val flutterView1 = FlutterView(context).apply {
    attachToFlutterEngine(engine1)
}
val flutterView2 = FlutterView(context).apply {
    attachToFlutterEngine(engine2)
}

2. iOS 平台

使用 FlutterEngineGroup(iOS 13+)或手动创建多个 FlutterEngine

// 创建引擎组
let engineGroup = FlutterEngineGroup(name: "group", project: nil)

// 生成引擎实例
let engine1 = engineGroup.makeEngine(withEntrypoint: "main", libraryURI: nil)
let engine2 = engineGroup.makeEngine(withEntrypoint: "main", libraryURI: nil)

// 绑定到 FlutterViewController
let flutterVC1 = FlutterViewController(engine: engine1, nibName: nil, bundle: nil)
let flutterVC2 = FlutterViewController(engine: engine2, nibName: nil, bundle: nil)

3. Flutter 侧配置

每个引擎默认独立运行,需确保入口点(main())支持多实例:

void main() {
  runApp(MyApp()); // 确保无全局静态状态冲突
}

注意事项

  • 1、内存开销:每个引擎实例约占用 5-20MB 内存,需根据设备性能合理控制引擎数量。
  • 2、插件注册:每个引擎需单独注册插件(通过 GeneratedPluginRegistrant)。若插件依赖原生上下文(如相机),需确保多引擎下的兼容性。
  • 3、生命周期管理:在页面销毁时调用 destroy() 释放引擎
  • 4、共享数据:不同引擎间无法直接共享内存,需通过原生层中转(如 MethodChannelEventChannel)。

引擎池(iOS端)

class FlutterEnginePool {
    // MARK: - 单例
    static let shared = FlutterEnginePool()
    private init() {}
    
    // MARK: - 核心属性
    private let engineGroup = FlutterEngineGroup(name: "flutter_engine_pool", project: nil)
    private var activeEngines: [String: FlutterEngine] = [:]  // 使用中的引擎 [路由标识: 引擎]
    private var idleEngines: [String: FlutterEngine] = [:]    // 闲置引擎池
    private let queue = DispatchQueue(label: "com.flutter.engine.pool.lock")  // 线程安全队列
    
    // MARK: - 配置参数
    private let maxIdleCount = 3           // 最大闲置引擎数
    private let maxIdleTime: TimeInterval = 300  // 闲置超时时间(秒)
    private var timer: Timer?              // 闲置检测定时器
    
    // MARK: - 公开方法
    /// 获取引擎(按路由标识)
    func getEngine(for route: String) -> FlutterEngine {
        return queue.sync {
            // 1. 查找可用闲置引擎
            if let engine = idleEngines.removeValue(forKey: route) {
                activeEngines[route] = engine
                return engine
            }
            
            // 2. 创建新引擎
            let engine = engineGroup.makeEngine(
                withEntrypoint: "main",
                libraryURI: nil,
                initialRoute: route
            )
            GeneratedPluginRegistrant.register(with: engine)
            activeEngines[route] = engine
            
            // 3. 启动闲置检测
            startIdleCheckTimer()
            
            return engine
        }
    }
    
    /// 归还引擎到池中
    func recycleEngine(for route: String) {
        queue.async {
            guard let engine = self.activeEngines.removeValue(forKey: route) else { return }
            
            // 1. 超过最大闲置数时销毁最旧的引擎
            if self.idleEngines.count >= self.maxIdleCount, let firstKey = self.idleEngines.keys.first {
                self.idleEngines.removeValue(forKey: firstKey)?.destroyContext()
            }
            
            // 2. 记录闲置时间戳(用于LRU回收)
            let metadata = ["recycleTime": Date()]
            engine.setMetadata(metadata)
            
            // 3. 存入闲置池
            self.idleEngines[route] = engine
        }
    }
    
    /// 强制销毁所有引擎(用于内存警告)
    func purgeAllEngines() {
        queue.async {
            self.activeEngines.values.forEach { $0.destroyContext() }
            self.idleEngines.values.forEach { $0.destroyContext() }
            self.activeEngines.removeAll()
            self.idleEngines.removeAll()
        }
    }
    
    // MARK: - 私有方法
    private func startIdleCheckTimer() {
        guard timer == nil else { return }
        
        timer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { [weak self] _ in
            self?.checkIdleEngines()
        }
    }
    
    private func checkIdleEngines() {
        queue.async {
            let now = Date()
            
            // 清理超时引擎
            self.idleEngines = self.idleEngines.filter { route, engine in
                guard let metadata = engine.metadata as? [String: Any],
                      let recycleTime = metadata["recycleTime"] as? Date else {
                    return false
                }
                
                if now.timeIntervalSince(recycleTime) > self.maxIdleTime {
                    engine.destroyContext()
                    return false
                }
                return true
            }
        }
    }
}

// MARK: - 内存警告扩展
extension FlutterEnginePool {
    func setupMemoryWarningObserver() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleMemoryWarning),
            name: UIApplication.didReceiveMemoryWarningNotification,
            object: nil
        )
    }
    
    @objc private func handleMemoryWarning() {
        purgeAllEngines()
    }
}

在 App 启动时初始化

// AppDelegate.swift
@main
class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        // 初始化引擎池并启动内存监听
        FlutterEnginePool.shared.setupMemoryWarningObserver()
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

在原生界面中跳转 Flutter

class NativeViewController: UIViewController {
    func navigateToFlutterPage(route: String) {
        // 获取引擎
        let engine = FlutterEnginePool.shared.getEngine(for: route)
        let flutterVC = FlutterViewController(engine: engine, nibName: nil, bundle: nil)
        
        // 跳转页面
        navigationController?.pushViewController(flutterVC, animated: true)
    }
    
    func onReturnFromFlutter() {
        // 归还引擎(通常在 viewDidDisappear 或 Flutter 侧触发返回时调用)
        FlutterEnginePool.shared.recycleEngine(for: "your_route_identifier")
    }
}

Flutter 侧返回通知

// 在 Flutter 页面中添加返回按钮逻辑
ElevatedButton(
  onPressed: () {
    Navigator.pop(context); // 退出当前页面
    // 通知 iOS 归还引擎
    const channel = MethodChannel('com.example/engine_channel');
    channel.invokeMethod('recycleEngine');
  },
  child: Text('返回原生'),
)

// iOS 端监听(在 AppDelegate 中)
MethodChannel(name: "com.example/engine_channel", binaryMessenger: engine.binaryMessenger)
  .setMethodCallHandler { call, _ in
    if call.method == "recycleEngine" {
      FlutterEnginePool.shared.recycleEngine(for: "your_route_identifier")
    }
  }