【星光不负 码向未来】3 周打造 “极简天气” 的踩坑与成长

30 阅读4分钟

前言

作为一名有三年开发经验的程序员,我对 “元服务” 的最初认知停留在 “轻量级应用” 的概念上。2024 年 HarmonyOS 创新赛启动时,我决定挑战自己 —— 从零开始开发一个 极简天气元服务,不求功能复杂,只求把鸿蒙元服务的核心特性 “吃透”。这三周的开发历程,从对着文档发懵到最终完成作品提交,每个环节都藏着对鸿蒙生态的新理解。

一、选题:为什么是 “极简天气”?

选择天气场景,是因为它天然契合元服务的 “轻量化、即时性” 特点:用户不需要打开 App,从服务中心的卡片就能快速查看气温、天气状况,符合 “服务直达” 的鸿蒙理念。同时,天气数据的获取、展示逻辑相对简单,适合作为元服务开发的入门实践。

我给这个元服务定了三个核心目标:

  • 桌面卡片实时显示当前城市气温、天气;
  • 支持点击卡片快速切换城市;
  • 数据本地缓存,无网络时也能查看历史天气。

二、技术攻坚:和元服务 “死磕” 的三个关键节点

1. 卡片适配:从 “模拟器好看” 到 “多设备能用”

元服务的核心载体是桌面卡片,但我最初的实现完全忽略了 “多设备适配”。写了个固定尺寸的卡片,在手机模拟器上看着挺精致,装到导师的华为平板上却发现:文字被挤成一团,天气图标显示不全。

// 最初的错误尝试:固定像素布局
@Entry
@Component
struct WeatherCard {
  build() {
    Column() {
      Text("北京 25℃ 晴")
        .fontSize(16)
      Image("sunny.png")
        .width(40)
        .height(40)
    }
    .width(200) // 固定宽度,平板上直接“崩了”
    .height(100)
  }
}

翻阅 HarmonyOS NEXT 官方文档的 “元服务适配指南” 后,我改用弹性布局(Flex)+ 动态资源,去掉所有固定尺寸,让卡片 “自适应” 不同设备的桌面空间:

// 优化后:弹性布局+动态资源,多设备友好
@Entry
@Component
struct WeatherCard {
  @State city: string = "北京"
  @State temp: number = 25
  @State condition: string = "晴"

  build() {
    Column({ space: 5 }) {
      Text(this.city)
        .fontSize($r('app.float.card_title_size'))
        .fontWeight(FontWeight.Bold)
      Row({ space: 8 }) {
        Text(`${this.temp}℃`)
          .fontSize($r('app.float.card_content_size'))
        Image(`${this.condition}.png`)
          .width('20vp')
          .height('20vp')
      }
    }
    .width('100%') // 占满卡片可分配宽度
    .height('100%')
    .padding($r('app.float.card_padding'))
  }
}

同时在 resources/base/element/resource.json 中配置动态资源:

{
  "float": {
    "card_title_size": { "value": "14vp" },
    "card_content_size": { "value": "12vp" },
    "card_padding": { "value": "8vp" }
  }
}

这个调整花了我整整两天。期间我还加入了华为开发者论坛的 “元服务交流群”,请教群友后才明白:vp 单位是鸿蒙适配的关键,它能自动根据设备屏幕密度调整尺寸。优化后,卡片在手机、平板上终于 “各得其所”—— 手机端紧凑显示核心数据,平板端扩展展示更多信息(如空气质量)。

2. 数据管理:从 “网络依赖” 到 “离线可用”

天气数据需要实时获取,但完全依赖网络会导致 “无网时卡片空白”。我决定用 Preferences 本地缓存,实现 “有网更新、无网读缓存” 的策略。

最初的代码只处理了网络请求,没有缓存逻辑:

// 最初的错误:无缓存,无网时数据丢失
async getWeather(city: string) {
  const response = await fetch(`https://api.weather.com/${city}`);
  const data = await response.json();
  this.temp = data.temp;
  this.condition = data.condition;
}

优化后,我加入了缓存读取和写入逻辑:

// 优化后:网络+缓存双保险
import preferences from '@ohos.data.preferences';

let prefs: preferences.Preferences | null = null;

@Entry
@Component
struct WeatherCard {
  @State city: string = "北京"
  @State temp: number = 0
  @State condition: string = "未知"

  async aboutToAppear() {
    // 初始化Preferences
    prefs = await preferences.getPreferences(this.context, 'weatherCache');
    // 先读缓存
    const cached = await prefs.get(`${this.city}_weather`, '{}');
    const cacheData = JSON.parse(cached);
    if (cacheData.temp) {
      this.temp = cacheData.temp;
      this.condition = cacheData.condition;
    }
    // 再请求网络更新
    this.getWeather();
  }

  async getWeather() {
    try {
      const response = await fetch(`https://api.weather.com/${this.city}`);
      const data = await response.json();
      this.temp = data.temp;
      this.condition = data.condition;
      // 写入缓存
      await prefs.put(`${this.city}_weather`, JSON.stringify({
        temp: data.temp,
        condition: data.condition
      }));
      await prefs.flush();
    } catch (err) {
      console.error('网络请求失败,使用缓存数据', err);
    }
  }

  // 点击切换城市的逻辑...
}

这个改动让元服务在无网络时也能显示历史天气,大大提升了实用性。测试时我特意断网打开卡片,看到缓存的天气数据正常显示时,才真正体会到鸿蒙 “本地优先” 的设计理念。

3. 用户交互:从 “单一展示” 到 “可操作”

元服务不能只是 “静态卡片”,需要支持基础交互。我给卡片添加了 “点击切换城市” 的功能,但最初的实现存在 “点击无反馈” 的问题。

// 优化前:点击无反馈,用户不知道操作是否生效
Text(this.city)
  .onClick(() => {
    this.city = this.city === "北京" ? "上海" : "北京";
    this.getWeather();
  })

优化后,我加入了微动效和加载状态,让用户明确感知操作过程:

// 优化后:点击有反馈,体验更友好
@State isLoading: boolean = false;

Text(this.city)
  .fontSize($r('app.float.card_title_size'))
  .fontWeight(FontWeight.Bold)
  .onClick(async () => {
    this.isLoading = true; // 显示加载状态
    this.city = this.city === "北京" ? "上海" : "北京";
    await this.getWeather();
    this.isLoading = false; // 加载完成
  })
  .loading(this.isLoading) // 鸿蒙自带的加载动效

同时,我还在服务中心配置了 “快捷切换城市” 的入口,用户长按卡片就能选择常用城市,进一步强化了 “服务直达” 的体验。

三、开发感悟:元服务的 “轻” 与 “重”

回顾这三周的开发,我对鸿蒙元服务的理解从 “技术概念” 变成了 “实战认知”:

  • “轻” 在形态,“重” 在体验:元服务的代码量可以很少,但对 “多设备适配”“用户交互细节” 的要求一点都不低。一个卡片的适配问题,可能需要反复调试才能解决。
  • 文档是最好的老师:鸿蒙官方文档的 “元服务开发指南” 和 “API 参考” 是我解决问题的核心依据。遇到卡点时,先查文档,再去论坛提问,效率远高于自己瞎琢磨。
  • 小场景也能体现价值:天气元服务功能简单,但它完美契合了 “快速查看、即时操作” 的元服务定位。这让我明白,开发元服务不必追求 “大而全”,把一个小场景做深做透,就是成功。

结语

虽然最终的 “极简天气” 元服务没有在赛事中获得亮眼成绩,但这段开发经历让我真正 “入门” 了鸿蒙生态。它不再是我眼中 “遥远的新技术”,而是一套 “能落地、能解决实际问题” 的开发工具。如果再开发元服务,我会更早关注 “用户操作路径” 和 “多设备一致性体验”,让元服务的 “轻” 发挥出最大价值。对于鸿蒙开发新手,我建议从类似的小场景入手,先把基础特性用扎实,再逐步探索更复杂的能力 —— 毕竟,每个大神都是从 “踩坑” 开始成长的。