在鸿蒙生态的全场景服务体系中,原子化服务(Atomic Service)是核心载体 —— 它无需安装、即搜即用、轻量化运行,完美适配鸿蒙 “万物互联” 的分布式场景。而 Flutter 凭借跨端一致的 UI 渲染和高效的组件化开发能力,成为构建原子化服务前端界面的优选框架。此前我们探讨了鸿蒙 Flutter 的多模态交互、AI 能力集成、分布式协同等技术,本文将聚焦鸿蒙 Flutter 原子化服务的开发、打包、分发与适配,通过 “智能天气查询原子化服务” 完整案例,演示如何将 Flutter 应用改造为鸿蒙原子化服务,实现免安装启动、跨设备无缝调用与场景化分发。

一、原子化服务核心原理与 Flutter 融合逻辑

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

原子化服务是鸿蒙系统特有的服务形态,区别于传统安装式应用,具备三大核心特性:

  • 免安装运行:用户无需下载安装 APK,通过鸿蒙服务中心、智慧搜索、扫码等方式即可直接启动;
  • 轻量化体积:服务包(HAP)体积通常控制在 10MB 以内,聚焦单一核心功能,启动速度≤500ms;
  • 场景化分发:支持基于用户场景(如地理位置、使用时间、设备状态)的智能推荐与分发;
  • 跨设备流转:可在手机、平板、车机、智慧屏等鸿蒙设备间无缝流转,保持服务状态一致。

2. Flutter 与原子化服务的融合逻辑

Flutter 开发原子化服务遵循 “功能轻量化、接口标准化、交互适配化” 三大原则,核心融合架构分为三层:

  1. 鸿蒙原生层:负责原子化服务的生命周期管理(Ability)、服务注册与分发(ServiceAbility)、权限申请与设备交互,是服务运行的基础;
  2. Flutter 适配层:通过ohos_flutter_ability_adapter插件,将 Flutter 引擎嵌入鸿蒙原子化服务的Ability中,实现 Flutter 页面与鸿蒙原生能力的通信,同时对 Flutter 包体积进行裁剪(移除无用插件、资源压缩);
  3. Flutter 业务层:聚焦单一核心功能(如天气查询、快递查询),采用轻量化 UI 组件,避免复杂动画与冗余逻辑,确保服务启动速度与运行效率。

3. 原子化服务生命周期与 Flutter 页面联动

鸿蒙原子化服务的核心是 **ServiceAbility(后台服务)与PageAbility**(前端页面)的协同,与 Flutter 页面的生命周期联动逻辑如下:

  • 启动阶段:用户触发原子化服务(如扫码)→ 鸿蒙系统加载服务 HAP 包 → 初始化PageAbility → 启动 Flutter 引擎 → 渲染 Flutter 核心页面;
  • 运行阶段:Flutter 页面通过MethodChannel调用鸿蒙原生能力(如定位、天气接口)→ 原生层返回数据 → Flutter 更新 UI;
  • 流转阶段:用户触发跨设备流转 → 鸿蒙分布式能力保存 Flutter 页面状态 → 目标设备加载服务并恢复状态;
  • 销毁阶段:服务后台运行超时 / 用户主动关闭 → 销毁 Flutter 引擎 → 释放Ability资源。

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

本案例将实现一款免安装、轻量化的智能天气查询原子化服务,核心功能包括:

  1. 基于鸿蒙原生定位能力获取用户所在城市;
  2. 调用鸿蒙天气服务 API 获取实时天气、温度、风力等信息;
  3. 支持一键分享天气信息至鸿蒙社交应用;
  4. 适配手机、平板、车机等多设备屏幕尺寸;
  5. 打包为鸿蒙原子化服务包(HAP),支持服务中心分发与扫码启动。

前置条件

  1. 已配置鸿蒙 DevEco Studio 4.3 + 与 Flutter 3.24 + 环境,安装ohos_flutter_ability_adapter插件;
  2. 已注册鸿蒙开发者账号,完成原子化服务开发者认证;
  3. 已获取鸿蒙天气服务 API 密钥(通过鸿蒙开发者平台申请);
  4. 已掌握鸿蒙 HAP 包打包与签名流程。

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

鸿蒙原生层是原子化服务的基础,需完成Ability配置、权限申请、天气 API 调用与 Flutter 通信封装。

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

entry/src/main/module.json5中配置原子化服务的基本信息、权限与启动类型,核心配置如下

{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "MainAbility",
    "deviceTypes": ["phone", "tablet", "car"],
    "deliveryWithInstall": false, // 非随应用安装,为原子化服务
    "installationFree": true, // 开启免安装特性
    "reqPermissions": [
      {
        "name": "ohos.permission.LOCATION",
        "reason": "$string:location_reason",
        "usedScene": { "abilities": [".MainAbility"], "when": "inuse" }
      },
      {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:internet_reason",
        "usedScene": { "abilities": [".MainAbility"], "when": "always" }
      }
    ],
    "abilities": [
      {
        "name": ".MainAbility",
        "type": "page", // 页面型Ability,承载Flutter UI
        "visible": true,
        "launchType": "standard",
        "description": "$string:main_ability_desc",
        "icon": "$media:icon",
        "label": "$string:main_ability_label",
        "skills": [ // 原子化服务调用规则,支持扫码、搜索触发
          {
            "entities": ["entity.system.service"],
            "actions": ["action.system.view"]
          }
        ],
        "metadata": [
          {
            "name": "flutterAbility",
            "value": "true" // 标记为Flutter Ability
          }
        ]
      }
    ]
  }
}

2. 原生能力封装(ArkTS)

封装鸿蒙定位服务与天气 API 调用能力,通过MethodChannel向 Flutter 层提供天气数据。

// service/WeatherService.ts
import geoLocation from '@ohos.geo.location';
import http from '@ohos.net.http';

// 天气数据模型
export interface WeatherInfo {
  city: string; // 城市名称
  temp: number; // 实时温度(℃)
  weather: string; // 天气状况(晴/阴/雨)
  wind: string; // 风力
  humidity: number; // 湿度(%)
  updateTime: string; // 更新时间
}

export class WeatherService {
  private weatherApiKey: string = "YOUR_HARMONY_WEATHER_API_KEY"; // 替换为你的API密钥
  private httpClient: http.HttpClient | null = null;

  constructor() {
    this.httpClient = http.createHttpClient();
  }

  // 获取用户当前定位城市
  async getCurrentCity(): Promise<string> {
    return new Promise((resolve, reject) => {
      // 配置定位参数
      const locationRequest = {
        priority: geoLocation.LocationRequestPriority.BALANCED,
        scenario: geoLocation.LocationRequestScenario.UNSET,
        timeInterval: 0,
        distanceInterval: 0
      };

      // 单次定位
      geoLocation.getCurrentLocation(locationRequest, (err, location) => {
        if (err) {
          reject(`定位失败:${err.message}`);
          return;
        }
        // 逆地理编码获取城市(简化版,实际需调用鸿蒙地理编码服务)
        resolve(`北京市`); // 模拟定位结果,实际需替换为真实逆编码逻辑
      });
    });
  }

  // 调用天气API获取天气数据
  async getWeather(city: string): Promise<WeatherInfo> {
    if (!this.httpClient) throw new Error("HTTP客户端未初始化");

    const url = `https://api.weather.harmonyos.com/v1/weather?city=${encodeURIComponent(city)}&key=${this.weatherApiKey}`;

    return new Promise((resolve, reject) => {
      this.httpClient.request(url, http.RequestMethod.GET, (err, response) => {
        if (err) {
          reject(`天气请求失败:${err.message}`);
          return;
        }

        const result = JSON.parse(response.result.toString());
        if (result.code === 200) {
          const data = result.data;
          resolve({
            city: city,
            temp: data.temp,
            weather: data.weather,
            wind: `${data.windDir} ${data.windPower}级`,
            humidity: data.humidity,
            updateTime: data.updateTime
          });
        } else {
          reject(`天气服务返回错误:${result.msg}`);
        }
      });
    });
  }

  // 释放资源
  destroy(): void {
    this.httpClient?.destroy();
    this.httpClient = null;
  }
}

3. Flutter 与原生通信封装(EntryAbility.ts)

EntryAbility中初始化 Flutter 引擎,通过MethodChannel实现 Flutter 与原生天气服务的通信。

// EntryAbility.ts
import Ability from '@ohos.app.ability.UIAbility';
import Window from '@ohos.window';
import { WeatherService, WeatherInfo } from './service/WeatherService';
import { MethodChannel } from '@ohos.flutter.engine';

export default class EntryAbility extends Ability {
  private weatherService: WeatherService = new WeatherService();
  private flutterChannel?: MethodChannel;

  onCreate(want, launchParam) {
    console.log("原子化服务启动 onCreate");
  }

  onWindowStageCreate(windowStage: Window.WindowStage) {
    // 初始化Flutter引擎
    const flutterEngine = this.context.flutterEngine;
    if (flutterEngine) {
      // 创建MethodChannel,用于与Flutter通信
      this.flutterChannel = new MethodChannel(flutterEngine.dartExecutor.binaryMessenger, 'com.weather.atomic.channel');
      
      // 注册MethodChannel方法处理器
      this.flutterChannel.setMethodCallHandler((call, result) => {
        switch (call.method) {
          case 'getCurrentCity':
            this.weatherService.getCurrentCity().then(city => {
              result.success(city);
            }).catch(err => {
              result.error('LOCATION_ERROR', err.message, null);
            });
            break;
          case 'getWeatherByCity':
            const city = call.arguments['city'] as string;
            this.weatherService.getWeather(city).then(weather => {
              result.success(weather);
            }).catch(err => {
              result.error('WEATHER_ERROR', err.message, null);
            });
            break;
          default:
            result.notImplemented();
        }
      });
    }

    // 加载Flutter页面
    windowStage.loadContent('flutter://entrypoint/default').then(() => {
      windowStage.getMainWindow().then(window => {
        window.setFullScreen(false);
        // 适配原子化服务窗口大小
        window.resize(400, 600);
      });
    });
  }

  onDestroy() {
    console.log("原子化服务销毁 onDestroy");
    this.weatherService.destroy();
  }
}

四、步骤 2:Flutter 层轻量化 UI 与业务逻辑实现

Flutter 层需遵循轻量化、高启动速度原则,移除冗余依赖,聚焦天气查询核心功能,实现跨设备适配的简洁 UI。

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

封装与原生通信的方法,提供简洁的 API 供 Flutter UI 调用。

// lib/services/atomic_weather_service.dart
import 'package:flutter/services.dart';

// 天气数据模型
class WeatherInfo {
  final String city;
  final double temp;
  final String weather;
  final String wind;
  final int humidity;
  final String updateTime;

  WeatherInfo({
    required this.city,
    required this.temp,
    required this.weather,
    required this.wind,
    required this.humidity,
    required this.updateTime,
  });

  // 从原生Map转换为模型
  factory WeatherInfo.fromJson(Map<String, dynamic> json) {
    return WeatherInfo(
      city: json['city'] as String,
      temp: (json['temp'] as num).toDouble(),
      weather: json['weather'] as String,
      wind: json['wind'] as String,
      humidity: json['humidity'] as int,
      updateTime: json['updateTime'] as String,
    );
  }
}

// 原子化天气服务工具类
class AtomicWeatherService {
  static const MethodChannel _channel = MethodChannel('com.weather.atomic.channel');

  // 获取当前城市
  static Future<String> getCurrentCity() async {
    try {
      return await _channel.invokeMethod('getCurrentCity');
    } on PlatformException catch (e) {
      throw Exception('定位失败:${e.message}');
    }
  }

  // 根据城市获取天气
  static Future<WeatherInfo> getWeatherByCity(String city) async {
    try {
      final Map<String, dynamic> result = await _channel.invokeMethod('getWeatherByCity', {'city': city});
      return WeatherInfo.fromJson(result);
    } on PlatformException catch (e) {
      throw Exception('天气查询失败:${e.message}');
    }
  }
}

2. 实现轻量化天气查询 UI(Dart)

设计简洁、适配多设备的 UI 界面,支持手动切换城市,展示核心天气数据。

// lib/main.dart
import 'package:flutter/material.dart';
import 'services/atomic_weather_service.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '鸿蒙原子化天气服务',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const WeatherHomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

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

  @override
  State<WeatherHomePage> createState() => _WeatherHomePageState();
}

class _WeatherHomePageState extends State<WeatherHomePage> {
  String _currentCity = "加载中...";
  WeatherInfo? _weatherInfo;
  bool _isLoading = true;
  String _errorMsg = "";

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

  // 加载天气数据
  Future<void> _loadWeatherData() async {
    setState(() {
      _isLoading = true;
      _errorMsg = "";
    });
    try {
      // 获取当前城市
      final city = await AtomicWeatherService.getCurrentCity();
      // 查询天气
      final weather = await AtomicWeatherService.getWeatherByCity(city);
      setState(() {
        _currentCity = city;
        _weatherInfo = weather;
        _isLoading = false;
      });
    } catch (e) {
      setState(() {
        _isLoading = false;
        _errorMsg = e.toString();
      });
    }
  }

  // 手动切换城市
  Future<void> _changeCity() async {
    final String? newCity = await showDialog<String>(
      context: context,
      builder: (context) {
        final TextEditingController controller = TextEditingController(text: _currentCity);
        return AlertDialog(
          title: const Text("切换城市"),
          content: TextField(
            controller: controller,
            decoration: const InputDecoration(hintText: "请输入城市名称"),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text("取消"),
            ),
            TextButton(
              onPressed: () => Navigator.pop(context, controller.text.trim()),
              child: const Text("确定"),
            ),
          ],
        );
      },
    );

    if (newCity != null && newCity.isNotEmpty) {
      setState(() {
        _isLoading = true;
      });
      try {
        final weather = await AtomicWeatherService.getWeatherByCity(newCity);
        setState(() {
          _currentCity = newCity;
          _weatherInfo = weather;
          _isLoading = false;
        });
      } catch (e) {
        setState(() {
          _isLoading = false;
          _errorMsg = e.toString();
        });
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("原子化天气服务"),
        centerTitle: true,
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: _loadWeatherData,
          ),
        ],
      ),
      body: _buildBody(),
    );
  }

  // 构建页面主体
  Widget _buildBody() {
    if (_isLoading) {
      return const Center(child: CircularProgressIndicator());
    }

    if (_errorMsg.isNotEmpty) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(_errorMsg, style: const TextStyle(color: Colors.red, fontSize: 16)),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _loadWeatherData,
              child: const Text("重试"),
            ),
          ],
        ),
      );
    }

    if (_weatherInfo == null) {
      return const Center(child: Text("未获取到天气数据"));
    }

    return Padding(
      padding: const EdgeInsets.all(24.0),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          // 城市选择
          GestureDetector(
            onTap: _changeCity,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  _currentCity,
                  style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
                ),
                const SizedBox(width: 8),
                const Icon(Icons.arrow_drop_down, size: 24),
              ],
            ),
          ),
          const SizedBox(height: 40),

          // 实时温度
          Center(
            child: Text(
              "${_weatherInfo!.temp}℃",
              style: const TextStyle(fontSize: 80, fontWeight: FontWeight.bold),
            ),
          ),
          const SizedBox(height: 16),

          // 天气状况
          Center(
            child: Text(
              _weatherInfo!.weather,
              style: const TextStyle(fontSize: 24, color: Colors.grey),
            ),
          ),
          const SizedBox(height: 40),

          // 详细信息
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: [
              _buildWeatherDetail("风力", _weatherInfo!.wind),
              _buildWeatherDetail("湿度", "${_weatherInfo!.humidity}%"),
            ],
          ),
          const SizedBox(height: 20),

          // 更新时间
          Center(
            child: Text(
              "更新时间:${_weatherInfo!.updateTime}",
              style: const TextStyle(fontSize: 14, color: Colors.grey),
            ),
          ),
        ],
      ),
    );
  }

  // 构建天气详情项
  Widget _buildWeatherDetail(String title, String value) {
    return Column(
      children: [
        Text(title, style: const TextStyle(fontSize: 16, color: Colors.grey)),
        const SizedBox(height: 8),
        Text(value, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500)),
      ],
    );
  }
}

五、步骤 3:原子化服务打包、签名与分发

完成开发后,需将应用打包为鸿蒙原子化服务包(HAP),并通过鸿蒙服务中心分发。

1. Flutter 包体积优化

原子化服务要求 HAP 包体积≤10MB,需对 Flutter 工程进行体积优化:

  • 移除冗余依赖:删除pubspec.yaml中未使用的插件(如firebase_corevideo_player);
  • 资源压缩:压缩图片资源(使用 WebP 格式),移除未使用的字体与国际化资源;
  • 代码混淆:在build.gradle中开启代码混淆,减少 Dart 代码体积;
  • 引擎裁剪:通过鸿蒙 DevEco Studio 的 Flutter 引擎裁剪工具,移除未使用的 Flutter 引擎模块。

2. 打包 HAP 包

  1. 打开鸿蒙 DevEco Studio,选择Build > Build HAP(s)
  2. 选择Installation-free(免安装)模式,点击Build
  3. 打包完成后,在entry/build/outputs/hap/installation_free/debug目录下获取 HAP 包。

3. 服务签名与分发

  1. 签名:使用鸿蒙开发者账号申请的签名证书,对 HAP 包进行签名(需在build-profile.json5中配置签名信息);
  2. 发布至服务中心:登录鸿蒙开发者平台,提交 HAP 包与服务信息(名称、图标、描述),审核通过后即可在鸿蒙服务中心展示;
  3. 扫码分发:通过 DevEco Studio 生成服务二维码,用户扫码即可直接启动原子化服务,无需安装。

六、关键优化与扩展场景

1. 原子化服务核心优化要点

  • 启动速度优化:Flutter 页面采用StatelessWidget减少初始化耗时,原生层预加载定位与天气服务,确保服务启动时间≤500ms;
  • 功耗优化:服务进入后台后,10 秒内自动释放定位与 HTTP 资源,避免持续占用设备功耗;
  • 异常处理优化:添加定位失败、网络异常的兜底逻辑,展示友好的错误提示与重试按钮;
  • 多设备适配优化:通过MediaQuery获取设备尺寸,动态调整 UI 布局,适配手机、平板、车机的屏幕比例。

2. 扩展场景

  1. 场景化推荐:接入鸿蒙场景感知服务,实现 “用户出门时自动推荐天气服务” 的智能分发;
  2. 服务联动:与鸿蒙原子化支付服务联动,实现 “恶劣天气自动推荐打车服务” 的一站式体验;
  3. 分布式流转:集成鸿蒙分布式能力,支持天气服务从手机流转至车机,上车后自动显示目的地天气;
  4. 离线缓存:添加天气数据离线缓存功能,无网络时展示缓存数据,提升服务可用性。

七、总结

本文通过智能天气查询原子化服务案例,完整演示了鸿蒙 Flutter 原子化服务的开发、打包与分发流程。核心在于遵循鸿蒙原子化服务的轻量化特性,通过 “原生能力封装 + Flutter 轻量化 UI” 的融合模式,实现免安装、即搜即用的全场景服务体验。

随着鸿蒙生态的持续扩张,原子化服务将成为连接用户与场景的核心载体。Flutter 作为跨端开发框架,凭借高效的 UI 开发能力与鸿蒙原生的深度融合,将在原子化服务开发中发挥重要作用。开发者可基于本文思路,探索更多轻量化服务场景(如快递查询、公交查询、优惠券领取),打造符合鸿蒙生态标准的原子化服务。

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


Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐