跳转到正文
YGJoon 技术博客

Flutter混合开发实践

发布于:
·1.5k 字 · 约 4 分钟·作者:
把 Flutter module 嵌进原生 App:Android 接 FlutterActivity、iOS 用 podhelper,通道通信与启动性能一并讲
Flutter混合开发实践文章封面
目录

简介

随着Flutter的普及,越来越多的项目需要将Flutter集成到现有的原生应用中,本文将详细介绍Flutter混合开发的实践经验,包括集成方案、通信机制、性能优化等内容。

基础配置

1. Android集成

在build.gradle中添加配置

android {
    defaultConfig {
        minSdkVersion 19
        // ... 其他配置
    }
}

dependencies {
    implementation project(':flutter')
    // ... 其他依赖
}

创建FlutterActivity

class MainActivity : FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        // 注册平台通道
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "app_channel").apply {
            setMethodCallHandler { call, result ->
                when (call.method) {
                    "getNativeData" -> {
                        result.success("来自Android的数据")
                    }
                    else -> result.notImplemented()
                }
            }
        }
    }
}

2. iOS集成

在Podfile中添加配置

platform :ios, '11.0'

target 'YourApp' do
  use_frameworks!
  use_modular_headers!

  flutter_application_path = '../flutter_module'
  load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
  install_all_flutter_pods(flutter_application_path)

  # ... 其他配置
end

创建FlutterViewController

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
        let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
        
        // 注册平台通道
        let channel = FlutterMethodChannel(name: "app_channel",
                                         binaryMessenger: flutterEngine.binaryMessenger)
        
        channel.setMethodCallHandler { [weak self] (call, result) in
            switch call.method {
            case "getNativeData":
                result("来自iOS的数据")
            default:
                result(FlutterMethodNotImplemented)
            }
        }
    }
}

通信机制

1. 平台通道

class PlatformChannel {
  static const platform = MethodChannel('app_channel');

  static Future<String> getNativeData() async {
    try {
      final String result = await platform.invokeMethod('getNativeData');
      return result;
    } on PlatformException catch (e) {
      return "Failed to get native data: ${e.message}";
    }
  }

  static Future<void> sendToNative(String data) async {
    try {
      await platform.invokeMethod('sendData', {'data': data});
    } on PlatformException catch (e) {
      print("Failed to send data: ${e.message}");
    }
  }
}

2. 事件通道

class EventChannel {
  static const stream = EventChannel('event_channel');

  static Stream<dynamic> get nativeEvents {
    return stream.receiveBroadcastStream();
  }

  static void listenToNativeEvents() {
    nativeEvents.listen(
      (event) {
        print('Received event from native: $event');
      },
      onError: (error) {
        print('Error: $error');
      },
    );
  }
}

页面导航

1. Flutter页面跳转

class NavigationService {
  static Future<void> navigateToNative(BuildContext context, String route) async {
    try {
      await PlatformChannel.platform.invokeMethod('navigateToNative', {
        'route': route,
      });
    } catch (e) {
      print('Navigation error: $e');
    }
  }

  static Future<void> navigateToFlutter(BuildContext context, String route) async {
    Navigator.of(context).pushNamed(route);
  }
}

2. 原生页面跳转

Android

class NavigationHelper {
    fun navigateToFlutter(activity: Activity, route: String) {
        FlutterActivity
            .withNewEngine()
            .initialRoute(route)
            .build(activity)
            .also { activity.startActivity(it) }
    }
}

iOS

class NavigationHelper {
    static func navigateToFlutter(from viewController: UIViewController, route: String) {
        let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
        let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
        
        viewController.present(flutterViewController, animated: true)
    }
}

资源共享

1. 图片资源

class SharedResources {
  static Future<String> getNativeImage(String name) async {
    try {
      final String path = await PlatformChannel.platform.invokeMethod(
        'getNativeImage',
        {'name': name},
      );
      return path;
    } catch (e) {
      print('Error getting native image: $e');
      return '';
    }
  }
}

2. 数据持久化

class SharedPreferences {
  static const platform = MethodChannel('shared_preferences_channel');

  static Future<void> setValue(String key, dynamic value) async {
    await platform.invokeMethod('setValue', {
      'key': key,
      'value': value,
    });
  }

  static Future<dynamic> getValue(String key) async {
    return await platform.invokeMethod('getValue', {'key': key});
  }
}

性能优化

1. 预热引擎

Android

class MyApplication : Application() {
    lateinit var flutterEngine: FlutterEngine

    override fun onCreate() {
        super.onCreate()
        // 预热Flutter引擎
        flutterEngine = FlutterEngine(this).apply {
            dartExecutor.executeDartEntrypoint(
                DartExecutor.DartEntrypoint.createDefault()
            )
        }
        FlutterEngineCache.getInstance().put("cached_engine", flutterEngine)
    }
}

iOS

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    lazy var flutterEngine = FlutterEngine(name: "cached_engine")

    override func application(_ application: UIApplication,
                             didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 预热Flutter引擎
        flutterEngine.run()
        return true
    }
}

2. 内存优化

不要在 Flutter 页面退出时把 imageCache.clearLiveImages() 当作立即释放图像内存的手段。它会清除 ImageCache 对 live image 的跟踪,但不保证图片资源因此立即回收,也不会减轻内存压力。优先释放页面持有的图片、provider 和 stream 引用,并按实际显示尺寸控制图片解码。更多快照排查步骤见 Flutter 内存优化。

参考:ImageCache.clearLiveImages API。

调试与测试

1. 调试工具

class DebugTools {
  static void setupDebugTools() {
    if (kDebugMode) {
      // 添加调试信息
      PlatformChannel.platform.setMethodCallHandler((call) async {
        print('Native call: ${call.method} ${call.arguments}');
        throw UnimplementedError();
      });
    }
  }
}

2. 单元测试

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  group('Platform Channel Tests', () {
    const channel = MethodChannel('app_channel');

    setUp(() {
      channel.setMockMethodCallHandler((MethodCall methodCall) async {
        if (methodCall.method == 'getNativeData') {
          return '测试数据';
        }
        return null;
      });
    });

    tearDown(() {
      channel.setMockMethodCallHandler(null);
    });

    test('getNativeData returns correct value', () async {
      expect(
        await PlatformChannel.getNativeData(),
        '测试数据',
      );
    });
  });
}

最佳实践

1. 一个 App 只养一个引擎实例

每个 FlutterEngine 是一份独立的 Dart isolate,内存占用翻倍。上文预热的引擎放进 FlutterEngineCache 后,所有 Flutter 页面都用 FlutterActivity.withCachedEngine() / FlutterViewController 复用它。反例:每个 Flutter 页面 new 一个引擎——进三个页面,内存里躺三个 Dart 堆。

2. 页面销毁时别顺手 destroy 缓存的引擎

FlutterEngineCache 里的引擎是全局复用的,某个 Activity onDestroy 时调 engine.destroy(),下个 Flutter 页面拿到的就是具尸体。引擎的生命周期跟 Application 走,不跟页面走。

3. 原生 ↔ Flutter 的高频通信要节流

滚动进度、传感器数据这种高频回调用 MethodChannel 每次都发,原生和 Flutter 两边都在跨线程序列化,帧率直接掉。批量聚合(如 100ms 合并一次)或降频后再发,界面该流畅流畅。

4. 路由协议先于代码定好

原生跳 Flutter、Flutter 跳原生,参数结构(路由名、参数 Map、返回值约定)先写成文档。两边各写各的”约定”,联调时就是互相甩锅现场。一个共享的路由表常量,两端引用同一份定义。

实战案例

混合开发(Flutter 嵌入原生 App / 原生页面嵌入 Flutter)我还没做过——我的项目全是纯 Flutter,一把梭。原因是我的 App 都是从零起的,没有”存量原生 App 要加 Flutter 页面”的场景。混合开发的坑我听说过不少:FlutterEngine 的预热(不预热首屏白屏 1 秒+)、原生和 Flutter 互相跳转的路由打通、内存占用翻倍。新项目直接纯 Flutter,别给自己加戏;只有存量原生 App 才值得谈混合。真要做,官方有 add-to-app 方案,iOS 用 FlutterViewController、Android 用 FlutterActivity,文档是全的。参考:Flutter 官方文档”Add Flutter to existing app”。

总结

混合开发的优势:

  1. 充分利用现有代码
  2. 平滑的技术迁移
  3. 灵活的技术选型
  4. 优化开发效率

成功实践要点:


分享这篇文章:


相关文章

上一篇
Flutter集成支付SDK
下一篇
Flutter 鸿蒙化基础适配:OpenHarmony 社区版工程流程