这个天气预报 App,涵盖了很多初学者必踩的知识点:权限申请、定位服务、网络请求、数据持久化、列表渲染、下拉刷新、动画等。这篇文章把整个项目从架构到核心代码完整梳理一遍,适合刚入门 ArkTS / ArkUI 的同学参考。


一、项目介绍


功能一览:
- 自动定位当前城市(GPS 定位优先,失败后降级 IP 定位,再失败兜底默认城市)
- 实时天气展示:温度、天气现象、体感温度、风向风力
- 24 小时逐时预报(横向滑动)
- 7 天天气预报
- 天气详情:湿度、气压、能见度、降水量、风速、云量
- 城市搜索(支持关键字搜索 + 热门城市 + 全量城市按字母分组)
- 城市管理:添加/删除常用城市,并显示每个城市的实时温度
- 下拉刷新 + 内容渐入动画


技术栈:

开发语言  :ArkTS(Stage 模型)
UI 框架    :ArkUI 声明式开发(@Entry / @Component / @State / @Builder)
天气数据    :和风天气 API v7(now / 24h / 7d)
定位    :@kit.LocationKit + 高德逆地理编码 / IP 定位
网络请求   : @kit.NetworkKit(http)
数据持久化   : @kit.ArkData(Preferences)
日志  :  hilog


二、项目结构

entry/src/main/ets
├── constants/Constants.ets      // API Key、URL 常量
├── utils/HttpUtil.ets           // 网络请求封装
├── model/                       // 数据模型(和风/高德响应实体)
│   ├── QWeatherNow.ets / QWeatherHourly.ets / QWeatherDaily.ets
│   ├── AmapIpLocation.ets / AmapWeather.ets
│   ├── CityData.ets / ManagedCity.ets
├── service/
│   ├── LocationService.ets      // 定位 + 城市搜索(GPS/IP/逆地理)
│   ├── WeatherService.ets       // 和风天气接口封装
│   └── CityStore.ets            // 城市管理持久化
├── pages/Index.ets              // 主页面(单页 + 覆盖层)
└── entryability/EntryAbility.ets


分层思路很清晰:model(实体)→ service(业务)→ pages(UI),ArkTS 强类型约束下每层之间都通过显式类传递数据。


三、权限配置


定位功能需要在 module.json5 中声明三个权限:

"requestPermissions": [
  { "name": "ohos.permission.INTERNET" },
  { "name": "ohos.permission.APPROXIMATELY_LOCATION" },
  { "name": "ohos.permission.LOCATION" }
]


注意:定位权限属于用户授权类权限,光声明不够,代码里还要动态申请:

const atManager = abilityAccessCtrl.createAtManager();
const result = await atManager.requestPermissionsFromUser(context, [
  'ohos.permission.APPROXIMATELY_LOCATION',
  'ohos.permission.LOCATION'
]);


四、核心实现


1. 网络请求封装(HttpUtil)
基于 @kit.NetworkKit 封装了一个泛型 GET 方法,统一处理超时、状态码校验、JSON 解析和资源释放:

export class HttpUtil {
  static async get<T>(url: string): Promise<T | undefined> {
    const httpRequest = http.createHttp();
    try {
      const response = await httpRequest.request(url, {
        method: http.RequestMethod.GET,
        expectDataType: http.HttpDataType.STRING,
        connectTimeout: 15000,
        readTimeout: 15000
      });
      if (response.responseCode === 200) {
        return JSON.parse(response.result as string) as T;
      }
      return undefined;
    } catch (err) {
      const error = err as BusinessError;
      hilog.error(DOMAIN, TAG, `HTTP request failed: ${error.message}`);
      return undefined;
    } finally {
      httpRequest.destroy();  // 记得销毁,避免资源泄漏
    }
  }
}


踩坑点: JSON.parse 在 ArkTS 里返回的结果需要配合显式类型标注,响应实体类必须和接口字段完全对齐,否则运行时取值就是 undefined。


2. 三级降级的定位策略
这是本项目最有意思的设计。LocationService.getCityLocation() 实现了完整的降级链:


GPS 定位(geoLocationManager.getCurrentLocation)
   ↓ 失败
高德 IP 定位(restapi.amap.com/v3/ip,取城市矩形区域中心点)
   ↓ 失败
兜底默认城市(北京)
GPS 定位拿到经纬度后,还要调高德逆地理编码把坐标翻译成城市/区县名。IP 定位则巧妙地利用高德返回的 rectangle(城市西南、东北角坐标),取两个点的中点作为坐标:
const lng = ((parseFloat(leftBottom[0]) + parseFloat(rightTop[0])) / 2).toFixed(2);
const lat = ((parseFloat(leftBottom[1]) + parseFloat(rightTop[1])) / 2).toFixed(2);


踩坑点:
- 真机上 getCurrentLocation 要先检查 isLocationEnabled(),位置服务没开会直接抛异常;
- 模拟器上 GPS 基本拿不到,所以 IP 降级这条路径非常必要,调试时强烈建议都做。


3. 和风天气 API 对接
WeatherService 封装了三个接口:/weather/now(实时)、/weather/24h(逐时)、/weather/7d(七天)。注意和风 v7 API 响应里 code === '200' 才是成功。还有一点:和风现在很多账号的 API Host 不再是 devapi.qweather.com,而是控制台里分配的专属域名,配置错会一直 404。


4. 城市搜索与管理
- 搜索用高德的 inputtips 联想接口,返回的 location 拆分出经纬度直接作为和风的 location 参数(和风支持 经度,纬度 格式查询,可以不查 LocationID,省一步接口调用);
- 城市列表页做了热门城市 + 全量城市按首字母分组(ListItemGroup + @Builder LetterHeader);
- 城市管理用 Preferences 持久化,ManagedCity 序列化成 JSON 字符串存储,读取时逐项校验字段合法性,防止脏数据崩溃。


5. UI 层的一些技巧
主页面采用单页 + Stack 覆盖层架构,搜索页和城市管理页不是独立路由,而是覆盖在主页面之上:

Stack() {
  Column() {
    if (this.isLoading) { this.LoadingView(); }
    else if (this.nowResponse === undefined) { this.ErrorView(); }
    else { this.WeatherContent(); }
  }
  if (this.showSearch) { this.SearchOverlay(); }
  if (this.showCityManage) { this.CityManageOverlay(); }
}

几个值得说的点:


1. 加载/错误/内容三态切换:用 if-else 条件渲染,比路由跳转体验好;
2. 内容渐入动画:数据加载完后先置 opacity: 0,setTimeout 50ms 后置 1,配合 translate 和 .animation() 实现上滑渐入,几个卡片用 delay: 100/200/300 做出阶梯式入场;
3. 下拉刷新:Refresh({ refreshing: $$this.isRefreshing }) 双向绑定刷新状态;
4. 城市列表局部更新:cityWeatherInfos 逐个请求每个城市的实时天气,每拿到一个结果就用 slice() 生成新数组替换,保证 @State 能感知变化,逐行从“加载中...”刷成真实温度;
5. 天气图标:没有引图片资源,直接用和风 icon 字段映射成 Emoji,省资源又好看。


五、ArkTS 语法上的注意点


写这个项目总结几条 ArkTS 和普通 TypeScript 的差异,都是编译期强约束:


1. 不能用对象字面量当类型,必须先 class 定义实体类(如 CityLocation、SearchResult)
2. 禁止 any / 结构化类型,接口响应解析必须 as 到显式类
3. ForEach 的键值生成函数要保证唯一,列表带 index 时用 index ?? 0 处理可选参数
4. 异步方法返回 Promise<T | undefined>,调用侧全部要判空,链路上一环判漏运行时就可能白屏。


六、总结与展望


这个项目虽然不大,但完整走通了 HarmonyOS 应用开发的典型链路:声明式 UI + 权限体系 + 系统能力(定位/网络/存储)+ 三方 API 集成。整体下来对 Stage 模型、ArkTS 类型约束和声明式范式有了体感。
后续可以优化的方向:
- 接入和风分钟级降雨和空气质量接口
- 用 Navigation 重构为多页面路由,支持深链
- 加上深色模式适配和 @ohos.permission.NOTIFICATION 的天气预警推送
- 把 API Key 迁移到服务端代理,避免硬编码在客户端

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐