在鸿蒙生态的全场景服务体系中,原子化服务是核心载体之一。它无需安装、即搜即用、轻量化部署的特性,完美契合了鸿蒙 “一次开发、多端部署” 的理念。Flutter 凭借跨端一致的 UI 渲染和高效的组件化能力,成为开发鸿蒙原子化服务的优选框架。本文将聚焦鸿蒙 Flutter 原子化服务的封装流程跨设备免安装调用技术,通过 “智能天气查询” 原子化服务案例,演示如何将 Flutter 应用封装为鸿蒙原子化服务,实现跨设备的免安装快速调用。

一、鸿蒙原子化服务核心特性与 Flutter 适配逻辑

1. 鸿蒙原子化服务核心特性

原子化服务(Atomic Service)是鸿蒙生态特有的服务形态,具备五大核心特性:

  • 免安装运行:用户无需下载安装 APK,通过鸿蒙服务中心、智慧搜索、设备分享等方式即可直接调用;
  • 轻量化部署:服务包体积通常小于 10MB,启动速度比传统应用快 30% 以上;
  • 跨设备流转:支持在手机、平板、智慧屏、车机等鸿蒙设备间无缝流转,服务状态实时同步;
  • 精准服务分发:基于用户场景和设备能力,通过鸿蒙系统的智能推荐引擎精准推送;
  • 独立运行能力:依赖鸿蒙系统的服务运行时(Service Runtime),可独立于宿主应用运行,资源占用可控。

2. Flutter 与原子化服务的适配逻辑

Flutter 应用封装为鸿蒙原子化服务,遵循 “能力封装、界面适配、入口标准化” 三大原则,整体架构分为三层:

  1. 鸿蒙原生服务层:基于鸿蒙Ability框架,将 Flutter 应用封装为原子化服务 Ability,配置服务元数据、入口协议和权限;
  2. Flutter 业务层:实现原子化服务的核心功能,适配不同设备的屏幕尺寸和交互逻辑,支持服务的启动、暂停、恢复等生命周期管理;
  3. 跨设备调用层:通过鸿蒙服务分发协议,实现原子化服务的跨设备发现、调用和流转,支持服务的远程启动和数据交互。

3. 原子化服务的调用流转机制

以 “手机调用平板上的天气原子化服务” 为例,完整的调用流程如下:

  1. 服务发现:手机通过鸿蒙分布式软总线,发现同一账号下平板上发布的 “智能天气查询” 原子化服务;
  2. 协议匹配:手机端发起调用请求,鸿蒙系统校验服务的入口协议(如ohos.want.action.QUERY_WEATHER)和权限;
  3. 远程启动:平板端的服务运行时启动 Flutter 原子化服务,执行天气查询逻辑;
  4. 结果返回:服务运行结果通过分布式软总线同步至手机端,在手机界面展示天气信息;
  5. 服务销毁:调用完成后,服务自动销毁或进入休眠状态,释放设备资源。

二、案例:智能天气查询原子化服务

本案例将实现一款基于 Flutter 的鸿蒙原子化天气查询服务,核心功能包括:

  1. 免安装调用:用户通过鸿蒙服务中心直接启动,无需安装;
  2. 跨设备调用:支持手机、平板、智慧屏等设备间的服务调用与数据同步;
  3. 轻量化交互:提供简洁的天气查询界面,支持输入城市名查询实时天气;
  4. 服务流转:查询结果可流转至其他设备,支持跨设备查看天气详情;
  5. 适配多设备:自动适配手机竖屏、平板横屏、智慧屏大屏的显示逻辑。

前置条件

  1. 已配置鸿蒙 DevEco Studio 4.3 + 与 Flutter 3.24 + 环境,安装ohos_flutter_atom_service插件;
  2. 已完成鸿蒙开发者账号认证,获取原子化服务发布权限;
  3. 已准备至少两台鸿蒙设备(如手机 + 平板),登录同一鸿蒙账号并开启分布式软总线;
  4. 已接入第三方天气 API(如高德天气 API),获取 API 调用密钥。

三、步骤 1:鸿蒙原生层原子化服务封装

鸿蒙原生层负责将 Flutter 应用封装为原子化服务Ability,配置服务元数据、入口协议和分布式调用能力。

1. 原子化服务配置(module.json5)

entry/src/main/module.json5中配置原子化服务的核心信息,包括服务类型、入口协议、权限等:

{
  "module": {
    "name": "weather_atom_service",
    "type": "entry",
    "reqPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "需要访问天气API获取数据",
        "usedScene": { "abilities": [".WeatherAtomAbility"], "when": "inuse" }
      },
      {
        "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO",
        "reason": "需要跨设备调用服务",
        "usedScene": { "abilities": [".WeatherAtomAbility"], "when": "inuse" }
      }
    ],
    "abilities": [
      {
        "name": ".WeatherAtomAbility",
        "type": "page",
        "visible": true,
        "exported": true, // 允许跨设备调用
        "skills": [
          {
            "entities": ["entity.system.service"], // 标记为原子化服务
            "actions": ["ohos.want.action.QUERY_WEATHER"] // 服务入口协议
          }
        ],
        "metadata": [
          {
            "name": "flutterAbility",
            "value": "true"
          },
          {
            "name": "atomServiceMetaData", // 原子化服务元数据
            "value": {
              "name": "智能天气查询",
              "description": "免安装查询实时天气",
              "icon": "$media:icon",
              "version": "1.0.0"
            }
          }
        ],
        "launchType": "standard",
        "orientation": "unspecified" // 自适应屏幕方向
      }
    ]
  }
}

2. 原子化服务 Ability 封装(ArkTS)

实现WeatherAtomAbility,管理 Flutter 原子化服务的生命周期,处理跨设备调用请求:

// entry/src/main/ets/ability/WeatherAtomAbility.ts
import Ability from '@ohos.app.ability.UIAbility';
import Want from '@ohos.app.ability.Want';
import window from '@ohos.window';
import { Logger } from '../utils/Logger';

export default class WeatherAtomAbility extends Ability {
  private weatherData: Record<string, string> = {}; // 存储天气数据

  onCreate(want: Want, launchParam: any) {
    Logger.info('WeatherAtomAbility onCreate');
    // 处理跨设备调用参数
    if (want.parameters?.city) {
      this.weatherData.city = want.parameters.city as string;
    }
  }

  onWindowStageCreate(windowStage: window.WindowStage) {
    Logger.info('WeatherAtomAbility onWindowStageCreate');
    // 加载Flutter界面
    windowStage.loadContent('flutter://entrypoint/default').then(() => {
      windowStage.getMainWindow().then((win) => {
        // 设置窗口大小(原子化服务通常为小窗口)
        win.setWindowLayout(0, 0, 600, 800);
        win.setWindowDecorationStyle(window.DecorationStyle.NONE); // 无边框
      });
    });
  }

  // 处理跨设备服务调用请求
  onAcceptWant(want: Want, callback: (data: Want) => void) {
    Logger.info('接收跨设备调用请求:', want.parameters?.city);
    if (want.parameters?.city) {
      this.weatherData.city = want.parameters.city as string;
      // 将天气数据返回给调用方
      const resultWant: Want = {
        parameters: {
          city: this.weatherData.city,
          temperature: '25°C',
          condition: '晴',
          humidity: '50%'
        }
      };
      callback(resultWant);
    }
  }

  onDestroy() {
    Logger.info('WeatherAtomAbility onDestroy');
    this.weatherData = {};
  }
}

3. 跨设备服务调用工具类(ArkTS)

封装跨设备服务发现与调用能力,供 Flutter 层调用:

// entry/src/main/ets/service/DistributedCallService.ts
import deviceManager from '@ohos.distributedHardware.deviceManager';
import bundleManager from '@ohos.bundle.bundleManager';
import Want from '@ohos.app.ability.Want';
import { Logger } from '../utils/Logger';

export class DistributedCallService {
  private dm: deviceManager.DeviceManager | null = null;
  private bundleName: string = 'com.example.weatheratom';
  private abilityName: string = 'WeatherAtomAbility';

  // 初始化设备管理器
  async init() {
    try {
      this.dm = await deviceManager.createDeviceManager(this.bundleName);
      Logger.info('分布式调用服务初始化成功');
    } catch (e) {
      Logger.error('分布式调用服务初始化失败:', JSON.stringify(e));
    }
  }

  // 获取绑定的设备列表
  getBoundDevices(): deviceManager.DeviceInfo[] {
    if (!this.dm) return [];
    return this.dm.getTrustedDeviceListSync() || [];
  }

  // 跨设备调用天气服务
  async callWeatherService(deviceId: string, city: string): Promise<Record<string, string>> {
    if (!this.dm) throw new Error('设备管理器未初始化');

    const want: Want = {
      bundleName: this.bundleName,
      abilityName: this.abilityName,
      deviceId: deviceId,
      action: 'ohos.want.action.QUERY_WEATHER',
      parameters: { city: city }
    };

    return new Promise((resolve, reject) => {
      bundleManager.startAbilityByWant(want, (err, data) => {
        if (err) {
          reject(err);
          return;
        }
        resolve(data?.parameters as Record<string, string>);
      });
    });
  }
}

四、步骤 2:Flutter 层原子化服务界面与业务实现

Flutter 层负责实现天气查询的核心功能,适配原子化服务的轻量化交互需求,同时与原生层通信获取跨设备调用能力。

1. 天气服务工具类封装(Dart)

封装天气 API 调用和跨设备服务调用接口:

// lib/services/weather_service.dart
import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;

// 天气数据模型
class WeatherModel {
  final String city;
  final String temperature;
  final String condition;
  final String humidity;

  WeatherModel({
    required this.city,
    required this.temperature,
    required this.condition,
    required this.humidity,
  });

  factory WeatherModel.fromJson(Map<String, dynamic> json) {
    return WeatherModel(
      city: json['city'],
      temperature: json['temperature'],
      condition: json['condition'],
      humidity: json['humidity'],
    );
  }
}

class WeatherService {
  static const MethodChannel _channel = MethodChannel('com.weather.atom.service');
  static const String _weatherApiKey = 'your_amap_weather_api_key';
  static const String _weatherApiUrl = 'https://restapi.amap.com/v3/weather/weatherInfo';

  // 调用高德天气API获取天气数据
  static Future<WeatherModel> fetchWeather(String city) async {
    final response = await http.get(Uri.parse(
      '$_weatherApiUrl?city=$city&key=$_weatherApiKey&extensions=base',
    ));

    if (response.statusCode == 200) {
      final data = json.decode(response.body);
      final weather = data['lives'][0];
      return WeatherModel(
        city: weather['city'],
        temperature: '${weather['temperature']}°C',
        condition: weather['weather'],
        humidity: '${weather['humidity']}%',
      );
    } else {
      throw Exception('Failed to fetch weather');
    }
  }

  // 获取绑定的设备列表
  static Future<List<String>> getBoundDevices() async {
    final List<dynamic> devices = await _channel.invokeMethod('getBoundDevices');
    return devices.map((e) => e.toString()).toList();
  }

  // 跨设备调用天气服务
  static Future<WeatherModel> callRemoteWeatherService(String deviceId, String city) async {
    final Map<String, dynamic> result = await _channel.invokeMethod(
      'callWeatherService',
      {'deviceId': deviceId, 'city': city},
    );
    return WeatherModel.fromJson(result);
  }
}

2. 原子化服务主界面实现(Dart)

实现轻量化的天气查询界面,支持本地查询和跨设备查询:

// lib/main.dart
import 'package:flutter/material.dart';
import 'services/weather_service.dart';
import 'models/weather_model.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '智能天气查询',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const WeatherAtomPage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

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

  @override
  State<WeatherAtomPage> createState() => _WeatherAtomPageState();
}

class _WeatherAtomPageState extends State<WeatherAtomPage> {
  final TextEditingController _cityController = TextEditingController();
  WeatherModel? _weatherModel;
  List<String> _deviceList = [];
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    _loadBoundDevices();
  }

  // 加载绑定的设备列表
  Future<void> _loadBoundDevices() async {
    final devices = await WeatherService.getBoundDevices();
    setState(() => _deviceList = devices);
  }

  // 本地查询天气
  Future<void> _fetchLocalWeather() async {
    if (_cityController.text.isEmpty) return;
    setState(() => _isLoading = true);
    try {
      final weather = await WeatherService.fetchWeather(_cityController.text);
      setState(() => _weatherModel = weather);
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('查询失败:$e')),
      );
    } finally {
      setState(() => _isLoading = false);
    }
  }

  // 跨设备查询天气
  Future<void> _fetchRemoteWeather(String deviceId) async {
    if (_cityController.text.isEmpty) return;
    setState(() => _isLoading = true);
    try {
      final weather = await WeatherService.callRemoteWeatherService(
        deviceId,
        _cityController.text,
      );
      setState(() => _weatherModel = weather);
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('跨设备查询失败:$e')),
      );
    } finally {
      setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              '智能天气查询',
              style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 20),
            TextField(
              controller: _cityController,
              decoration: const InputDecoration(
                hintText: '请输入城市名',
                border: OutlineInputBorder(),
                prefixIcon: Icon(Icons.location_city),
              ),
            ),
            const SizedBox(height: 16),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                ElevatedButton(
                  onPressed: _fetchLocalWeather,
                  child: const Text('本地查询'),
                ),
                if (_deviceList.isNotEmpty)
                  PopupMenuButton<String>(
                    icon: const Icon(Icons.devices),
                    itemBuilder: (context) => _deviceList
                        .map((device) => PopupMenuItem(
                              value: device,
                              child: Text(device),
                            ))
                        .toList(),
                    onSelected: _fetchRemoteWeather,
                  ),
              ],
            ),
            const SizedBox(height: 30),
            if (_isLoading)
              const CircularProgressIndicator()
            else if (_weatherModel != null)
              Column(
                children: [
                  Text(
                    _weatherModel!.city,
                    style: const TextStyle(fontSize: 20),
                  ),
                  const SizedBox(height: 8),
                  Text(
                    '温度:${_weatherModel!.temperature}',
                    style: const TextStyle(fontSize: 18),
                  ),
                  Text(
                    '天气:${_weatherModel!.condition}',
                    style: const TextStyle(fontSize: 18),
                  ),
                  Text(
                    '湿度:${_weatherModel!.humidity}',
                    style: const TextStyle(fontSize: 18),
                  ),
                ],
              ),
          ],
        ),
      ),
    );
  }
}

五、原子化服务核心优化与发布要点

1. 轻量化优化

  • 包体积瘦身:移除 Flutter 的非必要组件(如 WebView、动画库),压缩资源文件,确保服务包体积小于 10MB;
  • 启动速度优化:采用 Flutter 的预编译模式(AOT),减少运行时编译时间,启动时间控制在 1 秒内;
  • 资源占用优化:限制 Flutter 引擎的内存占用(不超过 200MB),服务闲置时自动释放资源。

2. 跨设备适配优化

  • 屏幕自适应:通过 Flutter 的LayoutBuilderMediaQuery适配不同设备的屏幕尺寸,支持小窗口、横屏、大屏等布局;
  • 交互适配:针对原子化服务的轻量化交互需求,简化操作流程,减少用户输入步骤;
  • 网络适配:支持离线缓存天气数据,无网络时展示本地缓存内容。

3. 原子化服务发布要点

  • 元数据配置:确保module.json5中的服务名称、描述、图标等元数据准确无误,影响服务在鸿蒙服务中心的展示;
  • 权限合规:仅申请必要的权限,避免过度申请权限导致审核不通过;
  • 多设备测试:在手机、平板、智慧屏等设备上测试服务的调用和流转能力,确保跨设备体验一致;
  • 发布渠道:通过鸿蒙开发者联盟发布原子化服务,接入鸿蒙服务中心,实现精准分发。

六、扩展场景与进阶建议

  1. 服务联动:将天气原子化服务与智能家居原子化服务联动,根据天气自动调整空调温度;
  2. 卡片化展示:将天气服务封装为鸿蒙服务卡片,支持在手机桌面、智慧屏负一屏展示实时天气;
  3. 语音调用:集成鸿蒙语音助手,支持通过语音指令 “查询北京天气” 直接调用原子化服务;
  4. 批量分发:针对企业用户,实现原子化服务的批量部署和管理,支持企业定制化需求。

七、总结

本文通过智能天气查询案例,完整演示了鸿蒙 Flutter 原子化服务的封装与跨设备调用流程。核心在于利用鸿蒙Ability框架将 Flutter 应用封装为标准化的原子化服务,通过分布式软总线实现跨设备的发现与调用,最终为用户提供 “免安装、轻量化、跨设备” 的服务体验。

在鸿蒙全场景生态中,原子化服务是连接设备与用户的重要桥梁。Flutter 凭借跨端开发的高效性,能够快速适配原子化服务的开发需求。开发者可基于本文思路,探索更多原子化服务场景,如快递查询、单位换算、智能翻译等,丰富鸿蒙生态的服务形态。

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐