news 2026/10/1 11:55:16

AndroidStudio天气预报小程序开发:API接口、线程模型与JSON解析避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AndroidStudio天气预报小程序开发:API接口、线程模型与JSON解析避坑指南

简介:基于Android Studio的天气预报小程序完整源码包,面向具备Android基础、希望学习网络请求与JSON解析的移动开发初学者及毕业设计参考者。资源共1260个文件,压缩包大小9.76MB,包含Java源码、XML布局、Gradle配置、APK安装包及大量编译中间文件,其中flat、dex、class等构建产物可直接对照项目结构理解Android工程组织方式。项目采用Retrofit与Gson实现天气API调用,涵盖城市查询、温度湿度显示、异步回调处理、下拉刷新及本地缓存等常见功能模块,源码注释清晰,适合边读边改快速上手。已有3876人学习下载,对想要掌握Android Studio项目初始化、第三方接口接入和UI数据绑定流程的开发者具有直接参考价值。

1. 用 AndroidStudio 做天气预报小程序,先想清楚数据源、线程和 UI 三件事

很多人拿着“AndroidStudio 天气预报小程序源码”这个关键词搜索,第一反应以为是微信小程序。其实这里说的是一个跑在 Android 手机上的原生小工具,工程入口叫 MainActivity,打包后装进手机就是一个能查天气的 App。做这件事的核心,不是写布局,而是三件事:天气数据从哪个接口来,网络请求放在哪个线程跑,接口返回的 JSON 怎么映射到界面。把这三件事理清楚,剩下的代码都是体力活。如果你是想做成微信小程序,请去用微信开发者工具或者 uniapp 那套;这篇讲的是 AndroidStudio 原生工程,适合课程设计、个人工具箱、或者工作室接小单的场景。

2. 把天气接口的数据结构吃透:选 API、定字段、建最小项目

2.1 免费天气 API 怎么挑:我用过三种方案后的选择

天气 App 的数据源只有两条路:自己买气象数据服务,或者用开放平台接口。个人项目一定走后者。市场上常见的几类:和风天气、聚合数据、心知天气,以及很多教程里会出现的不需要 key 的公开接口。我的建议是,学习阶段不要一上来就去注册和风天气、聚合数据拿 appKey,因为你还要处理密钥管理、服务条款这些额外因素,而公开接口能让你把注意力集中在 Android 的请求与解析上。

我平时给学生演示用的是wthrcdn.etouch.cn/weather_mini?city=北京这个接口。它不需要 key,返回的是 JSON,字段完整,有当天预报、温度、风力和污染指数,能在浏览器里直接打开验证。缺点是不保证长期可用,生产环境千万别依赖这种接口。如果你要做一个能上架的产品,去和风天气注册开发者账号,免费档足够个人 App 用;如果想在企业内网做演示,也可以自己在后端用高德天气那份数据包一层 JSON。

选接口时看三样东西:是否需要 key、是否支持 HTTPS、返回 JSON 是否稳定。下面是我常用的一个判断表。

方案是否需要 key返回格式适合场景主要坑
公开接口(wthrcdn 等)否JSON学习 / 课程设计随时可能失效或被限流
和风天气是JSON个人作品 / 小商单免费版有请求频率限制
聚合数据是JSON学生项目部分数据要付费,文档改动频繁
高德开放平台是JSON已经依赖高德地图的项目天气作为增值接口,权限配置较烦

结论很直接:只想跑通源码,用第一条;想拿出去展示给人用,用第二条。到最后统一用 https 接口,下面代码为了演示方便先用 http,避坑章里会专门讲明文流量限制。

2.2 接口返回的 JSON 长什么样:先看字段再写解析

拿到任何接口,第一件事不是写 Activity,而是先取一段真实返回样本,把结构剥开。wthrcdn.etouch.cn/weather_mini?city=北京返回的大致结构如下:

{ "data": { "yesterday": { "date": "4日星期五", "high": "高温 30℃", "low": "低温 21℃", "type": "晴" }, "city": "北京", "forecast": [ { "date": "5日星期六", "high": "高温 31℃", "low": "低温 22℃", "type": "多云", "fengxiang": "东南风" } ], "wendu": "26", "ganmao": "昼夜温差大,风力较强,易感冒" }, "status": 1000, "desc": "OK" }

注意几个容易搞错的点。城市名在data.city,这是你在 EditText 里输入北京后,接口回传的标准城市名;当天天气在data.forecast[0],数组第一位是今天,不是明天;当前实时温度在data.wendu,它不在 forecast 数组里,通常是一个字符串数字;感冒建议在data.ganmao,用来做文案展示非常合适。

写解析的时候,先找forecast数组,取[0],再从它里面取date、type、high、low。这四个字段决定了 UI 上要展示的内容。很多人写代码时以为data.temp就是当前温度,拿回来的 JSON 里根本没有,然后getString抛异常,这就是没做第一步的后果。拿到样本之后,建议把 JSON 存成一个本地文件,后面可以用它做单元测试,这一点在最后一章会展开。

2.3 项目最小化:文件清单与 AndroidManifest 权限

很多“源码包”下载下来一堆文件,但你能跑通的最少工程应该只有这几样。

WeatherDemo/ ├── app/ │ ├── src/main/ │ │ ├── java/com/example/weather/MainActivity.java │ │ ├── res/layout/activity_main.xml │ │ └── AndroidManifest.xml │ └── build.gradle ├── build.gradle └── settings.gradle

不要把网络库、图片加载库、城市数据库一上来全引进去。先跑通,再加东西。Manifest 是这个小程序能不能联网的第一道关卡。在 AndroidStudio 中新建工程后,如果忘了加 INTERNET 权限,所有请求都会报Permission denied。同时因为接口是 http 明文,低版本没事,高版本必须给 Application 加上usesCleartextTraffic="true"。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET" /> <application android:usesCleartextTraffic="true" android:label="@string/app_name"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

usesCleartextTraffic这个属性只适合开发调试。如果你面向 Android 9 以上做正式发布,最好是选支持 https 的天气接口,或者在后端做转发。曾见过有人把usesCleartextTraffic="true"留在线上包里的,审计时会被挑出来,这个坑要在最早期就避免掉。

布局方面,最小可行方案是一个输入框、一个查询按钮、一个文本展示区。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="20dp"> <EditText android:id="@+id/etCity" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="输入城市名,如:北京" /> <Button android:id="@+id/btnQuery" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="查询天气" /> <TextView android:id="@+id/tvInfo" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:textSize="16sp" android:lineSpacingExtra="6dp" /> </LinearLayout>

这里的两个控件 id 后面 MainActivity 要引用,不要改名字。所有布局都写在一个LinearLayout里,避免 ScrollView 嵌套的麻烦。等天气字段多了,再把 TextView 改成 RecyclerView,但那是后话。

3. 在 MainActivity 里跑通请求与解析:线程模型和完整 Java 代码

3.1 核心逻辑:用 HttpURLConnection 请求天气接口

AndroidStudio 创建的默认工程使用 androidx,MainActivity 继承AppCompatActivity。先声明 UI 控件,在onCreate里绑定,然后在按钮点击时读取城市输入框的值。

public class MainActivity extends AppCompatActivity { private EditText etCity; private Button btnQuery; private TextView tvInfo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); etCity = findViewById(R.id.etCity); btnQuery = findViewById(R.id.btnQuery); tvInfo = findViewById(R.id.tvInfo); btnQuery.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String city = etCity.getText().toString().trim(); if (city.isEmpty()) { tvInfo.setText("请输入城市名"); return; } queryWeather(city); } }); } }

queryWeather方法要负责两件事:拼 URL,发请求。拼 URL 时城市名必须做URLEncoder.encode,不然用户输入“上海”时是中文,直接拼进地址里会变成无效请求。

private void queryWeather(final String city) { new Thread(new Runnable() { @Override public void run() { HttpURLConnection connection = null; try { String urlStr = "http://wthrcdn.etouch.cn/weather_mini?city=" + URLEncoder.encode(city, "UTF-8"); URL url = new URL(urlStr); connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(8000); connection.setReadTimeout(8000); connection.setRequestProperty("User-Agent", "Mozilla/5.0"); connection.setRequestProperty("Accept-Encoding", "identity"); if (connection.getResponseCode() == 200) { InputStream is = connection.getInputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = is.read(buffer)) != -1) { baos.write(buffer, 0, len); } String json = baos.toString("UTF-8"); baos.close(); is.close(); parseAndShow(json); } else { showError("服务器返回:" + connection.getResponseCode()); } } catch (Exception e) { e.printStackTrace(); showError("请求失败:" + e.getMessage()); } finally { if (connection != null) { connection.disconnect(); } } } }).start(); }

这里新开了一个 Thread,原因写在代码后面:Android 不允许在主线程执行网络访问,强行访问直接抛NetworkOnMainThreadException。超时时间设置为 8 秒,因为免费天气接口在移动网络下经常超过 5 秒,太短会频繁失败,太长会让用户一直盯着空白页。Accept-Encoding设置成identity是为了避免服务器返回 gzip 压缩流,否则我们没解压就读出来会是乱码,这个坑在下一章专门讲。

3.2 JsonObject 解析与控件回写:注意 wendu 并不在 forecast 里

拿到 JSON 字符串后,不要立刻 setText,先把它解析成 Java 对象。我用的是原生org.json包,是 Android 自带的,不用额外引依赖。

private void parseAndShow(String json) throws JSONException { JSONObject root = new JSONObject(json); int status = root.getInt("status"); if (status != 1000) { showError("接口状态异常:" + status); return; } JSONObject data = root.getJSONObject("data"); String city = data.getString("city"); String wendu = data.getString("wendu"); String ganmao = data.getString("ganmao"); JSONArray forecast = data.getJSONArray("forecast"); JSONObject today = forecast.getJSONObject(0); String date = today.getString("date"); String type = today.getString("type"); String high = today.getString("high"); String low = today.getString("low"); String fengxiang = today.getString("fengxiang"); String text = "城市:" + city + "\n当前温度:" + wendu + "℃" + "\n" + date + "\n天气:" + type + "\n最高温:" + high + "\n最低温:" + low + "\n风向:" + fengxiang + "\n提醒:" + ganmao; runOnUiThread(new Runnable() { @Override public void run() { tvInfo.setText(text); } }); }

JSONObject的getString在字段不存在时会抛异常,所以代码里做了status的检查。还有一点,high和low返回的是“高温 31℃”“低温 22℃”这种带前缀的字符串,如果你打算把它们抠成数字,应该自己写一个替换逻辑:

String highPure = high.replace("高温", "").replace("℃", "").trim();

因为在 AndroidStudio 里用 debug 模式看 JSONObject 结构很不直观,我建议把接口返回的先Log.d打出来,再动手解析。这不是偷懒,而是很多“源码包”里解析错误都出在字段看不到、凭感觉瞎写这一步。

3.3 请求失败时的兜底文案 & 完整 MainActivity

网络请求不能指望永远成功。免费接口经常在晚上被刷挂,或者手机网络超时,所以要给用户一个明确的失败提示,而不是直接闪退。逻辑是:解析成功用runOnUiThread刷新 TextView,失败也用runOnUiThread刷新成错误信息。

private void showError(final String message) { runOnUiThread(new Runnable() { @Override public void run() { tvInfo.setText(message); } }); }

showError和parseAndShow都用到了异步线程,所以无论如何 UI 更新都发生在主线程,避免在子线程里直接改控件导致的CalledFromWrongThreadException。把前面几个方法合到一起,就是一个能跑的 MainActivity。实际应用里,我还喜欢在点击查询后先把按钮 disable 掉,防止用户连点十几个请求把自己的 IP 送进限流名单:

btnQuery.setEnabled(false); // 请求返回后再 setEnabled(true)

这个防抖逻辑虽然朴素,但效果明显。你可以把它放在queryWeather开头和parseAndShow/showError的尾部。

核心流程跑通后,你会发现天气小程序的主干根本不是 UI,而是线程模型和异常处理。这个项目小,正是学线程协作的好案例。

4. 天气预报小程序避坑指南:5 个新手最容易翻车的地方

4.1 崩溃:NetworkOnMainThreadException

现象是点击查询按钮后 App 直接闪退,Logcat 里写着android.os.NetworkOnMainThreadException。原因是 Android 从 API 11 开始禁止在 UI 线程执行网络请求,HttpURLConnection 的connect()一旦在主线程调用,必崩。解决方式就是前面演示过的new Thread加runOnUiThread。更现代的做法是用协程或 RxJava,但理解线程模型之前,不要用那些包装好的工具。

你也许会问:为什么我用模拟器打开浏览器能访问天气地址,App 里就崩?因为浏览器自己也用了网络线程。这不是普通异常,它是由系统安全策略强制抛出的,无法通过 try-catch 吞掉。

4.2 中文乱码:压缩流和编码不一致

现象是接口请求成功,返回的 JSON 能打到 Logcat 里,但城市名是乱码,温度是乱码。这里有两个常见原因。第一个是编码,HttpURLConnection 读取输入流时,你没有指定 UTF-8,而是直接拿了stream.toString(),得到的是操作系统默认编码的解码结果。第二个原因是服务器开启了 gzip 压缩,响应头里有Content-Encoding: gzip,而客户端没有解压,读出来全是乱码二进制。解决方式是两行:

connection.setRequestProperty("Accept-Encoding", "identity"); String json = baos.toString("UTF-8");

第一行告诉服务器不要压缩,第二行强制按 UTF-8 解码。如果要用 gzip,也可以,前提是自己包一层GZIPInputStream,但能少一事就少一事。这个坑在网上很多源码包里都没处理,因为它只在特定网络环境下才暴露,遇到一次就记住一辈子。

4.3 请求被拒:cleartext 限制与模拟器 10.0.2.2

现象是 AndroidStudio 自带模拟器上运行,Logcat 报错CLEARTEXT communication to wthrcdn.etouch.cn not permitted by network security policy。从 Android 9(API 28)开始,系统默认禁止明文 HTTP 请求。如果你还用系统生成的 default config,访问 http 接口就会报这个错。解决方式是在 Application 标签上加android:usesCleartextTraffic="true",或者把天气接口换成 https。要说明的是,usesCleartextTraffic只影响当前 App,不会影响系统设置。

如果你自己在本机写了一个 mock 接口,在模拟器上访问localhost或127.0.0.1永远连不上。模拟器里的localhost是模拟器自己,不是你的开发电脑。Android 模拟器约定10.0.2.2指向宿主机。调试本地接口时,地址应写成:

String urlStr = "http://10.0.2.2:8080/weather_mini?city=北京";

很多新手卡在这一关,折腾半天发现不是代码问题,而是地址写错。真机调试要用电脑的局域网 IP,还要保证同一个 Wi-Fi,这已经是基础网络常识了。

4.4 接口又超时了:免费接口的限流与重试策略

现象是应用偶尔返回超时,偶尔返回空数据,在某个时间段内频繁失败。原因是免费接口没有 SLA 承诺,尤其是不需要 key 的公开接口,经常限流或者被系统挡掉。解决办法有三个层级:第一,设置合理的超时,8 秒是我的经验值,再长用户就按退出键了;第二,在本地做缓存,把上一次成功的数据存到 SharedPreferences,至少让用户下次打开能看到离线结果;第三,做一次重试,但重试要退避,不要点击一下瞬间发三次请求。

重试我一般写得很保守:

int retryCount = 0; while (retryCount < 2) { try { // 执行请求 break; } catch (SocketTimeoutException e) { retryCount++; if (retryCount == 2) { showError("天气接口超时,请稍后重试"); } } }

SocketTimeoutException和IOException要分开处理,前者是网络慢,后者可能是地址失效。这个区分能帮你判断是接口挂了,还是自己网络不行。

4.5 模拟器第一次启动一直卡在 AndroidStudio 的 Starting up

现象是新装完 AndroidStudio,创建天气项目后启动模拟器,窗口卡在Starting up很久。原因不是天气代码的问题,而是模拟器选择了一个慢的 AVD 镜像,或者在下载系统镜像。解决方式是先检查AVD Manager里系统镜像是不是 x86_64,太老的 ARM 镜像在 Windows 上慢到怀疑人生。这个和天气接口无关,但既然你是从 AndroidStudio 开始,大概率会遇到。别在模拟器没起来时就去排查代码,先确认模拟器本身状态。

5. 让小程序像正式项目一样长跑:封装、本地 JSON 验证与发布注意

5.1 把网络请求抽成 WeatherData 类

当 MainActivity 已经跑通,下一步就是把请求逻辑从 Activity 里抽出去,不然以后想切 OkHttp、换协程,都要改 UI 层代码。我一般定义一个WeatherDataSource:

public class WeatherDataSource { public void query(final String city, final WeatherCallback callback) { new Thread(new Runnable() { @Override public void run() { try { // 原来的 HttpURLConnection 逻辑 callback.onSuccess(weatherText); } catch (Exception e) { callback.onError(e.getMessage()); } } }).start(); } public interface WeatherCallback { void onSuccess(String weatherText); void onError(String error); } }

Activity 只依赖WeatherDataSource和WeatherCallback,不再直接写 URL。这个封装大约二十行,但收益是后续换 OkHttp 时,只有这个文件要动。注意回调仍然是子线程,等引入协程后,可以再改成 suspend 函数,但在当前阶段,这样足够清晰。

5.2 用 Assets 里的本地 JSON 验证解析逻辑

我在写解析逻辑前,会把接口返回先存一份到app/src/main/assets/sample_weather.json。然后在解析方法里临时绕过网络,直接读本地文件,用来验证JSONObject的字段名是否正确。这个方法很土,但极其有效。特别是当接口升级字段名改变时,你只需要更新 assets 里的样本,跑一次解析用例,立刻知道哪里崩。

下面是一个简单的工具方法:

private String loadAsset(Context context, String fileName) { try { InputStream is = context.getAssets().open(fileName); ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buf = new byte[1024]; int len; while ((len = is.read(buf)) != -1) { baos.write(buf, 0, len); } return baos.toString("UTF-8"); } catch (IOException e) { return null; } }

然后把parseAndShow的输入改成:

parseAndShow(loadAsset(this, "sample_weather.json"));

这样跑 App 时,不联网也能把 UI 调好。我处理 Android 项目时,标准顺序是先离线调 UI,再切回在线请求,最后做真机测试。不要反过来,一边断网一边猜字段,永远写不顺。

5.3 发布前必须做的三件小事

如果你想把天气小程序打包给别人安装,有三个点要处理。第一,usesCleartextTraffic是否保留,为了上架或外出演示,建议换成 https 接口,这个属性最好去掉。第二,城市输入要做空值和非法字符校验,我已经在点击事件里做了isEmpty判断,还可以用正则过滤掉数字和符号。第三,字体大小和行间距在深色模式下要测试,TextInfo 默认是黑色字,如果用户开了深色主题,黑色字体会看不清。

我现在的习惯是,拿到任何一个天气类项目,先把接口返回样本存到 Assets,再写解析,最后才开始写界面。这个顺序救过我太多次。希望这篇 AndroidStudio 天气预报小程序的实战笔记能帮到你。

本文还有配套的精品资源,点击获取

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

Spring Cloud Alibaba与Spring Boot、Nacos版本对应关系及避坑指南

先说我自己的翻车经历。几年前第一次搭 Spring Cloud Alibaba 环境&#xff0c;Spring Boot 用的 2.6.2&#xff0c;Spring Cloud 用的 2021.0.1&#xff0c;Nacos Server 下的 1.4.2&#xff0c;结果服务就是注册不上去。控制台打开好几遍&#xff0c;服务列表空空如也&#x…

作者头像 李华
网站建设 2026/10/1 11:54:04

java 爬虫大型教程(一)

java 爬虫大型教程&#xff08;一&#xff09;在开始之前, 鉴于这是一份大型的教程, 我们应当从最基础的环境变量配置环节说起, 逐步展开详细的搭建过程。关于电脑环境这块, 因为我的这台设备是 pro 型号嘛, 所以系统环境变量配置的方式, 和标准版本的那些机器, 它是不一样的。…

作者头像 李华
网站建设 2026/10/1 11:53:00

python多线程并发测试过程

进行多线程并发测试的过程。更新时间为2025年05月27日15点25分35秒, 作者是姑娘别秃头。这篇文章主要的内容是介绍了关于多线程并发的测试过程, 这样的资料是具有非常好的参考价值的, 希望能够帮助到大家, 如果文章中存在错误或没有考虑完全的情况, 希望读者能够不吝赐教。一、…

作者头像 李华
网站建设 2026/10/1 11:52:35

Ember-1模型:大模型推理Token压缩40%的工程实践

1. 项目概述&#xff1a;一场被低估的推理效率革命Fireworks AI 这次发布的 Ember-1 模型&#xff0c;表面看只是“基于 Kimi K3 的后训练模型”&#xff0c;但真正值得所有开发者、算法工程师和产品技术负责人坐直身体细读的&#xff0c;是那句轻描淡写的“推理 Token 减少约 …

作者头像 李华
网站建设 2026/10/1 11:50:46

openclaw重启实战:从systemd到Docker的完整排查指南

1. 为什么“重新启动”会成为 openclaw 的日常操作先说个场景&#xff1a;我是在一台 Ubuntu 服务器上用一键部署脚本装的 openclaw&#xff0c;当时图省事&#xff0c;一切按默认配置跑起来。最初一两个月相安无事&#xff0c;直到有一次我改了配置文件里的模型参数&#xff0…

作者头像 李华