
1. 项目背景与核心需求天气预报类应用一直是移动端开发的经典练手项目而如何高效获取并展示天气数据则是这类应用的核心技术难点。最近在开发鸿蒙版天气预报应用时我选择了和风天气API作为数据源并采用Flutter的retrofit库来处理网络请求最终实现了城市天气信息卡片的功能模块。这个方案最大的优势在于和风天气API提供稳定可靠的全球天气数据Flutter的跨平台特性让代码可以复用到鸿蒙平台retrofit库能极大简化网络请求的编码工作卡片式UI符合现代移动应用的交互习惯2. 技术选型与架构设计2.1 和风天气API接入和风天气(HeWeather)是国内领先的气象数据服务商其API提供实时天气数据温度、湿度、风力等逐小时预报7天天气预报空气质量指数生活指数建议API采用RESTful设计返回JSON格式数据免费版完全能满足个人开发需求。注册开发者账号后可以获得一个API Key用于身份验证。2.2 Flutter网络请求方案对比在Flutter中处理网络请求主要有以下几种方式http/dio基础网络请求库需要手动处理请求构建和响应解析Chopper类似retrofit的库但社区活跃度较低retrofit基于dio的封装通过注解自动生成API客户端代码最终选择retrofit的原因是代码简洁通过注解自动生成网络请求代码内置JSON序列化支持与dio深度集成可以复用dio的拦截器等功能社区活跃文档完善2.3 鸿蒙平台适配考虑虽然Flutter官方尚未正式支持鸿蒙OS但通过OpenHarmony的Flutter引擎支持Flutter应用已经可以在鸿蒙设备上运行。在这个项目中我们主要关注UI组件在鸿蒙设备上的显示效果网络请求功能在鸿蒙平台的兼容性性能优化确保在鸿蒙设备上的流畅体验3. 具体实现步骤3.1 项目初始化与依赖配置首先创建一个新的Flutter项目然后在pubspec.yaml中添加所需依赖dependencies: flutter: sdk: flutter retrofit: ^3.2.0 dio: ^4.0.6 json_annotation: ^4.7.0 logger: ^1.1.0 dev_dependencies: build_runner: ^2.1.11 retrofit_generator: ^3.2.0 json_serializable: ^6.3.1运行flutter pub get安装依赖。3.2 定义API接口创建api/he_weather_api.dart文件定义和风天气的API接口import package:retrofit/retrofit.dart; import package:dio/dio.dart; import ../model/weather_response.dart; part he_weather_api.g.dart; RestApi(baseUrl: https://api.heweather.net/v7/) abstract class HeWeatherApi { factory HeWeatherApi(Dio dio, {String baseUrl}) _HeWeatherApi; GET(weather/now) FutureWeatherResponse getWeatherNow( Query(location) String location, Query(key) String key, Query(lang) String lang, Query(unit) String unit, ); }3.3 定义数据模型创建model/weather_response.dart文件定义天气数据模型import package:json_annotation/json_annotation.dart; part weather_response.g.dart; JsonSerializable() class WeatherResponse { final String code; final Now now; final String updateTime; WeatherResponse({ required this.code, required this.now, required this.updateTime, }); factory WeatherResponse.fromJson(MapString, dynamic json) _$WeatherResponseFromJson(json); MapString, dynamic toJson() _$WeatherResponseToJson(this); } JsonSerializable() class Now { final String obsTime; final String temp; final String feelsLike; final String icon; final String text; final String windDir; final String windScale; final String humidity; Now({ required this.obsTime, required this.temp, required this.feelsLike, required this.icon, required this.text, required this.windDir, required this.windScale, required this.humidity, }); factory Now.fromJson(MapString, dynamic json) _$NowFromJson(json); MapString, dynamic toJson() _$NowToJson(this); }3.4 生成代码运行以下命令生成retrofit和json序列化代码flutter pub run build_runner build这会生成he_weather_api.g.dart和weather_response.g.dart文件。3.5 实现天气卡片UI创建widgets/weather_card.dart文件实现天气卡片import package:flutter/material.dart; import ../model/weather_response.dart; class WeatherCard extends StatelessWidget { final WeatherResponse weather; final String cityName; const WeatherCard({ super.key, required this.weather, required this.cityName, }); override Widget build(BuildContext context) { return Card( elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( cityName, style: Theme.of(context).textTheme.headlineSmall, ), const SizedBox(height: 8), Row( children: [ Image.network( https://a.hecdn.net/img/plugin/190516/icon/c/${weather.now.icon}.png, width: 48, height: 48, ), const SizedBox(width: 16), Text( ${weather.now.temp}°, style: Theme.of(context).textTheme.displaySmall, ), ], ), const SizedBox(height: 8), Text(weather.now.text), const SizedBox(height: 8), Row( children: [ const Icon(Icons.air, size: 16), const SizedBox(width: 4), Text(${weather.now.windDir} ${weather.now.windScale}级), const SizedBox(width: 16), const Icon(Icons.water_drop, size: 16), const SizedBox(width: 4), Text(湿度 ${weather.now.humidity}%), ], ), const SizedBox(height: 8), Text( 更新时间: ${weather.updateTime}, style: Theme.of(context).textTheme.bodySmall, ), ], ), ), ); } }3.6 整合网络请求与UI在主页中整合网络请求和UI展示import package:flutter/material.dart; import package:dio/dio.dart; import package:logger/logger.dart; import api/he_weather_api.dart; import model/weather_response.dart; import widgets/weather_card.dart; class WeatherPage extends StatefulWidget { const WeatherPage({super.key}); override StateWeatherPage createState() _WeatherPageState(); } class _WeatherPageState extends StateWeatherPage { final logger Logger(); final dio Dio(); late final HeWeatherApi api; WeatherResponse? weather; String city 北京; bool isLoading false; String errorMessage ; override void initState() { super.initState(); api HeWeatherApi(dio); fetchWeather(); } Futurevoid fetchWeather() async { setState(() { isLoading true; errorMessage ; }); try { final response await api.getWeatherNow( city, YOUR_HEWEATHER_KEY, // 替换为你的和风天气API Key zh, m, ); setState(() { weather response; isLoading false; }); } catch (e) { logger.e(获取天气数据失败, error: e); setState(() { isLoading false; errorMessage 获取天气数据失败请稍后重试; }); } } override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text(城市天气预报), ), body: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ if (isLoading) const Center(child: CircularProgressIndicator()) else if (errorMessage.isNotEmpty) Text(errorMessage, style: TextStyle(color: Colors.red)) else if (weather ! null) WeatherCard(weather: weather!, cityName: city) else const Text(暂无天气数据), const SizedBox(height: 16), ElevatedButton( onPressed: fetchWeather, child: const Text(刷新数据), ), ], ), ), ); } }4. 关键问题与优化方案4.1 网络请求优化添加拦截器为Dio添加日志拦截器和错误处理拦截器dio.interceptors.add(LogInterceptor( request: true, requestHeader: true, requestBody: true, responseHeader: true, responseBody: true, error: true, )); dio.interceptors.add(InterceptorsWrapper( onError: (error, handler) { logger.e(API请求错误, error: error); return handler.next(error); }, ));超时设置配置合理的超时时间dio.options.connectTimeout const Duration(seconds: 10); dio.options.receiveTimeout const Duration(seconds: 10);4.2 数据缓存策略为了避免频繁请求API可以添加简单的内存缓存class WeatherRepository { final HeWeatherApi api; WeatherResponse? _cache; DateTime? _lastFetchTime; WeatherRepository(this.api); FutureWeatherResponse getWeather(String city) async { if (_cache ! null _lastFetchTime ! null DateTime.now().difference(_lastFetchTime!) const Duration(minutes: 10)) { return _cache!; } final response await api.getWeatherNow(city, YOUR_KEY, zh, m); _cache response; _lastFetchTime DateTime.now(); return response; } }4.3 鸿蒙平台适配问题在鸿蒙平台上运行时需要注意网络权限确保在鸿蒙的config.json中添加网络权限UI适配测试卡片在不同鸿蒙设备上的显示效果性能监控关注应用在鸿蒙平台上的内存占用和流畅度5. 扩展功能与未来优化5.1 多城市管理可以扩展应用支持多个城市的天气查询添加城市搜索功能实现城市列表管理支持城市天气卡片滑动浏览5.2 天气预警通知集成和风天气的预警API在恶劣天气时发送通知实现后台定时任务处理天气预警数据显示预警通知5.3 更丰富的天气图表使用charts_flutter等库展示更详细的天气趋势24小时温度变化曲线7天气温趋势图空气质量变化图表6. 项目总结与经验分享在实际开发过程中有几个关键点值得注意API Key管理不要将API Key硬编码在代码中应该通过环境变量或配置文件管理错误处理和风天气API返回的错误码需要特别处理如200表示成功404表示城市不存在国际化如果应用需要支持多语言可以利用和风天气的多语言参数lang单元测试为API请求和业务逻辑编写单元测试确保核心功能的稳定性retrofit库的使用大大简化了网络请求的代码量通过注解自动生成客户端代码让开发者可以更专注于业务逻辑的实现。同时Flutter的跨平台特性也让这个应用可以相对容易地适配到鸿蒙平台。