最近几年,空气质量问题越来越受到关注。作为一个经常骑自行车通勤的程序员,我每天出门前都会习惯性查看空气质量指数。但市面上的天气App要么功能太简单,要么广告太多,于是萌生了自己开发一款雾霾监测App的想法。
开发这样一款App其实并不复杂,核心就是三个功能:获取当前位置、查询当地空气质量数据、把数据直观展示出来。Android平台提供了完善的开发工具链,配合百度地图、和风天气这些成熟的第三方服务,即使是没有太多移动开发经验的新手,也能在几天内做出一个可用的版本。
这个教程我会从最基础的Android Studio配置讲起,手把手带你实现定位、数据获取和可视化这三个核心模块。过程中会用到一些实用的开源库,比如OkHttp网络请求、Gson数据解析、MPAndroidChart图表绘制等。不用担心基础问题,我会把每个步骤都拆解得足够细致。
首先需要安装Android Studio,建议下载最新稳定版。安装完成后,新建一个Empty Activity项目,Minimum API Level选择Android 6.0(API 23)即可,这样可以覆盖绝大多数设备。
在build.gradle(Module:app)文件中添加以下依赖:
groovy复制dependencies {
// 百度地图SDK
implementation 'com.baidu.lbsyun:BaiduMapSDK_Map:7.4.0'
implementation 'com.baidu.lbsyun:BaiduMapSDK_Search:7.4.0'
implementation 'com.baidu.lbsyun:BaiduMapSDK_Location:9.1.8'
// 网络请求
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
// JSON解析
implementation 'com.google.code.gson:gson:2.8.6'
// 图表库
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
}
我们需要申请两个关键服务的API Key:
百度地图:前往百度地图开放平台,创建应用并获取AK。注意要正确填写应用的包名和签名证书的SHA1值。
和风天气:注册开发者账号后,在控制台可以找到免费的天气API Key。免费版每分钟100次调用足够我们测试使用。
Android系统要求应用在使用敏感权限时必须动态申请。在AndroidManifest.xml中添加以下权限:
xml复制<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
然后在启动Activity中添加权限检查逻辑:
java复制private void checkPermissions() {
String[] permissions = {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.INTERNET
};
ArrayList<String> toRequest = new ArrayList<>();
for (String perm : permissions) {
if (ContextCompat.checkSelfPermission(this, perm) != PackageManager.PERMISSION_GRANTED) {
toRequest.add(perm);
}
}
if (!toRequest.isEmpty()) {
ActivityCompat.requestPermissions(this,
toRequest.toArray(new String[0]),
REQUEST_CODE_PERMISSIONS);
}
}
在Application类中初始化百度地图SDK:
java复制public class MyApp extends Application {
@Override
public void onCreate() {
super.onCreate();
// 初始化百度地图
SDKInitializer.setAgreePrivacy(this, true);
SDKInitializer.initialize(this);
// 设置全局定位参数
LocationClient.setAgreePrivacy(true);
}
}
定位功能的实现主要依靠BDAbstractLocationListener回调:
java复制public class MyLocationListener extends BDAbstractLocationListener {
@Override
public void onReceiveLocation(BDLocation location) {
if (location == null) return;
double latitude = location.getLatitude();
double longitude = location.getLongitude();
String city = location.getCity();
// 更新UI显示
runOnUiThread(() -> {
cityTextView.setText(city);
// 获取该城市的天气数据
fetchWeatherData(city);
});
}
}
我们使用OkHttp来请求和风天气的API。首先封装一个简单的网络工具类:
java复制public class HttpUtil {
public static void get(String url, Callback callback) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(url)
.build();
client.newCall(request).enqueue(callback);
}
}
然后实现天气数据请求:
java复制private void fetchWeatherData(String city) {
String weatherUrl = "https://free-api.heweather.net/s6/weather/now?key=YOUR_KEY&location=" + city;
String airUrl = "https://free-api.heweather.net/s6/air/now?key=YOUR_KEY&location=" + city;
HttpUtil.get(weatherUrl, new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("Weather", "请求失败", e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String json = response.body().string();
WeatherData data = parseWeatherJson(json);
updateWeatherUI(data);
}
}
});
// 同样的方式请求空气质量数据
}
和风天气返回的JSON数据结构比较复杂,我们可以先定义对应的Java类:
java复制public class WeatherData {
@SerializedName("HeWeather6")
public List<HeWeather> heWeatherList;
public static class HeWeather {
public Basic basic;
public Now now;
public String status;
}
public static class Basic {
public String location;
public String parent_city;
}
public static class Now {
public String tmp; // 温度
public String cond_txt; // 天气状况
public String wind_dir; // 风向
public String hum; // 湿度
}
}
然后使用Gson进行解析:
java复制private WeatherData parseWeatherJson(String json) {
Gson gson = new Gson();
return gson.fromJson(json, WeatherData.class);
}
空气质量指数(AQI)通常用圆形进度条展示比较直观。我们可以使用开源库RoundProgressBar:
xml复制<com.mikhaellopez.circularprogressbar.CircularProgressBar
android:id="@+id/aqiProgress"
android:layout_width="150dp"
android:layout_height="150dp"
app:cpb_progress="50"
app:cpb_progressMax="500"
app:cpb_backgroundProgressBarColor="#EEEEEE"
app:cpb_progressBarColor="@color/aqi_good"/>
在代码中动态更新:
java复制private void updateAqiUI(int aqi) {
aqiProgress.setProgress(aqi);
// 根据AQI值设置不同颜色
int color;
if (aqi <= 50) {
color = getResources().getColor(R.color.aqi_good);
} else if (aqi <= 100) {
color = getResources().getColor(R.color.aqi_moderate);
} else {
color = getResources().getColor(R.color.aqi_bad);
}
aqiProgress.setProgressBarColor(color);
}
使用MPAndroidChart绘制最近7天的温湿度折线图:
java复制private void setupChart(List<DailyData> dailyData) {
LineChart chart = findViewById(R.id.tempChart);
List<Entry> tempEntries = new ArrayList<>();
List<Entry> humEntries = new ArrayList<>();
for (int i = 0; i < dailyData.size(); i++) {
tempEntries.add(new Entry(i, dailyData.get(i).temp));
humEntries.add(new Entry(i, dailyData.get(i).humidity));
}
LineDataSet tempSet = new LineDataSet(tempEntries, "温度");
tempSet.setColor(Color.RED);
tempSet.setValueTextColor(Color.BLACK);
LineDataSet humSet = new LineDataSet(humEntries, "湿度");
humSet.setColor(Color.BLUE);
humSet.setValueTextColor(Color.BLACK);
LineData lineData = new LineData(tempSet, humSet);
chart.setData(lineData);
// 自定义图表外观
chart.getDescription().setEnabled(false);
chart.getXAxis().setValueFormatter(new IndexAxisValueFormatter(getWeekDays()));
chart.invalidate();
}
根据天气状况显示不同的背景图可以大大提升用户体验:
java复制private void updateBackground(String weatherCond) {
int resId;
if (weatherCond.contains("晴")) {
resId = R.drawable.bg_sunny;
} else if (weatherCond.contains("雨")) {
resId = R.drawable.bg_rainy;
} else if (weatherCond.contains("云")) {
resId = R.drawable.bg_cloudy;
} else {
resId = R.drawable.bg_default;
}
// 使用Glide加载图片并添加渐变过渡效果
Glide.with(this)
.load(resId)
.transition(DrawableTransitionOptions.withCrossFade())
.into(backgroundImage);
}
添加定时任务每小时自动刷新数据:
java复制private void setupAutoRefresh() {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
if (currentCity != null) {
fetchWeatherData(currentCity);
}
}, 1, 1, TimeUnit.HOURS);
}
在实际开发过程中,我遇到过几个典型问题:
百度地图黑屏问题:通常是因为SHA1配置不正确,或者AK与应用包名不匹配。建议使用百度提供的校验工具检查配置。
网络请求失败:Android 9.0以上默认禁止明文传输,需要在AndroidManifest.xml中添加:
xml复制<application android:usesCleartextTraffic="true">
图表卡顿:MPAndroidChart在数据量较大时可能会出现卡顿,可以通过以下方式优化:
java复制chart.setDrawMarkers(false);
chart.setHighlightPerTapEnabled(false);
lineData.setDrawValues(false);
内存泄漏:OkHttp回调持有Activity引用可能导致内存泄漏,建议使用弱引用或者在onDestroy中取消请求:
java复制@Override
protected void onDestroy() {
if (call != null) {
call.cancel();
}
super.onDestroy();
}
开发这类工具型App最有成就感的地方在于,你每天都会使用自己亲手打造的产品。从最初的简单功能到逐步添加各种实用特性,看着App一点点变得完善,这个过程本身就是最好的学习体验。