保姆级教程:用Android Studio + 百度地图API + 和风天气,手把手教你开发一个天气空气质量App
从零构建天气空气质量App:Android Studio实战指南
在移动应用开发领域,天气类应用始终占据着重要地位。这类应用不仅实用性强,而且涵盖了Android开发的多个核心技术点,是初学者进阶的理想项目。本文将带你完整实现一个整合百度地图定位与和风天气数据的空气质量监测应用,从环境配置到最终发布,每个步骤都配有详细说明和实用技巧。
1. 开发环境与项目初始化
1.1 Android Studio配置优化
在开始项目前,确保你的开发环境已经准备就绪。推荐使用最新稳定版的Android Studio,并安装以下必备组件:
- Android SDK Platform 31+:支持大多数现代Android设备
- Android Emulator:用于快速测试应用
- Kotlin插件:虽然本项目使用Java,但Kotlin插件有助于代码分析
- Git Integration:方便版本控制
提示:在Android Studio的SDK Manager中,勾选"Show Package Details"可以查看更详细的SDK版本信息。
配置gradle.properties文件,添加以下优化参数:
org.gradle.daemon=true org.gradle.parallel=true org.gradle.caching=true android.enableJetifier=true1.2 创建新项目
- 选择"Empty Activity"模板
- 设置项目名称:WeatherAirQualityApp
- 包名:com.yourdomain.weatherair(根据你的域名修改)
- 语言选择Java
- 最低API级别设为21(覆盖约95%的设备)
项目创建完成后,先进行基础配置:
// 在AndroidManifest.xml中添加基础权限 <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />2. 百度地图SDK集成与定位实现
2.1 百度地图开发者账号申请
- 访问百度地图开放平台(https://lbsyun.baidu.com/)
- 注册开发者账号(个人开发者选择个人认证)
- 进入控制台创建新应用
- 获取API Key(注意保存SHA1和包名信息)
2.2 SDK集成步骤
在项目的build.gradle中添加百度地图仓库:
allprojects { repositories { google() jcenter() maven { url 'https://mapapi.bdimg.com/repository/' } } }在app模块的build.gradle中添加依赖:
dependencies { implementation 'com.baidu.lbsyun:BaiduMapSDK_Map:7.4.0' implementation 'com.baidu.lbsyun:BaiduMapSDK_Location:9.1.8' }配置AndroidManifest.xml:
<application> <meta-data android:name="com.baidu.lbsapi.API_KEY" android:value="你的API_KEY" /> </application>2.3 定位功能核心实现
创建LocationService类处理定位逻辑:
public class LocationService { private LocationClient mLocationClient; private BDAbstractLocationListener mListener; public interface LocationCallback { void onReceiveLocation(BDLocation location); void onError(int locType, String message); } public void init(Context context, LocationCallback callback) { mLocationClient = new LocationClient(context.getApplicationContext()); mListener = new BDAbstractLocationListener() { @Override public void onReceiveLocation(BDLocation location) { if (location == null || callback == null) return; if (location.getLocType() == BDLocation.TypeGpsLocation || location.getLocType() == BDLocation.TypeNetWorkLocation) { callback.onReceiveLocation(location); } else { callback.onError(location.getLocType(), location.getLocDescribe()); } } }; LocationClientOption option = new LocationClientOption(); option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy); option.setCoorType("bd09ll"); option.setScanSpan(0); option.setOpenGps(true); option.setNeedDeviceDirect(true); option.setIsNeedAddress(true); mLocationClient.setLocOption(option); mLocationClient.registerLocationListener(mListener); } public void start() { if (mLocationClient != null && !mLocationClient.isStarted()) { mLocationClient.start(); } } public void stop() { if (mLocationClient != null && mLocationClient.isStarted()) { mLocationClient.stop(); } } }3. 和风天气API集成与数据解析
3.1 获取和风天气API Key
- 访问和风天气开发者平台(https://dev.heweather.com/)
- 注册账号并登录
- 进入控制台创建项目
- 获取免费版的Key(每日1000次调用)
3.2 网络请求框架配置
使用Retrofit + Gson进行网络请求和数据解析,在build.gradle中添加:
dependencies { implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.google.code.gson:gson:2.8.8' }创建API服务接口:
public interface HeWeatherService { @GET("weather/now") Call<WeatherResponse> getCurrentWeather( @Query("location") String location, @Query("key") String key ); @GET("air/now") Call<AirQualityResponse> getAirQuality( @Query("location") String location, @Query("key") String key ); }配置Retrofit实例:
public class ApiClient { private static final String BASE_URL = "https://free-api.heweather.net/s6/"; private static Retrofit retrofit; public static Retrofit getClient() { if (retrofit == null) { retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .build(); } return retrofit; } }3.3 数据模型设计
根据和风天气API返回的JSON结构,创建对应的Java类:
public class WeatherResponse { @SerializedName("HeWeather6") private List<Weather> weatherList; // getters and setters } public class Weather { @SerializedName("basic") private BasicInfo basic; @SerializedName("now") private NowInfo now; @SerializedName("update") private UpdateInfo update; // getters and setters } public class NowInfo { @SerializedName("tmp") private String temperature; @SerializedName("cond_txt") private String condition; @SerializedName("wind_dir") private String windDirection; // 其他字段... }4. 应用界面设计与实现
4.1 主界面布局设计
使用ConstraintLayout构建响应式界面:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/iv_background" android:layout_width="0dp" android:layout_height="0dp" android:scaleType="centerCrop" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/tv_city" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="24sp" android:textColor="@android:color/white" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" android:layout_marginTop="16dp" /> <!-- 其他UI元素 --> </androidx.constraintlayout.widget.ConstraintLayout>4.2 天气信息展示组件
创建自定义WeatherView显示天气信息:
public class WeatherView extends LinearLayout { private TextView tvTemperature; private TextView tvCondition; private ImageView ivWeatherIcon; public WeatherView(Context context) { super(context); init(); } private void init() { setOrientation(VERTICAL); LayoutInflater.from(getContext()).inflate(R.layout.view_weather, this, true); tvTemperature = findViewById(R.id.tv_temperature); tvCondition = findViewById(R.id.tv_condition); ivWeatherIcon = findViewById(R.id.iv_weather_icon); } public void setWeatherData(NowInfo nowInfo) { tvTemperature.setText(nowInfo.getTemperature() + "°C"); tvCondition.setText(nowInfo.getCondition()); // 根据天气状况设置图标 int resId = getWeatherIconRes(nowInfo.getCondition()); ivWeatherIcon.setImageResource(resId); } private int getWeatherIconRes(String condition) { switch (condition) { case "晴": return R.drawable.ic_sunny; case "多云": return R.drawable.ic_cloudy; // 其他天气状况... default: return R.drawable.ic_unknown; } } }4.3 空气质量指数可视化
使用MPAndroidChart库展示空气质量数据:
- 添加依赖:
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'- 创建AirQualityChartView:
public class AirQualityChartView extends BarChart { public AirQualityChartView(Context context) { super(context); initChart(); } private void initChart() { getDescription().setEnabled(false); setDrawGridBackground(false); setDrawBarShadow(false); setHighlightFullBarEnabled(false); XAxis xAxis = getXAxis(); xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); xAxis.setDrawGridLines(false); YAxis leftAxis = getAxisLeft(); leftAxis.setAxisMinimum(0f); leftAxis.setEnabled(false); getAxisRight().setEnabled(false); getLegend().setEnabled(false); } public void setData(AirQualityData data) { List<BarEntry> entries = new ArrayList<>(); entries.add(new BarEntry(0, data.getPm25())); entries.add(new BarEntry(1, data.getPm10())); entries.add(new BarEntry(2, data.getSo2())); entries.add(new BarEntry(3, data.getNo2())); entries.add(new BarEntry(4, data.getO3())); entries.add(new BarEntry(5, data.getCo())); BarDataSet dataSet = new BarDataSet(entries, "Air Quality"); dataSet.setColors(getColorsForAQI(data.getAqi())); dataSet.setDrawValues(true); BarData barData = new BarData(dataSet); barData.setBarWidth(0.5f); setData(barData); animateY(1000); invalidate(); } private int[] getColorsForAQI(int aqi) { // 根据AQI值返回不同颜色 if (aqi <= 50) return new int[]{Color.GREEN}; else if (aqi <= 100) return new int[]{Color.YELLOW}; // 其他范围... } }5. 应用优化与发布准备
5.1 权限管理最佳实践
实现动态权限请求:
public class PermissionHelper { private static final int REQUEST_CODE = 1001; private static final String[] PERMISSIONS = { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION }; public static boolean checkPermissions(Activity activity) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { return true; } for (String permission : PERMISSIONS) { if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { return false; } } return true; } public static void requestPermissions(Activity activity) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { activity.requestPermissions(PERMISSIONS, REQUEST_CODE); } } public static boolean handleResult(int requestCode, int[] grantResults) { if (requestCode != REQUEST_CODE) return false; for (int result : grantResults) { if (result != PackageManager.PERMISSION_GRANTED) { return false; } } return true; } }5.2 应用性能优化
内存优化:
- 使用LeakCanary检测内存泄漏
- 优化图片资源大小
- 及时释放不再使用的资源
网络请求优化:
- 添加请求缓存
- 合并API请求
- 使用Gzip压缩
启动优化:
- 减少Application初始化工作
- 使用SplashScreen API
- 延迟加载非必要组件
5.3 发布前检查清单
| 检查项 | 说明 | 是否完成 |
|---|---|---|
| 应用图标 | 准备各种分辨率的图标 | ☐ |
| 应用名称 | 确认最终名称无侵权 | ☐ |
| 权限说明 | 在隐私政策中说明权限用途 | ☐ |
| API密钥保护 | 检查是否硬编码了敏感信息 | ☐ |
| 混淆配置 | 启用ProGuard/R8混淆 | ☐ |
| 多设备测试 | 在不同尺寸设备上测试 | ☐ |
| 崩溃监控 | 集成Firebase Crashlytics | ☐ |
6. 常见问题解决方案
6.1 百度地图不显示
可能原因及解决方案:
SHA1配置错误:
- 确认debug和release的SHA1都已配置
- 使用keytool重新生成SHA1
包名不匹配:
- 检查AndroidManifest中的package属性
- 确认与百度控制台配置一致
网络权限问题:
- 确认已添加INTERNET权限
- 检查是否被安全软件拦截
6.2 天气数据获取失败
排查步骤:
- 检查和风天气API Key是否有效
- 确认网络请求是否成功(使用Charles或Fiddler抓包)
- 验证返回的数据格式是否与模型匹配
- 检查是否超过API调用限制
6.3 定位不准确
优化建议:
- 确保设备已开启高精度定位模式
- 在室外开阔地带测试
- 检查百度地图定位参数配置
- 考虑使用混合定位策略(GPS+网络)
7. 扩展功能建议
7.1 天气预警通知
实现步骤:
- 注册广播接收器监听系统时间变化
- 定时检查天气预警信息
- 使用NotificationManager发送通知
- 添加通知渠道适配Android 8.0+
核心代码片段:
public class WeatherNotification { private static final String CHANNEL_ID = "weather_alerts"; public static void showAlert(Context context, String title, String message) { createNotificationChannel(context); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.ic_alert) .setContentTitle(title) .setContentText(message) .setPriority(NotificationCompat.PRIORITY_HIGH) .setAutoCancel(true); NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); manager.notify(createID(), builder.build()); } private static void createNotificationChannel(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( CHANNEL_ID, "Weather Alerts", NotificationManager.IMPORTANCE_HIGH ); channel.setDescription("Weather warning notifications"); NotificationManager manager = context.getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } } private static int createID() { return (int) System.currentTimeMillis(); } }7.2 多城市天气管理
实现方案:
- 创建城市数据库表
- 实现城市添加/删除功能
- 使用ViewPager2展示多个城市天气
- 添加城市搜索自动完成
数据库表结构建议:
CREATE TABLE cities ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, latitude REAL NOT NULL, longitude REAL NOT NULL, is_current INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );7.3 天气数据本地缓存
缓存策略实现:
- 使用Room数据库存储历史数据
- 设置合理的缓存过期时间
- 网络不可用时自动使用缓存
- 添加数据同步状态提示
缓存模型设计:
@Entity(tableName = "weather_cache") public class CachedWeather { @PrimaryKey public String locationId; public String weatherJson; public String airQualityJson; public long timestamp; public boolean isExpired() { return System.currentTimeMillis() - timestamp > 30 * 60 * 1000; // 30分钟过期 } }在开发过程中,我发现百度地图定位在室内环境下有时会出现较大偏差,这种情况下可以结合IP定位进行补偿。另外,和风天气的免费API有调用频率限制,在实际项目中需要考虑缓存策略或升级到付费版本。
