【共创季稿事节】HarmonyOS 7.0 元服务开发实战:从零构建一个跨设备天气服务
文章目录

每日一句正能量
真正的高手都是专注把一件事做到极致的人。
高手不靠做很多事取胜,而靠把一件事挖到别人挖不到的深度。极致不是宽度,是深度;不是选项多,是放弃多。
导读
*元服务(Atomic Service)是鸿蒙生态中"免安装、即用即走"的轻量服务形态,也是 7.0 意图框架的核心入口之一。在校企合作的实训课程中,我通常会用"天气服务"作为元服务的入门项目——它需求清晰、数据实时、天然适合卡片化展示,且能很好地演示跨设备流转能力。本文将完整记录从零开始构建一个 HarmonyOS 7.0 跨设备天气元服务的全过程,涵盖工程创建、卡片设计、主页面开发、天气数据获取、跨设备流转和分布式数据同步六大环节。所有代码基于 API 26(7.0)风格编写,可直接在 DevEco Studio 4.2 中运行。
一、项目创建与工程结构
1.1 创建元服务工程
打开 DevEco Studio 4.2,选择 File → New → Create Project,在模板列表中选择 “Atomic Service”。
工程配置:
- Project name:WeatherAtom
- Bundle name:com.example.weatheratom
- Compile SDK:API 26(7.0)
- Model:Stage
1.2 工程结构说明
创建完成后,工程目录如下:
WeatherAtom/
├── entry/src/main/
│ ├── ets/
│ │ ├── entryability/
│ │ │ └── EntryAbility.ets # 主入口 Ability
│ │ ├── formability/
│ │ │ └── WeatherFormAbility.ets # 卡片 FormProvider
│ │ ├── pages/
│ │ │ └── Index.ets # 主页面
│ │ ├── components/
│ │ │ ├── WeatherCard.ets # 天气卡片 UI 组件
│ │ │ └── ForecastList.ets # 未来预报列表
│ │ ├── model/
│ │ │ ├── WeatherData.ets # 天气数据模型
│ │ │ └── WeatherService.ets # 网络请求服务
│ │ └── distributed/
│ │ └── WeatherSync.ets # 分布式数据同步
│ └── resources/
│ ├── base/layout/weather_form.json # 卡片布局描述
│ └── base/media/ # 天气图标资源
└── entry/src/main/module.json5 # 模块配置(含意图绑定)
图1:WeatherAtom 项目结构图
图片内容说明(中文):树状目录图,根节点"WeatherAtom"下展开 entry/src/main/ets/ 目录,各子目录用不同颜色标注:entryability(蓝色,主入口)、formability(绿色,卡片)、pages(紫色,页面)、components(橙色,组件)、model(灰色,数据)、distributed(青色,同步)。右侧图例标注各目录职责。关键文件用加粗显示。
二、元服务配置:module.json5 与意图绑定
元服务的核心配置在 module.json5 中。7.0 新增了意图框架绑定和数据分级声明。
// entry/src/main/module.json5
{
"module": {
"name": "entry",
"type": "atomicService",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"abilities": [
{
"name": "EntryAbility",
"srcEntry": "./ets/entryability/EntryAbility.ets",
"description": "$string:EntryAbility_desc",
"icon": "$media:layered_image",
"label": "$string:EntryAbility_label",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
// 7.0 新增:意图绑定,让系统能根据天气相关意图拉起服务
"skills": [
{
"actions": ["query.weather", "check.forecast"],
"entities": ["location.current", "time.now"]
}
]
},
{
"name": "WeatherFormAbility",
"srcEntry": "./ets/formability/WeatherFormAbility.ets",
"description": "天气服务卡片",
"label": "$string:WeatherFormAbility_label",
"forms": [
{
"name": "WeatherCard",
"displayName": "今日天气",
"description": "实时天气与温度",
"src": "./ets/formability/WeatherFormAbility.ets",
"window": {
"designWidth": 360,
"autoDesignWidth": true
},
"colorMode": "auto",
"isDefault": true,
"updateEnabled": true,
"scheduledUpdateTime": "10:00",
"updateDuration": 1,
"defaultDimension": "2*2",
"supportDimensions": ["2*2", "4*2"]
}
]
}
],
// 7.0 新增:数据分级
"dataClassification": {
"public": ["weather_cache"],
"internal": ["user_location"],
"sensitive": []
}
}
}
三、天气服务卡片设计
3.1 卡片数据模型
// model/WeatherData.ets
export class WeatherData {
city: string = '';
temp: number = 0; // 当前温度
condition: string = ''; // 天气状况:晴/雨/多云
icon: Resource = $r('app.media.sunny');
humidity: number = 0; // 湿度
windLevel: number = 0; // 风力
updateTime: string = '';
constructor(city: string, temp: number, condition: string) {
this.city = city;
this.temp = temp;
this.condition = condition;
this.updateTime = new Date().toLocaleTimeString();
}
}
export class ForecastItem {
day: string = '';
high: number = 0;
low: number = 0;
condition: string = '';
}
3.2 FormProvider 实现
// formability/WeatherFormAbility.ets
import { formProvider } from '@ohos.app.form.formProvider';
import { WeatherService } from '../model/WeatherService';
import { WeatherData } from '../model/WeatherData';
class WeatherFormAbility {
private service: WeatherService = new WeatherService();
// 卡片创建时触发
onAddForm(want: Want): formBindingData.FormBindingData {
const city = want.parameters?.['city'] as string || '北京';
return this.createFormData(city);
}
// 定时更新触发
onUpdateForm(formId: string): void {
const city = this.getCityByFormId(formId);
const weather = this.service.fetchWeather(city);
const formData = this.buildFormData(weather);
formProvider.updateForm(formId, formData);
}
private createFormData(city: string): formBindingData.FormBindingData {
const weather = this.service.fetchWeather(city);
return formBindingData.createFormBindingData({
city: weather.city,
temp: `${weather.temp}°`,
condition: weather.condition,
icon: weather.icon.id,
humidity: `${weather.humidity}%`,
updateTime: weather.updateTime
});
}
private buildFormData(weather: WeatherData): Object {
return {
city: weather.city,
temp: `${weather.temp}°`,
condition: weather.condition,
icon: weather.icon.id,
humidity: `${weather.humidity}%`,
updateTime: weather.updateTime
};
}
}
export default new WeatherFormAbility();
3.3 卡片布局(weather_form.json)
{
"layout": {
"width": "100%",
"height": "100%",
"type": "column",
"padding": "12vp",
"children": [
{
"type": "row",
"justifyContent": "space-between",
"children": [
{
"type": "text",
"text": "{{city}}",
"fontSize": "16fp",
"fontColor": "#FFFFFF"
},
{
"type": "text",
"text": "{{updateTime}}",
"fontSize": "10fp",
"fontColor": "#CCFFFFFF"
}
]
},
{
"type": "row",
"marginTop": "8vp",
"alignItems": "center",
"children": [
{
"type": "image",
"src": "{{icon}}",
"width": "40vp",
"height": "40vp"
},
{
"type": "text",
"text": "{{temp}}",
"fontSize": "36fp",
"fontColor": "#FFFFFF",
"marginLeft": "12vp"
}
]
},
{
"type": "text",
"text": "{{condition}} · 湿度 {{humidity}}",
"fontSize": "12fp",
"fontColor": "#DDFFFFFF",
"marginTop": "4vp"
}
],
"background": {
"type": "linearGradient",
"colors": ["#FF6B6B", "#4ECDC4"],
"direction": "toBottomRight"
}
}
}
四、主页面与天气数据层
4.1 网络请求服务
// model/WeatherService.ets
import { http } from '@ohos.net.http';
import { WeatherData, ForecastItem } from './WeatherData';
export class WeatherService {
private baseUrl: string = 'https://api.weather.example.com/v1';
async fetchWeather(city: string): Promise<WeatherData> {
const httpRequest = http.createHttp();
try {
const response = await httpRequest.request(
`${this.baseUrl}/current?city=${encodeURIComponent(city)}`,
{ method: http.RequestMethod.GET, header: { 'Accept': 'application/json' } }
);
// 7.0:response.body 为 ArrayBuffer,需解码
const decoder = new util.TextDecoder();
const json = JSON.parse(decoder.decodeToString(response.body));
return new WeatherData(json.city, json.temp, json.condition);
} catch (e) {
console.error('获取天气失败:', e);
// 降级:返回缓存数据或默认数据
return new WeatherData(city, 25, '晴');
} finally {
httpRequest.destroy();
}
}
async fetchForecast(city: string): Promise<ForecastItem[]> {
// 获取未来 7 天预报
const httpRequest = http.createHttp();
const response = await httpRequest.request(
`${this.baseUrl}/forecast?city=${encodeURIComponent(city)}&days=7`
);
const decoder = new util.TextDecoder();
const json = JSON.parse(decoder.decodeToString(response.body));
return json.list.map((item: any) => ({
day: item.day,
high: item.high,
low: item.low,
condition: item.condition
} as ForecastItem));
}
}
4.2 主页面 Index.ets
// pages/Index.ets
import { WeatherService } from '../model/WeatherService';
import { WeatherData, ForecastItem } from '../model/WeatherData';
import { WeatherSync } from '../distributed/WeatherSync';
@Entry
@Component
struct WeatherIndex {
@State weather: WeatherData = new WeatherData('北京', 25, '晴');
@State forecast: ForecastItem[] = [];
@State isLoading: boolean = false;
@State currentCity: string = '北京';
private service: WeatherService = new WeatherService();
private sync: WeatherSync = new WeatherSync();
aboutToAppear() {
this.loadWeather(this.currentCity);
// 初始化分布式同步
this.sync.init('weather_session_001', (data) => {
this.weather = data;
});
}
async loadWeather(city: string) {
this.isLoading = true;
this.weather = await this.service.fetchWeather(city);
this.forecast = await this.service.fetchForecast(city);
this.isLoading = false;
// 同步到分布式数据对象(供其他设备读取)
this.sync.updateWeather(this.weather);
}
build() {
Column() {
// 城市选择器
Row() {
Text(this.currentCity)
.fontSize(24)
.fontWeight(FontWeight.Bold)
Button('切换城市')
.onClick(() => {
// 简化示例:固定切换几个城市
const cities = ['北京', '上海', '广州', '深圳'];
const idx = (cities.indexOf(this.currentCity) + 1) % cities.length;
this.currentCity = cities[idx];
this.loadWeather(this.currentCity);
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(16)
// 当前天气
WeatherCard({ weather: this.weather })
// 跨设备流转按钮
Button('流转到平板/手表')
.margin({ top: 16 })
.onClick(() => this.transferToNearbyDevice())
// 预报列表
ForecastList({ forecast: this.forecast })
if (this.isLoading) {
LoadingProgress()
.width(40)
.height(40)
.margin({ top: 20 })
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
async transferToNearbyDevice() {
// 跨设备流转逻辑
const continuationManager = distributedMissionManager;
// 7.0:通过意图框架发现附近设备
const devices = await continuationManager.getContinuationDevices();
if (devices.length > 0) {
await continuationManager.continueMission({
srcDeviceId: '',
dstDeviceId: devices[0].deviceId,
missionId: 0
});
}
}
}
五、跨设备流转实现
5.1 分布式任务管理
// distributed/WeatherSync.ets
import { distributed } from '@ohos.data.distributed';
import { WeatherData } from '../model/WeatherData';
export class WeatherSync {
private g_object: distributed.DistributedObject | null = null;
private sessionId: string = '';
private callback: ((data: WeatherData) => void) | null = null;
async init(sessionId: string, onChange: (data: WeatherData) => void): Promise<void> {
this.sessionId = sessionId;
this.callback = onChange;
// 7.0:创建分布式数据对象,显式声明 QoS
this.g_object = distributed.createObject(sessionId, {
city: '',
temp: 0,
condition: '',
updateTime: ''
}, {
qos: distributed.QoS.HIGH_RELIABILITY,
syncMode: distributed.SyncMode.REALTIME
});
// 监听远程变更
this.g_object.on('change', (sessionId: string, fields: Array<string>) => {
if (this.callback && this.g_object) {
this.callback(new WeatherData(
this.g_object.city,
this.g_object.temp,
this.g_object.condition
));
}
});
}
updateWeather(weather: WeatherData): void {
if (this.g_object) {
// 7.0:批量原子更新
this.g_object.batchUpdate({
city: weather.city,
temp: weather.temp,
condition: weather.condition,
updateTime: weather.updateTime
});
}
}
destroy(): void {
if (this.g_object) {
this.g_object.off('change');
this.g_object = null;
}
}
}
5.2 流转时的状态保存与恢复
在 EntryAbility 中处理流转生命周期:
// entryability/EntryAbility.ets
import { UIAbility, Want } from '@ohos.app.ability.UIAbility';
import { window } from '@ohos.window';
export default class EntryAbility extends UIAbility {
private weatherSync: WeatherSync = new WeatherSync();
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 判断是否为接续启动
if (launchParam.launchReason === AbilityConstant.LaunchReason.CONTINUATION) {
// 从 want 参数中恢复状态
const city = want.parameters?.['city'] as string || '北京';
AppStorage.setOrCreate('continuedCity', city);
}
}
onContinue(wantParam: Record<string, Object>): AbilityConstant.OnContinueResult {
// 保存当前状态,供目标设备恢复
wantParam['city'] = AppStorage.get('currentCity') || '北京';
wantParam['temp'] = AppStorage.get('currentTemp') || 25;
return AbilityConstant.OnContinueResult.AGREE;
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
return;
}
});
}
}
六、运行效果与测试要点
6.1 本地运行效果
- 桌面卡片:长按桌面空白处 → 添加服务卡片 → 选择"WeatherAtom → 今日天气",卡片显示当前城市、温度、天气图标和湿度,背景为渐变色彩;
- 主页面:点击卡片进入全功能页面,展示当前天气 + 7 日预报 + 城市切换按钮;
- 实时更新:卡片每 30 分钟自动刷新(受 7.0 限流策略约束)。
6.2 跨设备流转测试
- 确保手机和平板登录同一华为账号,开启蓝牙和 WiFi;
- 在手机端打开天气元服务,查看北京天气;
- 点击"流转到平板/手表",在超级终端中选择平板;
- 平板上自动打开天气服务,显示与手机一致的城市和温度数据;
- 在手机端切换城市到上海,观察平板端是否自动同步更新。
图2:WeatherAtom 跨设备天气服务运行架构图
图片内容说明(中文):三个设备(手机、平板、手表)呈三角形排列。手机内标注"主控端:查询天气→本地显示→分布式同步",平板内标注"协同端:接收流转→恢复状态→显示详情",手表内标注"轻量端:仅显示温度+天气图标"。三个设备之间有双向箭头,标注"分布式数据对象实时同步"。底部说明:同一sessionId,数据变更自动推送到所有组网设备。
6.3 关键测试 checklist
| 测试项 | 通过标准 |
|---|---|
| 卡片添加 | 桌面可正常添加 2×2 和 4×2 两种尺寸卡片 |
| 数据刷新 | 卡片定时更新,主页面下拉刷新成功 |
| 城市切换 | 切换城市后天气数据正确更新 |
| 跨设备流转 | 任务可从手机流转到平板,状态不丢失 |
| 分布式同步 | 一端切换城市,另一端 1 秒内同步 |
| 弱网降级 | 网络断开时显示缓存数据,不崩溃 |
| 后台更新 | 卡片在后台仍可定时刷新(受 7.0 限流约束) |
七、结语
通过这个 WeatherAtom 项目,我们完整走通了 HarmonyOS 7.0 元服务的核心开发链路:从工程配置到意图绑定,从卡片设计到主页面开发,从网络请求到跨设备流转,从分布式数据同步到状态保存恢复。
元服务的开发哲学与传统应用不同——它追求"最小可用、最快触达、最轻交互"。在 7.0 的意图框架加持下,未来的天气元服务甚至不需要用户主动打开:当用户问小艺"今天会下雨吗",系统可以直接从桌面弹出天气卡片作答。
对于高校学生开发者,建议将本文项目作为元服务开发的"Hello World"——在理解完整链路后,可以尝试替换天气数据源、增加空气质量指数、接入意图框架的更多触发条件,逐步构建出属于自己的鸿蒙元服务作品。
转载自:https://blog.csdn.net/u014727709/article/details/162933111
欢迎 👍点赞✍评论⭐收藏,欢迎指正
更多推荐



所有评论(0)