news 2026/10/15 14:32:39

Android天气App实战:百度地图定位与和风天气API集成及折线图展示

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android天气App实战:百度地图定位与和风天气API集成及折线图展示

1. 这个天气App到底解决了什么问题

刚接触Android开发的朋友,十有八九都是从“做一个天气App”开始的。原因很简单:它足够小,小到一个周末就能跑通;又足够全,全到能把网络请求、数据解析、UI渲染、权限管理这些核心环节全部串一遍。但真正动手之后你会发现,坑远比想象中多——接口怎么选、数据怎么解析、城市怎么查、折线图怎么画,每一步都能卡住半天。

这个项目就是冲着这些坑去的。它把百度地图API、和风天气API、城市查询和折线展示这四个模块拼在一起,形成一个完整的天气查询工具。用户打开App,可以通过地图定位当前城市,也可以手动搜索城市名,然后拉取实时天气和未来几天的预报数据,最后用折线图把温度变化直观地画出来。

适合谁来参考?如果你已经装好了Android Studio,能跑通一个Hello World,但对网络请求、JSON解析、第三方SDK集成还比较模糊,那这个项目的结构就是为你准备的。它不追求UI多华丽,重点在于把“数据从哪来、怎么拿、怎么显示”这条链路讲清楚。下面我会按照实际开发的顺序,把每个环节的选型理由、关键代码和踩过的坑逐一拆开说。

2. 整体架构与技术选型思路

2.1 为什么是百度地图加和风天气这个组合

先说说为什么选这两个API。百度地图API在国内的覆盖率和定位精度都比较稳,尤其是它的城市反地理编码功能,能把经纬度直接转成城市名,省去了自己维护城市坐标表的麻烦。和风天气API的优势在于免费额度对个人开发者足够友好,而且返回的JSON结构清晰,字段命名规范,解析起来不费劲。

这两个服务配合的逻辑是这样的:百度地图负责“你在哪”,和风天气负责“那里天气怎么样”。中间用城市名或城市ID作为桥梁。百度地图返回的城市名可以直接传给和风天气的城市查询接口,拿到对应的Location ID,再用这个ID去请求天气数据。整个链路是通的,不需要额外的中间层。

注意:百度地图的定位SDK需要申请AK,和风天气需要申请Key,这两个凭证都要在对应的开发者后台配置包名和签名SHA1,否则请求会直接返回错误码。

2.2 项目模块划分与数据流向

整个App我把它拆成四个模块:定位模块、城市搜索模块、天气数据模块和图表渲染模块。定位模块负责拿到当前经纬度并反编码成城市名;城市搜索模块提供一个输入框,用户手动输入城市名后调用和风天气的城市查询接口;天气数据模块根据城市ID拉取实时天气和逐日预报;图表模块把逐日预报里的最高温和最低温抽出来,用折线图展示。

数据流向是单向的:定位或搜索产生城市信息,城市信息触发天气请求,天气请求返回的数据驱动图表更新。这种单向流动的好处是调试方便,哪个环节出问题,看日志就能定位到具体模块。我试过把定位和天气请求耦合在一起,结果定位失败的时候天气也拉不到,排查起来很头疼,后来拆开之后就清爽多了。

2.3 网络框架和图表库的选择理由

网络请求这块,我选的是OkHttp + Gson这个组合。OkHttp负责发请求和处理回调,Gson负责把JSON字符串转成Java对象。没有用Retrofit是因为这个项目的接口数量不多,手写请求反而更直观,新手也更容易看懂每一步在做什么。Gson的字段映射用@SerializedName注解就能搞定,和风天气返回的字段名和Java命名规范不一致的地方,加个注解就解决了。

折线图用的是MPAndroidChart。这个库在Android圈子里用得比较广,文档和示例都多,遇到问题搜一下基本能找到答案。它的LineChart控件支持多条折线、自定义轴标签、动画效果,对于温度展示这个场景来说功能绰绰有余。唯一需要注意的是它的坐标轴配置项比较多,第一次用容易配乱,后面我会把关键配置单独拎出来讲。

3. 开发环境搭建与依赖配置

3.1 Android Studio版本与Gradle配置要点

我用的Android Studio版本是较新的稳定版,Gradle插件版本和Gradle版本要匹配,不然同步的时候会报一堆莫名其妙的错。具体来说,build.gradle(项目级)里的com.android.tools.build:gradle版本要和gradle-wrapper.properties里的distributionUrl版本对应上。我一般直接看Android Studio新建项目时默认给的组合,那个组合是经过验证的,不会出问题。

minSdkVersion我设的是21,也就是Android 5.0。这个版本覆盖了绝大多数在用设备,同时也能用上一些比较新的API。targetSdkVersion设成最新的稳定版就行,但要注意Android 6.0以上定位权限需要动态申请,不能只在Manifest里声明。

3.2 百度地图SDK集成步骤与AK申请

百度地图的集成分三步。第一步是在开发者后台创建应用,填写包名和SHA1。SHA1的获取方式是在Android Studio的Terminal里执行keytool命令,或者直接在Gradle面板里找signingReport任务运行一下,输出里就有。第二步是下载SDK,把jar包和so库放到libs目录和jniLibs目录下。第三步是在Manifest里配置AK和定位服务。

<meta-data android:name="com.baidu.lbsapi.API_KEY" android:value="你的AK" /> <service android:name="com.baidu.location.f" android:enabled="true" android:process=":remote" />

提示:so库要按CPU架构分目录放,如果只放armeabi-v7a,在64位设备上可能会找不到库。保险起见把arm64-v8a也加上。

3.3 和风天气Key配置与网络权限声明

和风天气这边相对简单,在后台拿到Key之后,直接在请求URL里拼上就行。Manifest里需要声明INTERNET权限和ACCESS_NETWORK_STATE权限。如果要做定位,还要加ACCESS_FINE_LOCATION和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_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

Gradle依赖这块,把OkHttp、Gson和MPAndroidChart加进去:

implementation 'com.squareup.okhttp3:okhttp:4.9.3' implementation 'com.google.code.gson:gson:2.8.9' implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'

MPAndroidChart需要在项目级build.gradle里加JitPack仓库,不然拉不下来。

4. 城市查询功能的实现细节

4.1 百度地图定位与反地理编码

定位这块,百度地图的LocationClient是核心类。初始化的时候要设置定位参数,包括定位模式、返回坐标类型、是否开启缓存等。我一般用Hight_Accuracy模式,兼顾精度和耗电。定位回调里拿到BDLocation对象后,直接调getCity()就能拿到城市名,不需要再手动调反地理编码接口。

LocationClient locationClient = new LocationClient(getApplicationContext()); LocationClientOption option = new LocationClientOption(); option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy); option.setIsNeedAddress(true); option.setCoorType("bd09ll"); locationClient.setLocOption(option); locationClient.registerLocationListener(new BDLocationListener() { @Override public void onReceiveLocation(BDLocation bdLocation) { String city = bdLocation.getCity(); // 拿到城市名后触发天气请求 } }); locationClient.start();

注意:定位回调可能多次触发,要在拿到有效城市名后及时调用locationClient.stop(),不然会一直回调,浪费电也浪费流量。

4.2 和风天气城市搜索接口调用

和风天气的城市查询接口是https://geoapi.qweather.com/v2/city/lookup,传参是location(城市名)和key。返回的JSON里有一个location数组,每个元素包含name、id、adm1、adm2等字段。用户输入城市名后,我把返回结果展示在一个列表里,让用户选择具体是哪个城市,因为同一个名字可能对应多个地方。

OkHttpClient client = new OkHttpClient(); String url = "https://geoapi.qweather.com/v2/city/lookup?location=" + cityName + "&key=" + KEY; Request request = new Request.Builder().url(url).build(); client.newCall(request).enqueue(new Callback() { @Override public void onResponse(Call call, Response response) throws IOException { String json = response.body().string(); CityResponse cityResponse = new Gson().fromJson(json, CityResponse.class); // 更新UI列表 } });

4.3 城市列表去重与展示逻辑

城市搜索结果里经常会出现重复项,比如“朝阳”既是北京的一个区,也是辽宁的一个市。我的处理方式是按照adm1(一级行政区)和adm2(二级行政区)拼接一个显示名称,比如“北京市-朝阳区”和“辽宁省-朝阳市”,这样用户一眼就能区分。列表项点击后,把对应的id存到全局变量里,后续天气请求都用这个ID。

实操心得:和风天气的城市ID是稳定的,不会变来变去,所以可以放心地把它作为唯一标识。不要用城市名做标识,因为重名的情况太多了。

5. 天气数据拉取与解析实战

5.1 实时天气接口的请求与字段映射

实时天气接口是https://devapi.qweather.com/v7/weather/now,传location(城市ID)和key。返回的now对象里有temp(温度)、text(天气状况)、humidity(湿度)、windDir(风向)等字段。我用一个NowWeather类来映射,字段名不一致的地方加@SerializedName。

public class NowWeather { @SerializedName("temp") private String temperature; @SerializedName("text") private String weatherText; @SerializedName("humidity") private String humidity; // 省略getter和setter }

解析的时候直接new Gson().fromJson(json, NowWeather.class)就行,Gson会自动把JSON字段映射到Java字段上。如果某个字段在JSON里不存在,Java字段就是null,不会抛异常,这点比手动解析省心很多。

5.2 逐日预报接口与温度数据提取

逐日预报接口是https://devapi.qweather.com/v7/weather/7d,返回一个daily数组,每个元素包含fxDate(日期)、tempMax(最高温)、tempMin(最低温)、textDay(白天天气)等。我要做折线图,所以需要把tempMax和tempMin分别抽成两个List<Float>。

List<Float> maxTemps = new ArrayList<>(); List<Float> minTemps = new ArrayList<>(); for (Daily daily : dailyList) { maxTemps.add(Float.parseFloat(daily.getTempMax())); minTemps.add(Float.parseFloat(daily.getTempMin())); }

注意:和风天气返回的温度是字符串类型,不是数字,解析的时候要转一下。如果接口返回异常或者字段缺失,parseFloat会抛NumberFormatException,所以最好加个try-catch。

5.3 数据缓存与请求频率控制

和风天气的免费版有请求次数限制,所以不能每次打开App都无脑请求。我的做法是用SharedPreferences缓存上一次的天气数据和时间戳,如果距离上次请求不到30分钟,就直接读缓存,不再发网络请求。这样既省额度,又加快了页面加载速度。

long lastRequestTime = sp.getLong("last_request_time", 0); if (System.currentTimeMillis() - lastRequestTime < 30 * 60 * 1000) { // 读缓存 } else { // 发请求 }

实操心得:缓存时间不要设太长,天气变化快,超过半小时的数据参考价值就打折扣了。30分钟是个比较平衡的值。

6. 折线图展示温度变化的完整实现

6.1 MPAndroidChart初始化与坐标轴配置

MPAndroidChart的LineChart初始化有几个关键配置。setDrawGridBackground(false)去掉背景网格,getDescription().setEnabled(false)隐藏右下角的描述文字,getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM)把X轴放到下面。Y轴这边,setAxisMinimum和setAxisMaximum要根据温度数据动态计算,不然折线可能贴着边缘或者超出范围。

LineChart lineChart = findViewById(R.id.line_chart); lineChart.getDescription().setEnabled(false); lineChart.setDrawGridBackground(false); lineChart.getXAxis().setPosition(XAxis.XAxisPosition.BOTTOM); lineChart.getAxisRight().setEnabled(false); lineChart.getAxisLeft().setAxisMinimum(minTemp - 5); lineChart.getAxisLeft().setAxisMaximum(maxTemp + 5);

6.2 温度折线的数据集构建与渲染

每条折线对应一个LineDataSet,里面放Entry对象的列表。Entry的x值是索引,y值是温度。最高温和最低温用不同颜色区分,最高温用暖色,最低温用冷色。setMode(LineDataSet.Mode.CUBIC_BEZIER)可以让折线变成平滑曲线,看起来更舒服。

List<Entry> maxEntries = new ArrayList<>(); for (int i = 0; i < maxTemps.size(); i++) { maxEntries.add(new Entry(i, maxTemps.get(i))); } LineDataSet maxDataSet = new LineDataSet(maxEntries, "最高温"); maxDataSet.setColor(Color.RED); maxDataSet.setCircleColor(Color.RED); maxDataSet.setMode(LineDataSet.Mode.CUBIC_BEZIER);

X轴的标签用日期,通过IndexAxisValueFormatter把索引映射成MM-dd格式的字符串。

6.3 图表交互与动画效果调优

MPAndroidChart自带手势缩放和拖拽,但默认的缩放灵敏度有点高,容易误触。我一般把setScaleEnabled(false)关掉缩放,只保留拖拽。动画用animateX(800),800毫秒的横向展开动画,既流畅又不拖沓。

提示:如果数据点比较多,setDrawCircles(false)可以去掉圆点,只保留折线,视觉上更干净。数据点少的时候保留圆点反而更清晰。

7. 常见问题与排查技巧实录

7.1 定位失败与权限拒绝的处理

定位失败最常见的原因是权限没给。Android 6.0以上要在运行时动态申请ACCESS_FINE_LOCATION,用户拒绝之后还要给个合理的解释,引导去设置页手动开启。另外,如果在室内定位,GPS信号弱,可以降级到网络定位。百度地图的BDLocation.getLocType()返回值可以判断定位类型,161是网络定位,61是GPS定位,62是基站定位。

7.2 接口返回错误码的排查思路

和风天气返回的JSON里有一个code字段,200表示成功,其他值都是错误。401是Key无效,403是访问被拒绝,404是城市ID不存在。遇到错误码先检查Key有没有拼错,再检查城市ID是不是从城市查询接口拿到的。百度地图这边,如果AK配置不对,定位回调会返回162,表示鉴权失败。

7.3 折线图不显示或显示异常的修复

折线图不显示,先检查LineData有没有设置给LineChart,再检查Entry的y值是不是在Y轴范围内。如果折线显示成一条直线,可能是所有温度值都一样,或者Y轴的axisMinimum和axisMaximum设成了同一个值。还有一种情况是invalidate()没调用,数据更新了但图表没刷新。

问题现象可能原因解决方法
定位回调不触发权限未授予动态申请定位权限
天气接口返回401Key无效或过期重新申请Key并更新配置
折线图空白未设置LineData调用setData后invalidate
城市搜索无结果城市名拼写错误检查输入并尝试拼音

避坑技巧:调试网络请求的时候,先把URL复制到浏览器里跑一遍,确认接口本身是通的,再排查代码问题。这样能省很多时间。

8. 源码结构与后续扩展方向

8.1 项目目录组织与关键类说明

源码的目录结构按功能分包:ui包放Activity和Adapter,network包放OkHttp封装和接口定义,model包放Gson映射的实体类,util包放权限工具和缓存工具。核心类有四个:MainActivity负责整体调度,LocationHelper封装定位逻辑,WeatherRepository统一管理天气请求和缓存,ChartManager处理折线图的配置和渲染。

8.2 可以继续打磨的几个方向

这个项目目前是单页面结构,后续可以拆成多页面,比如首页只显示实时天气,第二页显示逐日预报和折线图。还可以加一个城市管理页面,让用户保存多个城市,左右滑动切换。数据层面,可以把缓存从SharedPreferences换成Room数据库,支持更复杂的查询和离线浏览。

个人体会:新手做项目最容易犯的错是贪多求全,一开始就想把功能做全。我的建议是先跑通一条完整链路,哪怕只有一个城市、只有实时天气,跑通了再往上加功能。每加一个功能就测一遍,确保之前的没被改坏。这样虽然看起来慢,但实际比反复调试要快得多。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 14:32:12

亚马逊账户资金冻结申诉实战指南

1. 案例背景与问题定位去年夏天&#xff0c;我收到一位跨境电商卖家的紧急求助&#xff1a;亚马逊账户因涉嫌侵权被冻结&#xff0c;账户余额4.5万美元无法提现。这个案例非常典型——产品Listing中使用的某张产品图被投诉侵犯版权&#xff0c;系统自动触发扫号机制。与常规侵权…

作者头像 李华
网站建设 2026/10/15 14:32:01

076、RDMA编程调试:使用ibv_asyncwatch监控事件

一次诡异的链路断开排查 上周三凌晨两点,生产环境告警:某台存储节点的RDMA链路频繁断开重连。我登录上去,ibstat看到端口状态在ACTIVE和DOWN之间反复横跳,dmesg里全是“ib0: link down”和“ib0: link up”交替出现。最头疼的是,业务进程里ibv_poll_cq返回的错误码是IBV_…

作者头像 李华
网站建设 2026/10/15 14:32:01

STM32F103 DAC输出正弦波失真原因与硬件软件协同优化

简介&#xff1a;本资源是一份面向STM32嵌入式初学者与中级开发者的DAC实践项目&#xff0c;聚焦于利用STM32F103RCT6芯片内置12位DAC通道&#xff08;PA4&#xff09;生成可控频率正弦波&#xff0c;解决模拟信号发生器基础功能实现问题&#xff0c;适用于课程实验、毕业设计及…

作者头像 李华
网站建设 2026/10/15 14:30:58

插件系统深度解析:从加载失败排查到生命周期与手写实现

最近凡是跟工程化沾点边的项目&#xff0c;就没几个能绕开plugins这个单词。光我这周就处理了三起相关报错&#xff1a;failed to load plugins web boot: 2 entries did not activate linxin666/dsh-p、harness failed to load plugins web boot: 1 entry did not activate hu…

作者头像 李华
网站建设 2026/10/15 14:30:55

SQL连续登录问题:时间序列建模与业务语义解析

1. 这道题为什么总被反复考&#xff1f;——它根本不是在考SQL语法“求用户最大连续登录天数”这道题&#xff0c;我在过去八年带过的几十场SQL面试中&#xff0c;几乎每场都会出现。不是因为面试官懒&#xff0c;而是因为它像一把手术刀&#xff0c;能精准切开候选人的真实能力…

作者头像 李华