ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AndroidStudio天气预报小程序源码:网络请求与界面渲染全解析

AndroidStudio天气预报小程序源码:网络请求与界面渲染全解析 简介基于Android Studio开发的天气预报小程序完整源码覆盖权限配置、网络请求、数据解析与界面刷新等关键环节适合Android入门开发者、移动应用课程设计或毕业设计参考。项目完整演示了通过Retrofit与Gson访问天气接口、在MainActivity中创建网络实例并调用enqueue异步处理响应同时利用XML设计城市、温度、湿度等展示控件将返回的JSON数据绑定到界面实现一套从网络到UI的完整闭环可在此基础上继续加入下拉刷新、本地缓存和天气图标等增强功能。压缩包共1260个文件包括33个Java源文件、76个XML布局与配置、120个JSON数据文件、194个dex编译文件及509个flat资源文件还带有gradle、properties工程配置和可直接安装的app-debug.apk整体仅9.76MB其中XML负责界面布局Java承担业务逻辑JSON为接口响应数据目录结构便于分段查看网络层、模型层和界面层代码。源码对关键请求与回调逻辑进行了清晰划分已有3876人下载学习适合边读边调试可帮助理解Android中网络请求、JSON解析、异步任务与UI更新的经典协作方式二次开发时能快速定位API地址、数据模型或UI控件作为天气类小程序的起步模板。1. 用 AndroidStudio 写天气预报小程序源码先想清楚这个“小程序”到底是什么每到课设季“AndroidStudio 实现天气预报小程序源码”这类需求就会扎堆出现。但这个标题真正对应的不是一个能显示温度数字的壳子而是一条完整数据链路用户输入或定位一个城市App 通过 HTTP 请求拉取天气服务商返回的 JSON解析成 Java 对象再渲染到列表或卡片上中间还要处理权限、生命周期、缓存和接口异常。适合做它的人有三类刚学完四大组件想练手的学生、想拿一个完整小项目当作品集入口的转行者、以及需要快速搭一个内部工具型 App 验证想法的开发者。下面按从业者做小项目的习惯把从建工程到排错的路完整走一遍。2. 技术选型与工程骨架为什么原生 Android 项目比“套壳页面”更值得动手2.1 别被“小程序”三个字带偏AndroidStudio 里做的是原生应用先解决一个最常见的认知偏差。如果标题里的“小程序”指的是微信小程序那么正确的开发工具是微信开发者工具不是 AndroidStudio。AndroidStudio 从创建工程到编译运行整个流程都不产出微信小程序包反过来微信开发者工具里也不能跑 Android 原生代码。把这个前提放清楚剩下的路就明朗了在 AndroidStudio 里做“天气预报小程序”本质上是用 Java/Kotlin 写一个轻量原生 App功能聚焦、页面少、代码量可控恰好符合“小程序”规模的直觉。选原生而不是 uniapp 那类跨端方案理由对学习场景很实在天气 App 的核心逻辑是网络请求、JSON 解析、列表渲染和生命周期管理这些恰恰是 Android 原生开发的四块基本功。用跨端框架做你调的是封装好的 API底层是 WebView 还是原生渲染对使用者是个黑匣子用原生写网络层、数据层、UI 层每一层都是你亲手搭的答辩或面试时能讲清楚每一个环节。常见做法是单 Activity 加一个 RecyclerView再加一个下拉刷新就能覆盖大部分需求。2.2 最小可用骨架依赖、权限与网络声明一次配好新建工程建议选 Empty Activity语言用 Java。不是说 Kotlin 不好而是课设、毕设场景下 Java 的参考代码最多遇到问题容易搜到对应方案。工程建好后先打开 module 级的 build.gradle加上网络库、JSON 解析库和 RecyclerView 依赖dependencies { implementation com.squareup.okhttp3:okhttp:4.12.0 implementation com.google.code.gson:gson:2.10.1 implementation androidx.recyclerview:recyclerview:1.3.2 }这里三个库各管一件事OkHttp 负责发 HTTP 请求和管理连接Gson 负责把 JSON 字符串转成 Java 对象RecyclerView 负责列表展示。版本号用你新建工程时 SDK Manager 里能拉到的最新正式发布版即可不必追求新版。加完依赖后同步一次 Gradle等下载完成再继续。接下来改 AndroidManifest.xml。天气 App 必须联网也要读定位两个权限加在 manifest 根节点下uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION /第三个关键点天气接口开发阶段如果走的是 http 明文协议Android 9API 28开始默认禁止。开发期图省事可以在 application 标签上加android:usesCleartextTraffictrue但生产或答辩前建议按第 5 章的方式收紧到具体域名。权限声明里 INTERNET 是 normal 级别安装即授予定位权限是 dangerous 级别Android 6.0 以上需要在代码里动态申请这个在第 4 章处理城市定位时会用到。骨架搭完先别急着写功能在 MainActivity 里加一个 Log 输出跑一次模拟器确认环境没问题。这一步能筛掉大量“代码还没写就卡在环境上”的时间。3. 拉取天气数据并解析从 HTTP 请求到 Java 对象的完整链路3.1 网络库选型OkHttp 与 HttpURLConnection 的取舍Android 原生自带 HttpURLConnection很多人觉得“自带的不需要引依赖更轻”。实际写下来你会发现它的问题不在功能而在工程体验连接管理和超时设置啰嗦异步请求要靠自己起线程回调切回主线程还得手动写 Handler。一个天气 App 只发三四个请求用 HttpURLConnection 勉强能扛但代码会越写越脏。OkHttp 的优势集中在这几点连接池复用 TCP 连接减少重复握手connectTimeout和readTimeout分开设置超时行为可控enqueue方法自带异步回调虽然回调仍在子线程但配合runOnUiThread或 Handler 就能干净地切回主线程。它的依赖体积对一个小应用来说完全可接受。我把 OkHttp 客户端封装成一个单例避免每次请求都新建对象public class HttpUtil { private static OkHttpClient client; public static OkHttpClient getClient() { if (client null) { client new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build(); } return client; } }connectTimeout是建立 TCP 连接的最长等待时间readTimeout是拿到响应数据的最长等待时间各设 10 秒对天气接口够用。如果你在弱网环境调试可以把 readTimeout 放到 15 秒但不要超过 20 秒——超过后用户会明显感知到“卡住了”。3.2 天气接口的 URL 拼装与参数说明把请求写到能复现的程度天气数据从哪来是很多人卡住的第一关。常见做法是注册和风天气或高德开放平台拿到一个免费 Key。以和风天气为例请求三天预报的 URL 长这样https://devapi.qweather.com/v7/weather/3d?location101010100keyYOUR_KEYlocation 参数支持城市 ID 和经纬度两种写法。城市 ID 是服务商维护的一套编码比如 101010100 是北京101020100 是上海。经纬度写法形如location39.93,116.40注意是纬度在前经度在后。用城市 ID 的好处是稳定可控适合课设用经纬度则要结合定位权限体验更完整。把 Key 放在哪也有讲究。直接写在 MainActivity 里代码一提交到 Git 仓库就泄露了。常见做法是配置在build.gradle的buildConfigField里buildTypes { release { buildConfigField String, WEATHER_KEY, \你的Key\ minifyEnabled false } debug { buildConfigField String, WEATHER_KEY, \你的Key\ } }这样代码里通过BuildConfig.WEATHER_KEY读取Key 不会出现在 Java 源码中。这只是基本保护真正上线还要把 Key 放到服务端做中转这里不展开。发请求的代码用 OkHttp 的异步方式String url https://devapi.qweather.com/v7/weather/3d?location cityId key BuildConfig.WEATHER_KEY; Request request new Request.Builder() .url(url) .get() .build(); HttpUtil.getClient().newCall(request).enqueue(new Callback() { Override public void onFailure(Call call, IOException e) { // 网络不通在这里读缓存或提示用户 } Override public void onResponse(Call call, Response response) throws IOException { if (!response.isSuccessful()) { return; } String body response.body().string(); // body 是整段 JSON下一步做解析 } });enqueue的异步回调运行在 OkHttp 的线程池里不能直接操作 UI。response.body().string()只能调用一次调用后流就关闭了所以要先存到变量里再用。这个细节很容易踩后面第 5 章会说。3.3 JSON 解析与模型设计宁可字段冗余不要解析崩溃拿到 JSON 字符串后很多人第一反应是new Gson().fromJson(body, WeatherBean.class)。Gson 一键映射确实爽但接口偶尔会缺字段、多字段或者直接返回一个业务错误码。直接映射时缺的字段变成 null你的 UI 层一取值就空指针接口返回错误码时整个结构对不上Gson 直接抛异常。手写解析虽然啰嗦但每一步都能控制容错逻辑。先定义一个模型类字段按展示需要来设计public class WeatherModel { public String cityName; // 城市名 public String temp; // 当前温度 public String text; // 天气现象 public String updateTime; // 数据更新时间 }解析时用 org.json 包逐层取字段JSONObject root new JSONObject(body); if (root.getInt(code) ! 200) { // 接口返回业务错误比如 Key 无效或配额超限 return; } JSONObject now root.getJSONObject(now); WeatherModel model new WeatherModel(); model.temp now.optString(temp, --); model.text now.optString(text, 未知); model.updateTime now.optString(obsTime, );optString与getString的区别在于前者取不到字段时返回默认值后者直接抛异常。在解析外部 JSON 时一律用opt系列方法是减少崩溃最直接的手段。code ! 200这个分支一定要保留免费接口调用次数超限或 Key 配置错误时服务商返回的不是天气数据而是一个错误码 JSON不判断的话后面取now就直接抛异常。解析逻辑写完后建议单独抽一个WeatherParser类输入 JSON 字符串输出WeatherModel。这样之后写单元测试时你不需要启动模拟器就能验证解析逻辑对不对。4. 界面渲染与数据刷新让天气从“日志能看到”变成“界面上能看到”4.1 RecyclerView 搭三天预报适配器、ViewHolder 与空视图处理天气预报只显示一个当前温度太单薄常见做法是展示三天的天气最高温、最低温、天气现象。用 RecyclerView 加一个 CardView 样式的 item 就能实现比 LinearLayout 动态加 View 干净得多。item 布局item_weather.xml里放三个 TextView分别显示星期、温度范围、天气现象LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationhorizontal android:padding16dp TextView android:idid/tvWeek android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 / TextView android:idid/tvTempRange android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 android:gravitycenter / TextView android:idid/tvWeatherText android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 android:gravityend / /LinearLayoutlayout_weight1让三个文本等宽分布左右对齐边界中间居中这是列表项的常见排版方式。适配器写法记住一个骨架public class WeatherAdapter extends RecyclerView.AdapterWeatherAdapter.VH { private ListWeatherModel list new ArrayList(); public void setData(ListWeatherModel data) { list.clear(); list.addAll(data); notifyDataSetChanged(); } static class VH extends RecyclerView.ViewHolder { TextView tvWeek, tvTempRange, tvWeatherText; VH(View itemView) { super(itemView); tvWeek itemView.findViewById(R.id.tvWeek); tvTempRange itemView.findViewById(R.id.tvTempRange); tvWeatherText itemView.findViewById(R.id.tvWeatherText); } } Override public VH onCreateViewHolder(ViewGroup parent, int viewType) { View v LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_weather, parent, false); return new VH(v); } Override public void onBindViewHolder(VH holder, int position) { WeatherModel m list.get(position); if (m null) { return; } holder.tvWeek.setText(m.week); holder.tvTempRange.setText(m.tempMin °C / m.tempMax °C); holder.tvWeatherText.setText(m.text); } Override public int getItemCount() { return list.size(); } }setData里先 clear 再 addAll是为了避免旧数据残留导致列表出现重复项。onBindViewHolder里的空值判断虽然简单但能防止解析阶段某个字段为空导致列表项直接不显示。很多“界面不报错但列表空白”的问题根源就在适配器里没有空值保护。关于动态标题把当前城市名通过setTitle(cityName)设置到 ActionBar 上就是你搜到过的“小程序动态设置标题”的效果原生里一行代码的事。4.2 生命周期边界转屏、杀后台与网络回调的错位天气 App 最常见的崩溃场景是请求发出去了用户旋转屏幕Activity 销毁重建网络回调回来时拿着旧 Activity 的引用去更新 UI。轻则空指针重则内存泄漏。这个问题在课设代码里普遍存在因为教程很少讲异步回调与生命周期的关系。最直接的保护是在回调里检查 Activity 状态runOnUiThread(new Runnable() { Override public void run() { if (isFinishing() || isDestroyed()) { return; } adapter.setData(list); } });isFinishing()判断 Activity 是否正在关闭isDestroyed()判断是否已经销毁。两者都返回 false 才更新 UI。这个写法能挡住大部分崩溃但不是最优雅的方案ViewModel 加 LiveData 才是正路——那部分在第 6 章说。另一个生命周期问题是转屏时丢失状态。Activity 重建后用户选好的城市如果没保存会跳回默认城市。标准做法是重写onSaveInstanceStateOverride protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putString(city_id, currentCityId); }然后在onCreate里恢复if (savedInstanceState ! null) { currentCityId savedInstanceState.getString(city_id); }这样转屏后城市不会丢也就不用重新请求接口。注意把城市 ID 存下来而不是城市名因为 ID 才是请求参数。4.3 下拉刷新与缓存做好体验再谈功能天气数据不是高频变化的数据同一个城市 10 分钟内温度几乎不会变。但免费天气接口有调用次数限制调试时每次进页面都发请求很快就会把配额耗尽。所以缓存和刷新是一对组合拳必须一起做。用 SwipeRefreshLayout 包住 RecyclerView下拉时触发刷新androidx.swiperefreshlayout.widget.SwipeRefreshLayout android:idid/swipeRefresh android:layout_widthmatch_parent android:layout_heightmatch_parent androidx.recyclerview.widget.RecyclerView android:idid/recyclerView android:layout_widthmatch_parent android:layout_heightmatch_parent / /androidx.swiperefreshlayout.widget.SwipeRefreshLayout刷新逻辑只做一件事清空旧数据重新请求接口拿到结果后更新列表并写入缓存。缓存用 SharedPreferences 就够不用上数据库。存两项JSON 字符串和时间戳。SharedPreferences sp getSharedPreferences(weather_cache, MODE_PRIVATE); sp.edit().putString(json_ cityId, body) .putLong(time_ cityId, System.currentTimeMillis()) .apply();读取时机是进入页面或点击刷新时先看时间戳long lastTime sp.getLong(time_ cityId, 0); if (System.currentTimeMillis() - lastTime 30 * 60 * 1000) { // 30分钟内直接读缓存渲染不发请求 } else { // 超过30分钟才请求网络 }缓存时长设 30 分钟是天气类 App 的通用习惯。太短失去缓存意义太长用户会觉得数据不准。有读者问过“微信小程序设置缓存时间怎么配”这里提一下小程序里它有专门的 storage API原生 Android 里你直接控制 SharedPreferences 的时间戳即可思路完全一样不用额外配置。5. 避坑与常见问题排查模拟器 Starting up 之外这些坑才真正耗时间5.1 请求报 Cleartext HTTP traffic not permitted明文流量被系统拦住现象模拟器上运行 App点击刷新后界面没反应Logcat 里看到类似Cleartext HTTP traffic to devapi.qweather.com not permitted的异常。原因Android 9API 28开始系统默认禁止所有明文 HTTP 流量。如果你的天气接口走的是http://而不是https://无论代码写得多对请求都会在系统层被拦截。解决优先让接口走 HTTPS这是根治方案。如果接口只能走 HTTP比如某些教学用接口分两种做法。开发期图省事在 application 标签加android:usesCleartextTraffictrue但这等于对所有域名放开明文不合适。更规范的做法是用 networkSecurityConfig 只放行你请求的域名network-security-config domain-config cleartextTrafficPermittedtrue domain includeSubdomainstruedevapi.qweather.com/domain /domain-config /network-security-config在 application 标签里引用这份配置application android:networkSecurityConfigxml/network_security_config ...这样只有天气接口域名允许明文其它域名依旧默认禁止。注意这个配置要放在res/xml/目录下。5.2 模拟器一直 Starting up、运行后界面不显示先分清是环境还是代码现象AndroidStudio 的模拟器冷启动时卡在 “Starting up” 界面等了十几分钟还在转圈或者点 Run 后应用装上了但模拟器界面上看不到 App 窗口Logcat 里也没有崩溃日志。原因模拟器冷启动本来就慢尤其是电脑没有开启硬件加速时ARM 镜像在 x86 机器上跑更是雪上加霜。第一次创建 AVD 时如果选了和本机 CPU 架构不匹配的系统镜像启动时间会成倍拉长。至于“运行后不显示”多半是你启动的 Activity 没在 Manifest 里配置MAIN和LAUNCHER的 intent-filter或者安装到的是另一个设备。解决日常调试不要反复冷启动模拟器窗口开着就直接热启动。创建 AVD 时优先选 x86_64 镜像并确认 SDK Manager 里已经装好对应的系统镜像。不要花时间找 AndroidStudio 历史版本下载——环境问题九成不是版本旧导致的而是镜像和加速不匹配。真机调试永远是兜底方案手机开 USB 调试连上后直接点 Run比模拟器可靠得多。另外检查 Manifest 里 MainActivity 是否有这段intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter没有这段应用装上了也点不开。5.3 列表空白不报错JSON 字段名对不上Gson 静默返回 null现象请求成功Logcat 里能看到response.body().string()打印出了一大段 JSON但界面上 RecyclerView 一片空白没有异常没有崩溃。原因最常见的是 JSON 字段名和模型类字段名不一致。比如接口返回的是tempMax你模型里写的是temp_max接口返回的是obsTime你写的是obstime。Gson 在字段找不到时不报错只把对应字段留成 nullUI 层拿到 null 后什么都不显示就成了“静默空白”。解决先在onResponse里把完整 JSON 打出来确认实际字段名Log.d(WeatherJson, body);然后逐层核对。用optString解析时设置一个可见默认值比如温度默认--天气现象默认“未知”。这样即使某个字段缺失界面至少会显示占位符而不是空白。另一个容易忽略的地方是接口返回的是错误码 JSON 而不是天气数据比如{code:429,message:too many requests}这时root.getJSONObject(now)会直接抛异常所以必须先判断根节点的code是否为 200再往下取数据。5.4 API 配额被刷爆调试一小时免费额度就没了现象上午调试还正常下午再打开应用天气数据突然不刷新Logcat 里看到服务商返回的错误码变成 429 或 401 之类。原因免费天气接口的调用次数是按时段限量的不是无限用。调试阶段每启动一次 App 发一次请求每次下拉刷新又发一次一小时内发几十次很常见配额很快耗尽。这时服务商不再返回天气数据而是返回业务错误码。解决把缓存时长调长调试期建议设 30 分钟到 1 小时。进入页面先读缓存超过缓存时间才发请求。手动刷新按钮保留但刷新后也要把新数据写回缓存。另一个常见做法是注册多个 Key 轮换使用这只适合个人课设不要用在做正式交付的工程里。最本质的办法是把请求放到服务端转发客户端不直接持有 Key但这超出了一个课设项目的范围这里不展开。5.5 APK 装到真机一打开就闪退Activity 没注册或依赖冲突现象模拟器上跑得好好的打包 APK 发到真机点图标直接闪退有时连闪退都看不到。原因最常见的两个。一是写了新 Activity 但忘了在 Manifest 里注册Android 系统找不到对应组件启动即崩。二是依赖库版本冲突比如 support 库和 androidx 混用Gradle 依赖解析时没报错运行时才炸。解决看 Logcat 里的FATAL EXCEPTION段里面会直接指出是哪个类找不到或哪个方法报了空指针。定位到具体行后Activity 没注册的就补注册依赖冲突的在依赖声明里统一只用 androidx不要混用 support 库。另外检查compileOptions的 Java 版本是否和依赖库要求一致Gradle 里指定 8 或 11 都行但要一致不要一个模块一个版本。6. 验证与进阶把“能显示温度”的源码变成“可交付”的小工程6.1 解析逻辑用 JUnit 固定下来天气 App 这类项目最容易翻车的环节就是 JSON 解析因为接口变化、字段缺失、类型不符都会在这里暴露。我的习惯是给解析器写一个本地 JUnit 测试把一段模拟 JSON 字符串喂进去直接断言输出Test public void parseNormalJson_returnsModel() throws Exception { String json {\code\:200,\now\:{\temp\:\23\,\text\:\晴\}}; WeatherModel model WeatherParser.parse(json); assertEquals(23, model.temp); assertEquals(晴, model.text); }这个测试不依赖模拟器跑一次只要几秒。以后改了解析逻辑跑一遍测试就知道有没有破坏原有功能不用反复装 APK 去点界面。把时间花在能自动验证的地方是降低调试成本最划算的投入。6.2 从 Activity 到 MVVM值得动手的四个改进点工程跑通之后想往“可交付”的方向走一层优先动这四个地方把网络请求从 Activity 里抽到 Repository 层Activity 只调接口拿数据这样换天气服务商时不用动 UI 代码。引入 ViewModel 和 LiveData替代 Handler 和 runOnUiThread天然解决转屏导致的重复请求问题。用 WorkManager 做定时刷新每 30 分钟在后台静默更新一次缓存用户打开时永远有数据。加一个桌面 WidgetAppWidgetProvider 实现让天气直接显示在主屏上。这个功能加分效果明显实现难度不高。我自己的习惯是做完一个功能先写测试再装机点界面。刚开始觉得麻烦翻过几次车——尤其是列表空白这种不报错的问题排查半天最后发现只是字段名拼错——之后才明白自动化验证省下来的时间远比写测试花的时间多。一个天气小程序做到这里数据链路、生命周期、缓存、异常处理都过了一遍已经能当成一个完整的 Android 入门作品拿出手了。希望帮到你。本文还有配套的精品资源点击获取
返回列表