• Lv1
    粉丝0

积分12 / 贡献0

提问0答案被采纳0文章1

作者动态

    [经验分享] HarmonyOS NEXT-Flutter 混合开发之鸿蒙 - 代码实践

    zhousg 显示全部楼层 发表于 2025-3-26 16:05:30

    在 Flutter 三端分离模式下完成纯血鸿蒙混入的过程中,虽然官方文档提供了一定的指导,但实际操作中可能会遇到一些坑。以下是我在适配过程中的一些经验总结,供各位开发者参考 😄 如果有帮助点个赞。

    在混入过程中是基于咸鱼团队 flutter_boost(这里不讨论和其他方案的差别) 和自定义 FlutterPlugin 实现的。

    主要涉及内容:

    1. 环境搭建
    2. Flutter module 创建
    3. Futter 引入 flutter_boost
    4. Harmony 引入 flutter_boost
    5. Flutter 与鸿蒙侧通信
    6. Flutter 调用鸿蒙原生

    环境搭建

    • Fluter 环境

    准备支持鸿蒙的 Flutter 开发环境,flutter_fluter 仓库基于 Flutter SDK 对于 OpenHarmony 平台的兼容拓展,可支持 IDE 或者终端使用 Flutter Tools 指令编译和构建 OpenHarmony 应用程序。

    • Harmonyos NEXT 环境

    不再赘述,上链接

    Flutter module 创建

    创建 Fluter 项目

    flutter create -t module --org xyz.zhousg demo_fluter

    打包 Fluter 项目

    flutter build har --debug

    Fluter 引入 flutter_boost

    • 安装依赖
    dependencies:
      flutter:
        sdk: flutter
    
      # The following adds the Cupertino Icons font to your application.
      # Use with the CupertinoIcons class for iOS style icons.
      cupertino_icons: ^1.0.2
      fl_chart: ^0.62.0
      flutter_boost:
    +    git:
    +        url: 'https://github.com/alibaba/flutter_boost.git'
    +        ref: '4.6.5'
    • 配置路由表
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter_boost/flutter_boost.dart';
    
    // 1. 创建一个自定义的Binding,继承和with的关系如下,里面什么都不用写
    class CustomFlutterBinding extends WidgetsFlutterBinding
        with BoostFlutterBinding {
       }
    
    void main() {
    
      // 2. 这里的CustomFlutterBinding调用务必不可缺少,用于控制Boost状态的resume和pause
      CustomFlutterBinding();
      runApp(const MyApp());
    }
    
    class MyApp extends StatefulWidget {
    
      const MyApp({
       super.key});
    
      @override
      State<MyApp> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
    
      // 3. 路由表
      Map<String, FlutterBoostRouteFactory> routerMap = {
    
        'SettingsPage': (settings, isContainerPage, uniqueId) {
    
          return CupertinoPageRoute(
            settings: settings,
            builder: (BuildContext ctx) {
    
              return const Placeholder();
            },
          );
        },
        'DeviceStoragePage': (settings, isContainerPage, uniqueId) {
    
          return CupertinoPageRoute(
            settings: settings,
            builder: (BuildContext ctx) {
    
              return const Placeholder();
            },
          );
        },
        'AboutPage': (settings, isContainerPage, uniqueId) {
    
          return CupertinoPageRoute(
            settings: settings,
            builder: (BuildContext ctx) {
    
              return const Placeholder();
            },
          );
        },
        '/': (settings, isContainerPage, uniqueId) {
    
          return CupertinoPageRoute(
            settings: settings,
            builder: (BuildContext ctx) {
    
              return const Placeholder();
            },
          );
        },
      };
    
      // 路由工厂函数
      Route<dynamic> routeFactory(
          RouteSettings settings, bool isContainerPage, String? uniqueId) {
    
        FlutterBoostRouteFactory? fn = routerMap[settings.name];
        if (fn == null) {
    
          throw FlutterError(
              'Route "${ settings.toString()}" is not defined in routerMap.');
        }
        return fn(settings, isContainerPage, uniqueId)!;
      }
    
      @override
      Widget build(BuildContext context) {
    
        // flutter_boost 接管
        return FlutterBoostApp(
          routeFactory,
          // Flutter 侧直接预览需要,需要使用 Deveco Studio 导入 .ohos 项目进行自动签名预览至鸿蒙设备
          initialRoute: 'SettingsPage',
          appBuilder: (home) {
    
            return MaterialApp(
              builder: (context, child) => home,
            );
          },
        );
      }
    }

    Harmony 引入 flutter_boost

    这里使用的是 router + FlutterPage 方式展示 Flutter 界面,Navigation 后续再说吧~

    a. 先打包 Fluter 项目,会生成三个产物

    .ohos
       |--har
           |-- fluter_boost.har
           |-- fluter_module.har
           |-- fluter.har

    b. 在鸿蒙项目中,引入依赖

    oh-package.json5

    "dependencies": {
      "@ohos/flutter_module": "file:../demo_flutter/.ohos/har/flutter_module.har",
      // 下面两个依赖我直接拷贝到了 libs 下
      "@ohos/flutter_ohos": "file:./libs/flutter.har",
      "flutter_boost": "file:./libs/flutter_boost.har"
    },
    "overrides": {
      "@ohos/flutter_ohos": "file:./libs/flutter.har",
      "flutter_boost": "file:./libs/flutter_boost.har"
    },

    c. 初始化 flutter_boost

    entryAbility.ets

    import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
    import { router } from '@kit.ArkUI';
    import { FlutterManager } from '@ohos/flutter_ohos';
    import { FlutterBoostDelegate, FlutterBoostRouteOptions, FlutterBoost, FlutterBoostSetupOptionsBuilder } from 'flutter_boost';
    import { GeneratedPluginRegistrant } from '@ohos/flutter_module';
    
    
    export default class EntryAbility extends UIAbility implements  FlutterBoostDelegate{
      pushNativeRoute(options: FlutterBoostRouteOptions): void {
        // throw new Error('Method not implemented.');
      }
    
      pushFlutterRoute(options: FlutterBoostRouteOptions,): void {
        // throw new Error('Method not implemented.');
      }
    
      popRoute(options: FlutterBoostRouteOptions): boolean {
        // throw new Error('Method not implemented.');
        router.back()
        return true
      }
    
      async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
        FlutterManager.getInstance().pushUIAbility(this);
      }
    
      onDestroy(): void {
        FlutterManager.getInstance().popUIAbility(this);
      }
    
      onWindowStageCreate(windowStage: window.WindowStage): void {
        // Flutter bind in UIAbility
        FlutterManager.getInstance().pushWindowStage(this, windowStage);
        // Initial FlutterBoost
        const optionsBuilder: FlutterBoostSetupOptionsBuilder = new FlutterBoostSetupOptionsBuilder()
        FlutterBoost.getInstance().setup(this, this.context, (engine) => {
          GeneratedPluginRegistrant.registerWith(engine)
        }, optionsBuilder.build())
    
        windowStage.loadContent('pages/Index');
      }
    
      onWindowStageDestroy(): void {
        FlutterManager.getInstance().popWindowStage(this);
      }
    
      onForeground(): void {
        logger.info('Ability onForeground');
      }
    
      onBackground(): void {
        logger.info('Ability onBackground');
      }
    }

    这里部分代码省略了~pushNativeRoute pushFlutterRoute popRoute 具体实现还是参考官方仓库

    d. Flutter 容器与跳转

    • FlutterPage 承载 Flutter 的页面

    pages/FluterPage.ets

    import {
        FlutterEntry, FlutterPage, FlutterView} from '@ohos/flutter_ohos';
    import {
        FlutterBoost, FlutterBoostEntry } from 'flutter_boost';
    import {
        router } from '@kit.ArkUI';
    
    @Entry
    @Component
    struct SettingsPage {
    
      private flutterEntry?: FlutterEntry;
      private flutterView?: FlutterView
    
      aboutToAppear() {
    
        this.flutterEntry = new FlutterBoostEntry(getContext(this), router.getParams())
        this.flutterEntry?.aboutToAppear()
        this.flutterView = this.flutterEntry?.getFlutterView()
      }
    
      aboutToDisappear() {
    
        this.flutterEntry?.aboutToDisappear()
      }
    
      onPageShow() {
    
        this.flutterEntry?.onPageShow()
      }
    
      onPageHide() {
    
        this.flutterEntry?.onPageHide()
      }
    
      onBackPress(): boolean | void {
    
        FlutterBoost.getInstance()
          .getPlugin()?.onBackPressed();
        return true;
      }
    
      build() {
    
        Column() {
    
          FlutterPage({
        viewId: this.flutterView?.getId() })
            .width('100%')
            .height('100%')
        }
        .width('100%')
        .height('100%')
      }
    }
    • 在 Index 中的跳转

    pages/Index.ets

    // uri Flutter Module 中的路由表 KEY params 是传参
    router.pushUrl({
        url: 'pages/FlutterPage', params: {
        uri: 'DeviceStoragePage', params: {
       } } })
    router.pushUrl({
        url: 'pages/FlutterPage', params: {
        uri: 'SettingPage', params: {
       } } })
    router.pushUrl({
        url: 'pages/FlutterPage', params: {
        uri: 'AboutPage', params: {
       } } })

    Flutter 与鸿蒙侧通信

    • Flutter 侧
    TextButton(
        onPressed: () {
    
          Map<String, String> data = {
       'name': 'jack'};
          BoostChannel.instance.sendEventToNative('updateUser', data);
        },
        child: const Text('发送消息'),
      ),
    • 鸿蒙侧

    pages/FlutterPage.ets

    aboutToAppear() {
    
      this.flutterEntry = new FlutterBoostEntry(getContext(this), router.getParams())
      this.flutterEntry?.aboutToAppear()
      this.flutterView = this.flutterEntry?.getFlutterView()
    
      const plugin = FlutterBoost.getInstance()
        .getPlugin()
      if (plugin) {
    
        // 通信
        plugin.addEventListener('updateUser', {
    
          onEvent: (key, args) => {
    
            // logger.debug(`事件名称 ${key}`, JSON.stringify(args))
            promptAction.showToast({
        message: 'Flutter Data: ' + JSON.stringify(args) })
          }
        })
      }

    Flutter 调用鸿蒙原生

    • Flutter 侧
    final _platform = const MethodChannel('xyz.zhousg.interview_success_project');
    TextButton(
        onPressed: () {
    
          _platform.invokeMethod('openCamera').then(
                (value) => setState(() {
    
                  // value 鸿蒙侧回传数据
                }),
              );
        },
        child: Text('打开相机),
    ),
    • 鸿蒙侧

    定义 Flutter 插件

    NativePlugin.ets

    import {
        FlutterPlugin, FlutterPluginBinding, MethodChannel, MethodResult } from '@ohos/flutter_ohos';
    
    export class NativePlugin implements FlutterPlugin {
    
      private channel?: MethodChannel;
    
      getUniqueClassName(): string {
    
        return 'CameraPlugin'
      }
    
      onAttachedToEngine(binding: FlutterPluginBinding): void {
    
        this.channel = new MethodChannel(binding.getBinaryMessenger(), 'xyz.zhousg.interview_success_project')
        this.channel.setMethodCallHandler({
    
          onMethodCall: (call, result) => {
    
            switch (call.method) {
    
              case "openCamera":
                this.openCamera(result)
                break;
              default:
                result.notImplemented()
                break;
            }
          }
        })
      }
    
      onDetachedFromEngine(binding: FlutterPluginBinding): void {
    
        this.channel?.setMethodCallHandler(null);
      }
    
    
      // native api
      openCamera (result: MethodResult) {
    
        // 回传数据给 Flutter
        result.success('http://test.png')
      }
    }

    注册插件

    entryAbility.ets

    // Initial FlutterBoost
    const optionsBuilder: FlutterBoostSetupOptionsBuilder = new FlutterBoostSetupOptionsBuilder()
    FlutterBoost.getInstance().setup(this, this.context, (engine) => {
    
      GeneratedPluginRegistrant.registerWith(engine)
      // 打开相机
      engine.getPlugins()?.add(new NativePlugin())
    }, optionsBuilder.build())

    总结

    使用 flutter_boost 开发 Flutter 混合项目,在鸿蒙这边和混合 Web 组件进行混合开发很相似,搞定 Flutter 页面栈 + 鸿蒙页面栈跳转,搞定数据通信和原生调用,基本可以满足一般的开发,以上这里仅供参考哈~

    ©著作权归作者所有,转载或内容合作请联系作者

    您尚未登录,无法参与评论,登录后可以:
    参与开源共建问题交流
    认同或收藏高质量问答
    获取积分成为开源共建先驱

    Copyright   ©2025  OpenHarmony开发者论坛  京ICP备2020036654号-3 | 京公网安备11030102011662号 |技术支持 Discuz!

    返回顶部