鸿蒙 Flutter 开发:原子化服务开发与全场景适配实战

鸿蒙系统的原子化服务是其生态的核心特色之一,它以免安装、轻量化的形式为用户提供核心功能,支持跨设备无缝流转;而全场景适配则是鸿蒙 “万物互联” 理念的体现,要求应用能在手机、平板、智慧屏、智能穿戴等不同形态设备上提供一致且适配的体验。此前我们探讨了鸿蒙 Flutter 的基础开发、分布式能力、混合开发以及方舟卡片集成,本文将聚焦鸿蒙 Flutter 原子化服务开发全场景设备适配,通过完整代码案例,带你打造符合鸿蒙生态的全场景 Flutter 应用。

一、鸿蒙原子化服务与 Flutter 的结合逻辑

鸿蒙原子化服务(也叫元服务)本质是轻量化的鸿蒙应用,支持免安装运行、卡片化展示和跨设备流转。Flutter 与原子化服务的结合主要基于以下核心逻辑:

  1. Flutter 作为原子化服务的功能载体:将 Flutter 的核心业务逻辑封装为鸿蒙原子化服务的页面,利用 Flutter 的跨端优势实现原子化服务的跨设备渲染。
  2. 原子化服务的鸿蒙特性赋能:为 Flutter 实现的原子化服务添加鸿蒙特有的能力,如免安装启动、服务卡片、分布式流转。
  3. 通信桥梁:通过Platform Channel实现 Flutter 代码与鸿蒙原子化服务原生 API 的交互,如获取原子化服务的启动参数、更新服务卡片数据。

需要注意的是,鸿蒙原子化服务目前对 Flutter 的支持需要通过鸿蒙原生工程嵌套 Flutter 模块的方式实现,即原子化服务的入口为鸿蒙原生 Ability,内部嵌入 Flutter 页面。

二、案例 1:鸿蒙 Flutter 原子化服务开发(免安装待办服务)

本案例将实现一个免安装的待办事项原子化服务:用户无需安装应用,直接从鸿蒙应用市场或桌面卡片启动 Flutter 实现的待办事项功能,支持添加待办、展示列表,并同步到鸿蒙服务卡片。

前置条件

  1. 已配置鸿蒙 DevEco Studio 4.0 + 和 Flutter 3.10 + 环境,确保两者版本兼容。
  2. 已掌握鸿蒙原子化服务的基础开发流程(配置config.json、创建原子化服务 Ability)。
  3. 已创建鸿蒙原生工程,并集成 Flutter 模块(参考鸿蒙官方的 Flutter 集成文档)。

步骤 1:配置鸿蒙原子化服务基础信息

首先在鸿蒙原生工程的entry > src > main > config.json中配置原子化服务的核心信息,包括免安装、服务类型、卡片配置等。

{
  "app": {
    "bundleName": "com.example.harmony_flutter_atos",
    "vendor": "example",
    "version": {
      "code": 1000000,
      "name": "1.0.0"
    },
    "apiVersion": {
      "compatible": 9,
      "target": 9
    },
    "atomicService": true // 声明为原子化服务
  },
  "module": {
    "package": "com.example.harmony_flutter_atos",
    "name": ".MyApplication",
    "mainAbility": ".MainAbility",
    "deviceTypes": [
      "phone",
      "tablet",
      "tv"
    ],
    "deliveryWithInstall": false, // 免安装(原子化服务核心配置)
    "installationFree": true, // 开启免安装能力
    "abilities": [
      {
        "name": ".MainAbility",
        "type": "page",
        "visible": true,
        "skills": [
          {
            "entities": [
              "entity.system.home",
              "entity.system.installation_free" // 免安装实体
            ],
            "actions": [
              "action.system.home",
              "action.system.installation_free"
            ]
          }
        ],
        "metadata": [
          {
            "name": "ohos.extra.param.installation_free",
            "value": "true" // 开启免安装
          }
        ]
      },
      {
        "name": ".TodoCardAbility",
        "type": "service",
        "visible": true,
        "skills": [
          {
            "entities": [
              "entity.system.servicecard"
            ],
            "actions": [
              "action.system.servicecard"
            ]
          }
        ]
      }
    ],
    "serviceCards": [
      {
        "name": "todo_card",
        "description": "待办事项服务卡片",
        "icon": "$media:icon",
        "preview": "$media:preview",
        "styles": [
          {
            "name": "style_2_2",
            "dimensions": "2*2",
            "description": "2x2待办卡片"
          }
        ],
        "abilities": [
          {
            "name": ".TodoCardAbility"
          }
        ]
      }
    ],
    "reqPermissions": [
      {
        "name": "ohos.permission.DISTRIBUTED_DATA_SYNC",
        "reason": "需要分布式数据同步",
        "usedScene": {
          "abilities": [".MainAbility"],
          "when": "always"
        }
      }
    ]
  }
}

步骤 2:鸿蒙原生端集成 Flutter 模块

在鸿蒙原生工程的MainAbilitySlice.java中初始化 Flutter 引擎,并嵌入 Flutter 页面作为原子化服务的核心界面。

package com.example.harmony_flutter_atos;

import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.MethodChannel;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;

public class MainAbilitySlice extends AbilitySlice {
    private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, "MainAbilitySlice");
    private static final String CHANNEL_NAME = "com.example.todo_atos/todo_data";
    private FlutterEngine flutterEngine;

    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        // 初始化Flutter引擎
        flutterEngine = new FlutterEngine(getContext());
        // 执行Flutter入口函数(lib/main.dart的main方法)
        flutterEngine.getDartExecutor().executeDartEntrypoint(
                DartExecutor.DartEntrypoint.createDefault()
        );

        // 注册MethodChannel,用于接收Flutter的待办数据并同步到服务卡片
        new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL_NAME)
                .setMethodCallHandler((call, result) -> {
                    if (call.method.equals("syncTodoData")) {
                        String todoData = call.argument("data");
                        // 同步数据到服务卡片(具体实现参考前文方舟卡片案例)
                        syncToServiceCard(todoData);
                        result.success(null);
                    } else {
                        result.notImplemented();
                    }
                });

        // 将Flutter页面嵌入鸿蒙原生AbilitySlice
        setContentView(flutterEngine.getView());
    }

    /**
     * 同步待办数据到鸿蒙服务卡片
     */
    private void syncToServiceCard(String todoData) {
        HiLog.info(LABEL, "同步待办数据到卡片:%{public}s", todoData);
        // 此处复用前文方舟卡片的分布式KvStore同步逻辑
    }

    @Override
    public void onActive() {
        super.onActive();
    }

    @Override
    public void onForeground(Intent intent) {
        super.onForeground(intent);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 释放Flutter引擎资源(原子化服务退出时)
        flutterEngine.destroy();
    }
}

步骤 3:Flutter 端实现原子化服务核心功能(待办事项)

在 Flutter 模块的lib/main.dart中实现待办事项的核心功能,包括添加待办、展示列表,并通过MethodChannel将数据同步到鸿蒙服务卡片。

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyTodoAtosApp());
}

class MyTodoAtosApp extends StatelessWidget {
  const MyTodoAtosApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '鸿蒙原子化待办服务',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        // 适配鸿蒙原子化服务的浅色/深色模式
        brightness: Brightness.light,
      ),
      home: const TodoAtosPage(),
      debugShowCheckedModeBanner: false, // 原子化服务隐藏调试横幅
    );
  }
}

class TodoAtosPage extends StatefulWidget {
  const TodoAtosPage({super.key});

  @override
  State<TodoAtosPage> createState() => _TodoAtosPageState();
}

class _TodoAtosPageState extends State<TodoAtosPage> {
  final List<String> _todoList = [];
  final TextEditingController _textController = TextEditingController();
  // 与鸿蒙原生通信的MethodChannel(名称与原生端一致)
  static const MethodChannel _channel = MethodChannel('com.example.todo_atos/todo_data');

  // 添加待办并同步到服务卡片
  void _addTodo() {
    if (_textController.text.isNotEmpty) {
      setState(() {
        _todoList.add(_textController.text);
      });
      // 同步数据到鸿蒙服务卡片
      _syncToServiceCard();
      _textController.clear();
    }
  }

  // 同步待办数据到鸿蒙原生端
  Future<void> _syncToServiceCard() async {
    try {
      // 将待办列表转换为字符串传递
      String todoData = _todoList.join('|');
      await _channel.invokeMethod('syncTodoData', {'data': todoData});
    } on PlatformException catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('同步卡片失败:${e.message}')),
      );
    }
  }

  // 删除待办
  void _deleteTodo(int index) {
    setState(() {
      _todoList.removeAt(index);
    });
    _syncToServiceCard();
  }

  @override
  Widget build(BuildContext context) {
    // 适配鸿蒙原子化服务的窗口大小(如2x2卡片对应的窗口)
    final screenSize = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        title: const Text('原子化待办服务'),
        // 原子化服务适配:隐藏返回按钮(免安装服务无上级页面)
        leading: null,
      ),
      body: Container(
        width: screenSize.width,
        height: screenSize.height,
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            // 输入框和添加按钮
            Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _textController,
                    decoration: const InputDecoration(
                      hintText: '输入待办(免安装服务)',
                      border: OutlineInputBorder(),
                    ),
                    onSubmitted: (value) => _addTodo(),
                  ),
                ),
                const SizedBox(width: 10),
                ElevatedButton(
                  onPressed: _addTodo,
                  child: const Text('添加'),
                ),
              ],
            ),
            const SizedBox(height: 20),
            // 待办列表(适配原子化服务的小窗口)
            Expanded(
              child: _todoList.isEmpty
                  ? const Center(child: Text('暂无待办事项'))
                  : ListView.builder(
                      itemCount: _todoList.length,
                      itemBuilder: (context, index) {
                        return ListTile(
                          title: Text(_todoList[index]),
                          trailing: IconButton(
                            icon: const Icon(Icons.delete, color: Colors.red),
                            onPressed: () => _deleteTodo(index),
                          ),
                          // 原子化服务适配:简化列表项样式
                          dense: true,
                        );
                      },
                    ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _textController.dispose();
    super.dispose();
  }
}

步骤 4:打包与发布原子化服务

  1. 打包 HAP 包:在 DevEco Studio 中选择Build > Build HAP(s) > Build Debug HAP(s),生成原子化服务的 HAP 包(免安装包)。
  2. 发布到鸿蒙应用市场:将 HAP 包上传到鸿蒙应用市场的原子化服务专区,用户可通过市场直接启动或添加到桌面卡片。

案例说明

  1. 原子化服务配置:通过config.json声明atomicServiceinstallationFree,开启免安装能力,成为鸿蒙原子化服务。
  2. Flutter 嵌入:鸿蒙原生 AbilitySlice 初始化 Flutter 引擎,将 Flutter 页面作为原子化服务的核心界面,实现免安装运行。
  3. 数据同步:Flutter 端通过MethodChannel将待办数据同步到鸿蒙原生端,再由原生端同步到服务卡片,实现 Flutter 功能与鸿蒙服务卡片的联动。
  4. 核心优势:用户无需安装应用,即可使用 Flutter 实现的待办功能,且支持卡片化展示,符合鸿蒙原子化服务的轻量化理念。

三、案例 2:鸿蒙 Flutter 应用全场景设备适配(手机 / 平板 / 智慧屏)

鸿蒙系统支持多种设备形态,Flutter 应用需要针对不同设备的屏幕尺寸、交互方式(如触屏、遥控器)进行适配。本案例将实现一个全场景适配的 Flutter 媒体应用,支持在手机、平板、智慧屏上的不同布局和交互方式。

核心适配策略

  1. 屏幕尺寸适配:使用MediaQueryLayoutBuilder获取设备屏幕信息,结合GridViewRow/Column实现自适应布局。
  2. 交互方式适配:针对手机的触屏、智慧屏的遥控器操作(如方向键、确认键)进行适配。
  3. 组件大小适配:根据设备类型调整组件的大小、字体、间距,提升大屏设备的体验。
  4. 设备类型判断:通过鸿蒙原生 API 获取设备类型(手机 / 平板 / 智慧屏),针对性调整 UI 逻辑。

步骤 1:Flutter 端获取鸿蒙设备类型

通过MethodChannel调用鸿蒙原生 API 获取设备类型,为后续适配提供依据。

鸿蒙原生端代码(获取设备类型)

// 在MainAbilitySlice.java中添加MethodChannel
private static final String DEVICE_CHANNEL = "com.example.device_info/type";

// 在onStart方法中注册
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), DEVICE_CHANNEL)
        .setMethodCallHandler((call, result) -> {
            if (call.method.equals("getDeviceType")) {
                // 获取鸿蒙设备类型
                String deviceType = getDeviceType();
                result.success(deviceType);
            } else {
                result.notImplemented();
            }
        });

/**
 * 判断鸿蒙设备类型
 * @return 设备类型:phone/tablet/tv
 */
private String getDeviceType() {
    String deviceType = "phone";
    // 获取鸿蒙设备类型信息
    ohos.system.DeviceInfo deviceInfo = ohos.system.DeviceInfo.getInstance();
    String deviceModel = deviceInfo.getDeviceModel();
    // 简化判断:实际项目可通过鸿蒙的DeviceType API判断
    if (deviceModel.contains("tablet") || deviceModel.contains("pad")) {
        deviceType = "tablet";
    } else if (deviceModel.contains("tv") || deviceModel.contains("box")) {
        deviceType = "tv";
    }
    return deviceType;
}
Flutter 端代码(封装设备类型获取方法)

import 'package:flutter/services.dart';

/// 设备类型工具类
class DeviceTypeUtil {
  static const MethodChannel _channel = MethodChannel('com.example.device_info/type');

  /// 获取鸿蒙设备类型:phone/tablet/tv
  static Future<String> getDeviceType() async {
    try {
      final String? type = await _channel.invokeMethod('getDeviceType');
      return type ?? 'phone';
    } on PlatformException {
      return 'phone';
    }
  }
}

步骤 2:Flutter 端实现全场景适配的媒体应用

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  // 智慧屏适配:隐藏状态栏(全屏显示)
  SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
  runApp(const MyMediaApp());
}

class MyMediaApp extends StatelessWidget {
  const MyMediaApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '鸿蒙全场景媒体应用',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const MediaHomePage(),
    );
  }
}

class MediaHomePage extends StatefulWidget {
  const MediaHomePage({super.key});

  @override
  State<MediaHomePage> createState() => _MediaHomePageState();
}

class _MediaHomePageState extends State<MediaHomePage> {
  String _deviceType = 'phone';
  // 模拟媒体数据
  final List<String> _mediaList = [
    "视频1:鸿蒙生态介绍",
    "视频2:Flutter全场景适配",
    "视频3:原子化服务开发",
    "视频4:分布式能力实战",
    "视频5:智慧屏交互设计",
    "视频6:平板分屏操作",
  ];

  @override
  void initState() {
    super.initState();
    // 获取设备类型
    _getDeviceType();
  }

  Future<void> _getDeviceType() async {
    String type = await DeviceTypeUtil.getDeviceType();
    setState(() {
      _deviceType = type;
    });
  }

  // 根据设备类型返回列数(手机:2列,平板:3列,智慧屏:4列)
  int _getCrossAxisCount() {
    switch (_deviceType) {
      case 'tablet':
        return 3;
      case 'tv':
        return 4;
      default:
        return 2;
    }
  }

  // 根据设备类型返回字体大小
  double _getFontSize() {
    switch (_deviceType) {
      case 'tv':
        return 20;
      case 'tablet':
        return 16;
      default:
        return 14;
    }
  }

  // 根据设备类型返回间距
  double _getSpacing() {
    switch (_deviceType) {
      case 'tv':
        return 20;
      case 'tablet':
        return 15;
      default:
        return 10;
    }
  }

  // 媒体项点击事件(智慧屏适配:支持遥控器确认键)
  void _onMediaTap(String media) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('播放:$media(设备:$_deviceType)')),
    );
  }

  @override
  Widget build(BuildContext context) {
    final screenSize = MediaQuery.of(context).size;
    return Scaffold(
      appBar: AppBar(
        title: Text('鸿蒙${_deviceType}媒体中心'),
        // 智慧屏适配:隐藏返回按钮(遥控器返回键替代)
        leading: _deviceType == 'tv' ? null : const Icon(Icons.arrow_back),
      ),
      body: Padding(
        padding: EdgeInsets.all(_getSpacing()),
        child: GridView.builder(
          // 智慧屏适配:增加焦点间距,方便遥控器操作
          padding: const EdgeInsets.all(10),
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
            crossAxisCount: _getCrossAxisCount(),
            crossAxisSpacing: _getSpacing(),
            mainAxisSpacing: _getSpacing(),
            childAspectRatio: _deviceType == 'tv' ? 1.5 : 1.0, // 智慧屏宽高比调整
          ),
          itemCount: _mediaList.length,
          itemBuilder: (context, index) {
            return InkWell(
              onTap: () => _onMediaTap(_mediaList[index]),
              // 智慧屏适配:添加焦点效果(遥控器选中时高亮)
              focusColor: Colors.blue.withOpacity(0.3),
              splashColor: Colors.blue.withOpacity(0.1),
              child: Container(
                decoration: BoxDecoration(
                  color: Colors.grey[100],
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(color: Colors.blue.withOpacity(0.5)),
                ),
                alignment: Alignment.center,
                child: Text(
                  _mediaList[index],
                  style: TextStyle(
                    fontSize: _getFontSize(),
                    fontWeight: FontWeight.w500,
                  ),
                  textAlign: TextAlign.center,
                ),
              ),
            );
          },
        ),
      ),
      // 平板适配:添加底部导航栏(分屏时仍可见)
      bottomNavigationBar: _deviceType == 'tablet'
          ? BottomNavigationBar(
              items: const [
                BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
                BottomNavigationBarItem(icon: Icon(Icons.video_library), label: '媒体'),
                BottomNavigationBarItem(icon: Icon(Icons.settings), label: '设置'),
              ],
              currentIndex: 1,
              onTap: (index) {},
            )
          : null,
    );
  }
}

案例说明

  1. 设备类型判断:通过MethodChannel调用鸿蒙原生 API 获取设备类型(手机 / 平板 / 智慧屏),为适配提供依据。
  2. 布局适配:根据设备类型调整GridView的列数、组件间距、字体大小,实现不同设备的布局优化。
  3. 交互适配:针对智慧屏的遥控器操作,添加焦点效果、隐藏触屏相关的返回按钮;针对平板添加底部导航栏,支持分屏显示。
  4. 显示适配:智慧屏适配为沉浸式全屏显示,手机和平板保留状态栏和导航栏,符合不同设备的交互习惯。

四、鸿蒙 Flutter 开发的全场景优化建议

  1. 响应式布局:优先使用MediaQueryLayoutBuilderExpanded等 Flutter 组件实现响应式布局,避免固定宽高,适配不同屏幕尺寸。
  2. 交互适配
    • 手机:支持触屏、手势操作(如滑动、缩放)。
    • 平板:支持分屏、拖拽操作,利用鸿蒙的多窗口能力。
    • 智慧屏:支持遥控器操作(焦点导航、确认 / 返回键)、语音控制。
    • 智能穿戴:简化 UI,突出核心功能,支持手势和语音交互。
  3. 性能优化
    • 智慧屏:减少动画效果,降低渲染开销,确保流畅的遥控器操作。
    • 智能穿戴:优化内存占用,使用轻量级组件,避免复杂计算。
  4. 鸿蒙原生能力融合:利用鸿蒙的多窗口管理分布式流转语音助手等原生能力,增强 Flutter 应用的全场景体验。

五、总结

本文通过原子化服务开发和全场景设备适配两个案例,展示了鸿蒙 Flutter 开发的高级场景。原子化服务让 Flutter 应用具备免安装、轻量化的特性,符合鸿蒙生态的用户体验;全场景适配则让 Flutter 应用能在不同形态的鸿蒙设备上提供一致且优化的体验。两者结合,能充分发挥 Flutter 的跨端优势和鸿蒙的全场景能力。

随着鸿蒙系统对 Flutter 支持的不断深化,以及 Flutter 响应式布局和跨平台能力的持续提升,鸿蒙 Flutter 开发将成为打造全场景应用的重要选择。开发者可基于本文的思路,进一步探索智能穿戴、车载设备等更多鸿蒙设备的 Flutter 适配方案,丰富鸿蒙生态的应用场景

欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),一起共建开源鸿蒙跨平台生态。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐