ARTICLE DETAIL

资讯详情

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

Android天气预报App课设实战:从源码解析到答辩高分技巧

Android天气预报App课设实战:从源码解析到答辩高分技巧 简介这份资源是面向Android初学者与课程设计需求者的完整天气预报App项目源码基于Android Studio开发可直接导入运行适合作为期末大作业或练手项目。项目通过中国天气网API以HTTP方式获取网络数据使用JSON解析后展示到界面并用数据库存储城市及其对应URL实现城市收藏与天气切换功能覆盖网络请求、数据解析、本地存储与界面交互等核心知识点。压缩包共107个文件约6.75MB其中15个java文件承载主要业务逻辑24个xml文件负责布局与配置53个png为界面图片资源另含gradle构建脚本与pinyin4j等依赖jar包结构完整、层次清晰。目前已有532人学习下载读者可借此获得一套可运行的课设级方案对照源码理解Android网络编程与数据库操作流程快速完成项目搭建与功能扩展。1. 从一份天气预报 App 源码说起课设拿满分到底靠什么每年期末总有人拿着「Android Studio 实现天气预报 App」这个题目来找我问的都是同一件事源码能不能直接跑、跑起来能不能交。先说结论——能跑但能不能拿高分取决于你有没有真正理解这份源码里那几个关键决策。天气预报 App 是 Android 课设里最经典的选题之一因为它同时踩中了网络请求、JSON 解析、列表展示、定位权限、本地缓存这几个核心知识点老师一眼就能看出你是自己写的还是复制粘贴的。我见过太多人把源码导入 Android Studio 之后改个包名就交上去结果答辩时被问「你这个城市列表是怎么缓存的」「网络请求失败怎么处理的」直接卡壳。这篇不是教你复制一份源码交差而是把这份天气预报 App 从架构到实现拆开让你知道每一行代码为什么这么写、参数怎么调、哪里最容易翻车。适合正在做 Android 课设的学生也适合刚接触 Android 开发想找一个完整项目练手的人。读完你拿到的不只是一份能跑的源码而是一套能讲清楚、能改、能扩展的完整方案。2. 天气预报 App 的技术选型为什么不用 Retrofit MVVM2.1 课设场景下的技术栈取舍逻辑很多教程一上来就推 Retrofit OkHttp MVVM Hilt这套组合在生产环境确实好用但放在课设里就是给自己找麻烦。原因很直接老师检查的是你对 Android 基础组件的理解不是你对第三方库的熟练度。一份天气预报 App 源码如果用了五六个第三方依赖答辩时老师问「这个数据怎么从网络到界面的」你回答「Retrofit 自动帮我解析的」这个回答在课设场景下是减分的。我一般建议课设项目用 HttpURLConnection 原生 JSONObject 解析 ListView/RecyclerView 的组合。听起来原始但好处是每一层你都能讲清楚。网络请求用 HttpURLConnection 意味着你要自己处理输入流、自己拼 URL、自己判断响应码这些恰恰是 Android 网络编程的核心考点。JSON 解析用 org.json 包自带的 JSONObject 和 JSONArray不引入 Gson虽然代码量多一些但每一步解析逻辑都是透明的。当然如果你想让项目看起来更「现代」一点可以用 OkHttp 替代 HttpURLConnection但不要再往上加 Retrofit 了。OkHttp 的用法足够简单老师也认而且它帮你处理了连接池和重试代码量能少三分之一。下面这张表是我带过的几届学生里不同技术组合的答辩通过率和被追问次数统计技术组合代码量答辩被追问次数适合场景HttpURLConnection JSONObject多少都能答稳妥拿分OkHttp JSONObject中少想省事又不想被问倒Retrofit Gson MVVM少多容易卡壳有 Android 基础想冲高分Volley Gson中中不推荐Volley 已过时选 OkHttp 的话在build.gradle里加一行依赖就行// app/build.gradle dependencies { implementation com.squareup.okhttp3:okhttp:4.12.0 implementation com.google.android.material:material:1.11.0 implementation androidx.recyclerview:recyclerview:1.3.2 }这里注意版本号不要照抄用 Android Studio 提示的最新稳定版即可。OkHttp 4.x 和 3.x 的 API 有差异4.x 用 Kotlin 重写了但 Java 调用方式基本兼容。如果你用的是 Android Studio Dolphin 或更早版本Gradle 插件版本可能不匹配同步失败的话把compileSdk和targetSdk统一改成 34 试试。2.2 天气数据源的选型与接口参数天气数据从哪来这是整个项目最关键的决策。常见的选择有三个和风天气、心知天气、OpenWeatherMap。我推荐和风天气原因是它的免费版够用、中文文档友好、返回的 JSON 结构清晰而且不需要信用卡注册。注册之后你会拿到一个 API Key这个 Key 要放在请求 URL 里。和风天气的实时天气接口格式是这样的https://devapi.qweather.com/v7/weather/now?location101010100key你的APIKey其中location参数是城市 ID不是城市名。城市 ID 需要另外调一个接口获取或者直接在和风天气的官网上查。北京是101010100上海是101020100广州是101280101。这个细节很多人会忽略直接传「北京」进去结果返回错误码 400。返回的 JSON 结构大概长这样{ code: 200, updateTime: 2024-01-15T10:3008:00, now: { temp: 5, feelsLike: 2, text: 晴, windDir: 西北风, windScale: 3, humidity: 45 } }解析的时候注意code字段是字符串200不是数字200用jsonObject.getString(code)而不是getInt。这个坑我见过至少五个人踩过报错信息是JSONException: Value 200 at code of type java.lang.String cannot be converted to int一看就知道问题在哪但第一次遇到确实会懵。注意和风天气的免费版每天有请求次数限制开发阶段建议在本地做缓存不要每次打开 App 都请求一遍。缓存策略后面会讲。3. 从零跑通天气预报 App环境配置与核心代码实现3.1 Android Studio 环境准备与项目导入拿到一份天气预报 App 源码压缩包之后第一步不是直接 Open而是先看build.gradle文件里的 Gradle 版本和 Android Gradle Plugin 版本。如果源码是用 Android Studio Dolphin 创建的你本地装的是最新版 Android Studio直接打开大概率会提示升级 Gradle点确认之后等它下载就行但国内网络下载 Gradle 经常超时。我的做法是先看源码根目录的gradle/wrapper/gradle-wrapper.properties文件找到distributionUrl那一行把版本号记下来。然后打开 Android Studio新建一个空项目确保新项目能正常编译再把源码的app/src/main目录下的内容复制过去。这样做的原因是新项目的 Gradle 配置和你的 Android Studio 版本是匹配的省去了改版本号的麻烦。复制的时候注意这几个目录app/src/main/java/你的包名/ - 所有 Java 文件 app/src/main/res/layout/ - 所有布局文件 app/src/main/res/drawable/ - 图标和背景资源 app/src/main/res/values/ - strings.xml、colors.xml app/src/main/AndroidManifest.xml - 权限和 Activity 注册复制完之后把 Java 文件里的包名改成你自己的包名。Android Studio 里选中包名目录右键 Refactor - Rename输入新包名勾选 Search in comments and strings让它自动改。AndroidManifest.xml 里必须加网络权限和定位权限uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION /如果 App 需要定位当前城市还要在application标签里配置和风天气的 SDK 或者用 Android 原生的 LocationManager。课设项目建议用原生 LocationManager虽然代码多一些但不需要额外引入依赖答辩时也好讲。3.2 网络请求与 JSON 解析的完整代码这是整个项目的核心部分。我用 OkHttp 写一个完整的请求和解析流程你可以直接抄到自己的项目里。// WeatherNetwork.java public class WeatherNetwork { private static final String TAG WeatherNetwork; private static final String API_KEY 你的和风天气APIKey; private static final String BASE_URL https://devapi.qweather.com/v7/weather/now; // 回调接口把结果传回主线程 public interface WeatherCallback { void onSuccess(WeatherBean weather); void onFailure(String errorMsg); } public static void fetchWeather(String cityId, WeatherCallback callback) { // 拼接请求 URLlocation 参数传城市 ID String url BASE_URL ?location cityId key API_KEY; OkHttpClient client new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) // 连接超时 10 秒 .readTimeout(10, TimeUnit.SECONDS) // 读取超时 10 秒 .build(); Request request new Request.Builder() .url(url) .get() .build(); // 异步请求enqueue 内部自动开子线程 client.newCall(request).enqueue(new Callback() { Override public void onFailure(Call call, IOException e) { // 网络失败回调到主线程 callback.onFailure(网络请求失败 e.getMessage()); } Override public void onResponse(Call call, Response response) throws IOException { if (!response.isSuccessful()) { callback.onFailure(服务器返回错误 response.code()); return; } String json response.body().string(); try { JSONObject root new JSONObject(json); String code root.getString(code); // 注意是 String if (!200.equals(code)) { callback.onFailure(接口返回错误码 code); return; } JSONObject now root.getJSONObject(now); WeatherBean bean new WeatherBean(); bean.setTemp(now.getString(temp)); bean.setFeelsLike(now.getString(feelsLike)); bean.setText(now.getString(text)); bean.setWindDir(now.getString(windDir)); bean.setWindScale(now.getString(windScale)); bean.setHumidity(now.getString(humidity)); callback.onSuccess(bean); } catch (JSONException e) { callback.onFailure(JSON 解析失败 e.getMessage()); } } }); } }这段代码有几个关键点需要说明。第一enqueue是异步方法OkHttp 内部会开子线程执行网络请求所以你不能在onResponse里直接更新 UI必须通过回调把数据传回主线程。第二response.body().string()只能调用一次调用两次会报IllegalStateException因为响应流已经被消费了。第三超时时间设 10 秒是经验值太短了弱网环境容易失败太长了用户等不及。对应的 WeatherBean 类就是一个普通的 Java Bean// WeatherBean.java public class WeatherBean { private String temp; private String feelsLike; private String text; private String windDir; private String windScale; private String humidity; // getter 和 setter 省略用 Android Studio 自动生成 }在 Activity 里调用的时候注意回调是在子线程执行的更新 UI 必须切回主线程// MainActivity.java WeatherNetwork.fetchWeather(101010100, new WeatherNetwork.WeatherCallback() { Override public void onSuccess(WeatherBean weather) { runOnUiThread(() - { tvTemp.setText(weather.getTemp() °C); tvText.setText(weather.getText()); tvWind.setText(weather.getWindDir() weather.getWindScale() 级); tvHumidity.setText(湿度 weather.getHumidity() %); }); } Override public void onFailure(String errorMsg) { runOnUiThread(() - { Toast.makeText(MainActivity.this, errorMsg, Toast.LENGTH_SHORT).show(); }); } });runOnUiThread是 Activity 的方法在 Fragment 里要用getActivity().runOnUiThread。这个细节在课设答辩时经常被问到因为老师想确认你知不知道 Android 的线程模型。3.3 城市列表与本地缓存的实现天气预报 App 通常需要支持多个城市切换。城市列表的数据结构建议用ArrayListCityBean每个 CityBean 包含城市名和城市 ID。数据来源可以写死在代码里也可以从本地 JSON 文件读取。课设项目建议写一个cities.json放在assets目录下启动时读取一次这样显得更规范。// 从 assets 读取城市列表 private ListCityBean loadCitiesFromAssets() { ListCityBean cityList new ArrayList(); try { InputStream is getAssets().open(cities.json); int size is.available(); byte[] buffer new byte[size]; is.read(buffer); is.close(); String json new String(buffer, StandardCharsets.UTF_8); JSONArray array new JSONArray(json); for (int i 0; i array.length(); i) { JSONObject obj array.getJSONObject(i); CityBean city new CityBean(); city.setName(obj.getString(name)); city.setId(obj.getString(id)); cityList.add(city); } } catch (IOException | JSONException e) { e.printStackTrace(); } return cityList; }缓存策略用 SharedPreferences 就够了。每次请求成功之后把 JSON 字符串存进去下次打开 App 先读缓存显示再发请求更新。这样即使网络不好用户也能看到上次的天气数据。// 保存缓存 SharedPreferences sp getSharedPreferences(weather_cache, MODE_PRIVATE); sp.edit().putString(last_weather, json).apply(); // 读取缓存 String cached sp.getString(last_weather, null); if (cached ! null) { // 先解析缓存显示 }SharedPreferences 的apply()是异步提交commit()是同步提交。UI 线程里用apply()不要用commit()否则可能阻塞主线程导致掉帧。4. 天气预报 App 开发中最容易翻车的五个坑4.1 网络请求在主线程执行导致 NetworkOnMainThreadException现象App 一打开就崩溃Logcat 里报android.os.NetworkOnMainThreadException。原因Android 4.0 之后禁止在主线程执行网络请求而很多初学者直接把HttpURLConnection的connect()写在onCreate里。解决把网络请求放到子线程或者用 OkHttp 的enqueue异步方法。如果非要用HttpURLConnection就开一个Thread或者用AsyncTask虽然已废弃但课设还能用。我一般直接用 OkHttp 的enqueue省事。4.2 JSON 解析时字段类型不匹配导致 JSONException现象网络请求成功了但解析的时候抛异常提示Value xxx at xxx of type java.lang.String cannot be converted to int。原因和风天气返回的code、temp等字段都是字符串类型但代码里用了getInt去取。解决统一用getString取出来需要数字的时候再Integer.parseInt。如果某个字段可能为空用optString(field, 默认值)代替getString避免JSONException。4.3 忘记加 INTERNET 权限导致 UnknownHostException现象请求一直失败报java.net.UnknownHostException: Unable to resolve host。原因AndroidManifest.xml 里没有声明android.permission.INTERNET权限。解决在manifest标签下、application标签上面加一行uses-permission android:nameandroid.permission.INTERNET /。这个坑最气人的地方在于它不报权限错误而是报域名解析失败让你以为是网络问题。4.4 在 Fragment 里用 getActivity() 返回 null 导致空指针现象切换城市之后Fragment 里的网络回调执行时崩溃报NullPointerException。原因网络请求还没返回用户就切走了 FragmentgetActivity()返回 null。解决在回调里先判断isAdded()和getActivity() ! null再更新 UI。或者用ViewLifecycleOwner配合 LiveData但课设项目用简单的判空就够了。if (isAdded() getActivity() ! null) { getActivity().runOnUiThread(() - { // 更新 UI }); }4.5 城市 ID 传错导致接口返回 400现象请求返回{code:400}天气数据为空。原因location参数传了城市名而不是城市 ID或者城市 ID 拼写错误。解决在和风天气官网的「城市列表」页面查对应的 Location ID北京是101010100上海是101020100。如果要做定位功能用LocationManager拿到经纬度之后调和风天气的 GeoAPI 反查城市 ID不要自己猜。5. 让天气预报 App 从及格到优秀的三个进阶技巧5.1 用 RecyclerView 替代 ListView 做城市列表ListView 是课设里最常用的列表控件但 RecyclerView 才是现在的主流。把 ListView 换成 RecyclerView答辩时老师会觉得你对新技术有了解。改造步骤不复杂在build.gradle里加androidx.recyclerview:recyclerview依赖把布局文件里的ListView换成androidx.recyclerview.widget.RecyclerView然后写一个CityAdapter继承RecyclerView.Adapter。// CityAdapter.java public class CityAdapter extends RecyclerView.AdapterCityAdapter.ViewHolder { private ListCityBean cityList; private OnItemClickListener listener; public interface OnItemClickListener { void onItemClick(CityBean city); } public CityAdapter(ListCityBean cityList, OnItemClickListener listener) { this.cityList cityList; this.listener listener; } NonNull Override public ViewHolder onCreateViewHolder(NonNull ViewGroup parent, int viewType) { View view LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_city, parent, false); return new ViewHolder(view); } Override public void onBindViewHolder(NonNull ViewHolder holder, int position) { CityBean city cityList.get(position); holder.tvCityName.setText(city.getName()); holder.itemView.setOnClickListener(v - listener.onItemClick(city)); } Override public int getItemCount() { return cityList.size(); } static class ViewHolder extends RecyclerView.ViewHolder { TextView tvCityName; ViewHolder(View itemView) { super(itemView); tvCityName itemView.findViewById(R.id.tv_city_name); } } }RecyclerView 比 ListView 多了一个LayoutManager的概念在 Activity 里要设置recyclerView.setLayoutManager(new LinearLayoutManager(this))。这个改动大概花半小时但效果很明显。5.2 加一个下拉刷新让 App 看起来更完整下拉刷新用SwipeRefreshLayout包裹 RecyclerView 就行。在布局文件里androidx.swiperefreshlayout.widget.SwipeRefreshLayout android:idid/swipe_refresh android:layout_widthmatch_parent android:layout_heightmatch_parent androidx.recyclerview.widget.RecyclerView android:idid/recycler_view android:layout_widthmatch_parent android:layout_heightmatch_parent / /androidx.swiperefreshlayout.widget.SwipeRefreshLayout然后在 Activity 里设置监听swipeRefresh.setOnRefreshListener(() - { // 重新请求天气数据 fetchWeather(currentCityId); // 请求完成后调用 swipeRefresh.setRefreshing(false); });注意setRefreshing(false)必须在数据更新之后调用否则刷新圈一直转。如果请求失败也要记得关掉刷新圈不然用户会以为卡死了。5.3 用 Lottie 加一个天气动画提升视觉效果Lottie 是 Airbnb 开源的动画库可以把 JSON 格式的动画直接渲染到 Android 上。天气 App 加一个晴天、雨天、阴天的动画视觉效果提升非常明显。在build.gradle里加依赖implementation com.airbnb.android:lottie:6.3.0在布局文件里加LottieAnimationViewcom.airbnb.android.lottie.LottieAnimationView android:idid/lottie_weather android:layout_width200dp android:layout_height200dp app:lottie_autoPlaytrue app:lottie_looptrue app:lottie_rawResraw/sunny /raw/sunny是你放在res/raw目录下的 JSON 动画文件。Lottie 的动画文件可以从 LottieFiles 网站下载选免费的就够用。根据天气状况切换不同的动画private void updateWeatherAnimation(String weatherText) { if (weatherText.contains(晴)) { lottieWeather.setAnimation(R.raw.sunny); } else if (weatherText.contains(雨)) { lottieWeather.setAnimation(R.raw.rainy); } else if (weatherText.contains(阴) || weatherText.contains(云)) { lottieWeather.setAnimation(R.raw.cloudy); } lottieWeather.playAnimation(); }这个功能加进去答辩的时候老师大概率会问「这个动画怎么做的」你回答「Lottie 加载 JSON 动画」简洁明了。5.4 验证与调试怎么确认你的 App 真的没问题写完代码之后不要只在模拟器上跑一遍就交。我一般会做这几项检查第一把手机开飞行模式打开 App看缓存数据能不能正常显示错误提示是不是友好。第二切换城市之后快速返回看会不会崩溃。第三在弱网环境用 Android Studio 的模拟器网络延迟设置下测试看超时处理是否合理。第四检查 Logcat 里有没有StrictMode的警告如果有主线程磁盘读写趁早改掉。# 用 adb 查看 App 的崩溃日志 adb logcat -s AndroidRuntime:E # 清除 App 数据模拟首次安装 adb shell pm clear com.your.package.name这两条命令在调试阶段非常有用。第一条只看崩溃日志过滤掉其他噪音。第二条清除 App 的所有数据包括 SharedPreferences用来测试首次启动的流程。说实话我带过的学生里凡是认真把上面这些坑都踩过一遍的最后答辩都是优秀。那些直接交源码的老师随便问两个问题就露馅了。天气预报 App 这个题目不难但细节特别多网络请求、JSON 解析、线程切换、缓存策略、权限管理每一个点都能展开问。你把这篇里的代码跑通把五个坑都避开再挑一个进阶技巧加上去这份课设就稳了。希望帮到你。本文还有配套的精品资源点击获取
返回列表