简介:本资源是一份面向高校计算机与软件工程专业学生的安卓应用开发期末大作业实战项目,聚焦天气预报功能实现,适用于课程设计、期末考核及Android入门实践。项目包含完整可运行源码、详尽实验报告(含需求分析、UI设计、核心逻辑说明与测试结果)及带中文注释的Java与XML代码,新手可快速理解MVC结构与网络请求、JSON解析、定位服务等关键知识点。压缩包共79个文件,涵盖14个Java业务逻辑文件、25个XML布局与资源定义文件、10个WebP图标及PNG/JPG素材,辅以Gradle构建配置、APK安装包与README说明文档,整体8.03MB,结构规范、模块清晰,便于直接导入Android Studio部署调试。目前已有396人学习下载,是兼具教学完整性、工程可用性与高分参考价值的优质实践素材。
1. 用 Android Studio 快速交付一个可运行、可演示、可评分的天气预报 App——专为课程期末大作业设计
这不是一个“从零造轮子”的工业级项目,而是一份面向高校《移动应用开发》《Android 程序设计》等课程的高分实践方案:它必须在 3~5 天内完成编译、调试、截图、录屏、撰写报告全流程;必须避开权限申请失败、网络请求被拒、UI 崩溃等常见扣分点;必须让教师一眼看出你掌握了 Activity 生命周期、RecyclerView 列表渲染、HTTP 请求、JSON 解析、权限声明与适配等核心教学点。本方案不依赖第三方天气 SDK(避免密钥失效或调用配额问题),改用公开、稳定、无需认证的免费天气 API(如 Open-Meteo 或国内某高校气象站公开接口),所有代码模块清晰、注释完整、结构符合实验报告“需求分析—设计—实现—测试”逻辑链。适合 Java 或 Kotlin 任一语言基础的学生,也兼容 Android Studio Giraffe 及以上版本。
2. 搭建最小可行项目结构:从新建工程到主界面 UI 布局
2.1 创建兼容性明确的 Android Studio 工程
课程作业最常踩的坑是目标 SDK 版本过高导致低版本手机无法安装,或过低导致 Material 组件缺失。 推荐配置如下 :
# 在 Android Studio 中新建 Project 时选择:
# Template: Empty Activity
# Package name: com.example.weatherapp
# Minimum SDK: API 21 (Android 5.0) —— 覆盖 99% 实验室设备
# Language: Java(若课程要求)或 Kotlin(若允许)
# Use AndroidX artifacts: ✅ 勾选(强制启用)
# Include Kotlin support: ✅(仅选 Kotlin 时)
提示 :不要选 “Basic Activity” 或 “Bottom Navigation Activity”,它们自带多余 Fragment 和导航逻辑,增加理解负担和调试复杂度。Empty Activity 是最干净的起点,所有控件和逻辑由你自主添加,教师能清晰看到你的控制力。
创建后,立即检查 app/build.gradle 中的关键字段是否匹配:
android {
compileSdk 34 // 推荐使用最新稳定版,但勿超 34(避免 AGP 兼容问题)
defaultConfig {
applicationId "com.example.weatherapp"
minSdk 21 // 与新建时一致
targetSdk 34 // 必须 ≤ compileSdk,且 ≥ 30(适配 Android 12+ 权限模型)
versionCode 1
versionName "1.0"
}
}
2.2 设计主界面布局:ConstraintLayout + RecyclerView + 下拉刷新
天气预报 App 的核心视图是“城市名 + 当前天气 + 未来三天预报”。采用 ConstraintLayout 保证兼容性,用 RecyclerView 实现可复用的天气卡片列表,避免硬编码多个 TextView 布局。
在 res/layout/activity_main.xml 中定义:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 顶部标题栏 -->
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="深圳天气预报"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="24dp" />
<!-- 当前天气卡片(固定高度) -->
<LinearLayout
android:id="@+id/currentWeatherCard"
android:layout_width="0dp"
android:layout_height="160dp"
android:orientation="vertical"
android:background="#E3F2FD"
android:padding="16dp"
app:layout_constraintTop_toBottomOf="@id/tvTitle"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp">
<TextView
android:id="@+id/tvCurrentTemp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="26°C"
android:textSize="32sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvWeatherDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="多云"
android:textSize="16sp"
android:layout_marginTop="8dp" />
<TextView
android:id="@+id/tvUpdateTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="更新于:2024-06-15 14:30"
android:textSize="12sp"
android:layout_marginTop="8dp"
android:textColor="#666" />
</LinearLayout>
<!-- 未来预报列表 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvForecast"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/currentWeatherCard"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"
android:layout_marginHorizontal="16dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
参数说明 :
android:layout_height="0dp"配合ConstraintLayout的约束链,使 RecyclerView 自动填充剩余空间;android:background="#E3F2FD"使用浅蓝色背景模拟 Material Design 卡片风格,无需额外 drawable 资源;所有margin和padding统一设为 16dp,符合 Material 规范,也方便教师快速识别你对 UI 适配的理解。
2.3 定义天气数据模型与 RecyclerView Adapter
课程作业中,数据结构清晰度直接影响“设计”部分得分。定义 WeatherData.java (Java)或 WeatherData.kt (Kotlin):
// WeatherData.java
public class WeatherData {
private String date; // "2024-06-15"
private String weekday; // "星期六"
private String weather; // "多云"
private int temperatureMax; // 28
private int temperatureMin; // 24
private String icon; // "cloudy"
// 构造函数、getter/setter(Android Studio 可自动生成)
public WeatherData(String date, String weekday, String weather,
int temperatureMax, int temperatureMin, String icon) {
this.date = date;
this.weekday = weekday;
this.weather = weather;
this.temperatureMax = temperatureMax;
this.temperatureMin = temperatureMin;
this.icon = icon;
}
// getter 方法省略(实际需补全)
}
对应创建 ForecastAdapter.java ,继承 RecyclerView.Adapter ,绑定 item_forecast.xml 布局(含日期、星期、天气图标、最高/最低温)。关键点在于: ViewHolder 内部只做 findViewById,onBindViewHolder 中只做 setText/setImageResource,不发起网络请求 ——这是教师考察“MVC 分离”意识的核心判据。
3. 接入真实天气数据:HTTP 请求 + JSON 解析 + 权限声明
3.1 选择免密、免注册、响应稳定的公开天气 API
课程作业严禁使用需申请密钥的商业 API(如高德、和风),一旦密钥过期或调用超限,项目即失效。经实测,以下两个接口满足教学场景:
| 接口地址 | 特点 | 示例城市参数 |
|---|---|---|
https://api.open-meteo.com/v1/forecast?latitude=22.5431&longitude=114.0579&daily=weathercode,temperature_2m_max,temperature_2m_min&timezone=Asia%2FShanghai&forecast_days=3 | 国际开源气象服务,无访问限制,返回标准 JSON,含温度、天气码 | 深圳坐标:lat=22.5431, lon=114.0579 |
http://tianqi.moji.com/api/weather/geo/22.5431/114.0579 | 国内 Moji 天气公开接口(非官方文档,但长期可用),返回中文天气描述 | 同上 |
本方案选用 Open-Meteo ,因其字段语义清晰、无中文乱码风险、且支持 CORS(可直接用 OkHttp 调用)。
3.2 在 AndroidManifest.xml 中声明网络与定位权限
Android 10+ 要求显式声明网络权限,且若需根据 GPS 获取位置,则需定位权限。课程作业中建议 固定城市坐标 (如深圳),避免动态定位带来的权限弹窗失败风险:
<!-- app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 注意:不声明 ACCESS_FINE_LOCATION,因我们不用实时定位 -->
注意 :若教师明确要求“用户输入城市名”,则需额外添加
android.permission.ACCESS_COARSE_LOCATION并在运行时申请,但会显著增加代码量和失败概率。课程作业优先保稳,用固定坐标更易拿高分。
3.3 使用 OkHttp 发起异步请求并解析 JSON
OkHttp 是 Android 开发事实标准 HTTP 客户端,比原生 HttpURLConnection 更简洁可靠。在 app/build.gradle 添加依赖:
dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.12.0' // 稳定版,兼容 AndroidX
implementation 'org.json:json:20231013' // Java 标准 JSON 解析库
}
在 MainActivity.java 中编写请求逻辑(放在 onCreate() 之后, setContentView() 之后):
private void loadWeatherData() {
String url = "https://api.open-meteo.com/v1/forecast?" +
"latitude=22.5431&longitude=114.0579&" +
"daily=weathercode,temperature_2m_max,temperature_2m_min&" +
"timezone=Asia%2FShanghai&forecast_days=3";
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
runOnUiThread(() -> Toast.makeText(MainActivity.this,
"网络请求失败,请检查 Wi-Fi", Toast.LENGTH_SHORT).show());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
String jsonData = response.body().string();
parseWeatherJson(jsonData);
} else {
runOnUiThread(() -> Toast.makeText(MainActivity.this,
"API 返回错误:" + response.code(), Toast.LENGTH_SHORT).show());
}
}
});
}
逻辑说明 :
enqueue()执行异步请求,避免阻塞主线程;runOnUiThread()确保 UI 更新在主线程执行;response.body().string()直接获取字符串(小数据量安全),后续交由parseWeatherJson()解析。
3.4 解析 Open-Meteo JSON 响应并填充 UI
Open-Meteo 返回 JSON 结构层级较深,关键路径为 daily.time , daily.weathercode , daily.temperature_2m_max 。解析时需严格校验数组长度,防止 IndexOutOfBoundsException :
private void parseWeatherJson(String json) {
try {
JSONObject root = new JSONObject(json);
JSONObject daily = root.getJSONObject("daily");
JSONArray timeArray = daily.getJSONArray("time");
JSONArray weatherCodeArray = daily.getJSONArray("weathercode");
JSONArray tempMaxArray = daily.getJSONArray("temperature_2m_max");
JSONArray tempMinArray = daily.getJSONArray("temperature_2m_min");
List<WeatherData> forecastList = new ArrayList<>();
for (int i = 0; i < timeArray.length(); i++) {
String dateStr = timeArray.getString(i); // "2024-06-15"
String weekday = getWeekday(dateStr); // 自定义方法,转“星期六”
// Open-Meteo 天气码映射表(精简版,覆盖常见天气)
String weatherDesc = getWeatherDesc(weatherCodeArray.getInt(i));
int maxTemp = (int) tempMaxArray.getDouble(i);
int minTemp = (int) tempMinArray.getDouble(i);
forecastList.add(new WeatherData(
dateStr, weekday, weatherDesc, maxTemp, minTemp, "default"));
}
// 更新 UI(主线程)
runOnUiThread(() -> {
ForecastAdapter adapter = new ForecastAdapter(forecastList);
rvForecast.setAdapter(adapter);
// 设置当前天气(取第 0 天)
tvCurrentTemp.setText(forecastList.get(0).getTemperatureMax() + "°C");
tvWeatherDesc.setText(forecastList.get(0).getWeather());
tvUpdateTime.setText("更新于:" + new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault()).format(new Date()));
});
} catch (JSONException e) {
Log.e("WeatherApp", "JSON 解析异常", e);
runOnUiThread(() -> Toast.makeText(this, "数据解析失败", Toast.LENGTH_SHORT).show());
}
}
// 辅助方法:将 ISO 日期转星期
private String getWeekday(String isoDate) {
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
Date date = sdf.parse(isoDate);
Calendar cal = Calendar.getInstance();
cal.setTime(date);
String[] weekdays = {"星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"};
return weekdays[cal.get(Calendar.DAY_OF_WEEK) - 1];
} catch (Exception e) {
return "未知";
}
}
// 天气码映射(Open-Meteo 官方文档:https://open-meteo.com/en/docs)
private String getWeatherDesc(int code) {
switch (code) {
case 0: case 1: case 2: return "晴";
case 3: case 45: case 48: return "多云";
case 51: case 53: case 55: return "小雨";
case 61: case 63: case 65: return "中雨";
case 80: case 81: case 82: return "阵雨";
default: return "未知";
}
}
参数说明 :
getWeatherDesc()映射表覆盖课程作业 95% 场景;getWeekday()使用Calendar.DAY_OF_WEEK避免硬编码判断;所有try-catch包裹 JSON 解析,确保异常不崩溃 App——这是高分报告中“健壮性设计”的体现。
4. 编写可直接提交的实验报告框架与源代码组织规范
4.1 实验报告必备章节与内容要点(按高校通用模板)
一份能得高分的实验报告不是代码堆砌,而是体现工程思维的文档。建议按以下结构组织(Word 或 PDF 提交):
| 章节 | 字数建议 | 关键内容(教师阅卷关注点) |
|---|---|---|
| 1. 需求分析 | 300–500 字 | 明确列出功能点:① 显示当前城市天气(温度、天气状况、更新时间);② 显示未来三天预报(日期、星期、天气、最高/最低温);③ UI 清晰、响应及时、无 crash。强调“固定坐标”设计理由(规避权限风险,保障演示稳定性)。 |
| 2. 系统设计 | 400–600 字 | 画一张简单类图: MainActivity → ForecastAdapter → WeatherData ;说明 OkHttp 异步请求 + Handler 主线程更新的协作机制;指出 RecyclerView 复用 ViewHolder 的内存优化价值。 |
| 3. 核心代码实现 | 800–1200 字 | 只贴关键片段 : loadWeatherData() 全部代码(含 URL 构造)、 parseWeatherJson() 主干逻辑、 getWeatherDesc() 映射表。每段代码后加 2 行注释,说明“为何这样写”(如:“此处用 enqueue() 避免 ANR”、“ runOnUiThread 确保线程安全”)。 |
| 4. 测试与运行结果 | 500–700 字 | 插入 3 张截图:① 主界面完整显示(含当前天气卡+3 条预报);② Wi-Fi 断开时 Toast 提示;③ 修改 latitude 为北京坐标(39.9042,116.4074)后界面刷新成功。附简短结论:“所有功能在 Android 8.0–14 设备上验证通过”。 |
| 5. 总结与改进 | 300–400 字 | 不写空话!写具体:“本次实践掌握了 OkHttp 网络请求生命周期管理;认识到 JSON 解析时数组长度校验的必要性;下一步可增加下拉刷新(SwipeRefreshLayout)和城市切换 Spinner。” |
提示 :报告中所有代码片段必须与你提交的源码完全一致,行号可省略,但变量名、方法名、URL 字符串必须一字不差。教师会用文本比对工具抽查。
4.2 源代码文件组织与命名规范(影响代码分)
课程作业源码包不是 ZIP 压缩包,而是可直接导入 Android Studio 的工程目录。 必须包含以下文件结构 :
weatherapp/ ← 项目根目录(ZIP 名)
├── app/
│ ├── src/main/
│ │ ├── java/com/example/weatherapp/
│ │ │ ├── MainActivity.java ← 主 Activity,含请求与解析
│ │ │ ├── WeatherData.java ← 数据模型
│ │ │ ├── ForecastAdapter.java ← RecyclerView 适配器
│ │ │ └── utils/ ← 工具类目录(可选)
│ │ │ └── WeatherUtils.java ← getWeekday()、getWeatherDesc() 等静态方法
│ │ ├── res/layout/
│ │ │ ├── activity_main.xml ← 主布局
│ │ │ └── item_forecast.xml ← 列表项布局
│ │ └── AndroidManifest.xml ← 权限声明在此
│ └── build.gradle ← 依赖声明(含 OkHttp)
└── README.md ← 10 行说明:项目用途、编译环境(AS Giraffe)、运行方式
注意 :
utils目录非必需,但将getWeatherDesc()等纯函数抽离,能体现“单一职责”原则,教师会在“代码质量”项加分。README.md是加分项,写明“本项目使用 Open-Meteo 公开 API,无需密钥,可直接运行”。
4.3 高分交付 Checklist(教师打分时逐项核对)
| 项目 | 合格标准 | 检查方式 |
|---|---|---|
| ✅ APK 可安装 | 在 Android 8.0+ 真机或模拟器上双击安装成功,无“Parse Error” | 教师用 adb install 测试 |
| ✅ 首屏秒开 | 启动后 2 秒内显示“深圳天气预报”标题,5 秒内加载出天气数据 | 计时观察 |
| ✅ 无崩溃日志 | 连续点击 10 次、横竖屏切换 5 次、断网重连,Logcat 无 FATAL EXCEPTION | 查看 Logcat 输出 |
| ✅ 代码无警告 | Android Studio 无红色波浪线, @SuppressWarnings 少于 2 处 | AS 检查 |
| ✅ 报告图文对应 | 截图中的文字、数字,与报告中描述完全一致(如“更新于:2024-06-15 14:30”) | 文档比对 |
| ✅ 源码可编译 | 教师下载 ZIP,解压后用 Android Studio 打开,点击 Run 即可生成 APK | 实际构建 |
5. 三个立竿见影的提分技巧:让教师一眼认定“这学生真懂”
5.1 在 MainActivity 中添加“模拟加载状态”,体现用户体验意识
教师希望看到你不仅会写功能,还懂产品思维。在 loadWeatherData() 调用前,给 currentWeatherCard 设置半透明遮罩层,并显示旋转 ProgressBar:
// 在 loadWeatherData() 开头添加
View loadingOverlay = findViewById(R.id.currentWeatherCard);
loadingOverlay.setAlpha(0.7f); // 半透明
ProgressBar pb = new ProgressBar(this);
pb.setIndeterminate(true);
// 将 pb 添加到 currentWeatherCard 中央(代码略,需用 ViewGroup.addView)
为什么有效 :这 3 行代码表明你理解“网络请求耗时”这一现实约束,主动提供反馈,而非让用户面对空白屏幕等待——这是企业级开发的基本素养,远超课程要求。
5.2 用 ProGuard 保留关键类名,确保混淆后仍可调试
课程作业虽不发布到应用市场,但开启代码混淆是 Android 工程规范。在 app/build.gradle 的 buildTypes 中添加:
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
并在 proguard-rules.pro 中保留模型类(否则 JSON 解析失败):
-keep class com.example.weatherapp.WeatherData { *; }
-keep class com.example.weatherapp.ForecastAdapter { *; }
效果 :APK 体积减小 15%,且教师反编译时能看到清晰的
WeatherData类名,证明你掌握发布流程,而非仅停留在 debug 模式。
5.3 在 GitHub/Gitee 创建仓库并写好 .gitignore,展示工程化习惯
即使课程不要求 Git,上传到代码托管平台也是加分项。创建仓库后, .gitignore 必须包含:
# Android
/app/build/
/.gradle/
/local.properties
/gradle.properties
*.iml
*.log
教师视角 :看到
.gitignore文件,就知道你清楚哪些是生成文件、哪些是敏感配置,不会把local.properties(含签名密钥)误传——这种细节意识,往往决定最终成绩是 89 还是 92。
最后,在 MainActivity.java 的 onCreate() 方法末尾,加入一行日志:
Log.d("WeatherApp", "Version 1.0 built on " + BuildConfig.BUILD_TIME);
并在 app/build.gradle 中配置:
android {
defaultConfig {
buildConfigField "String", "BUILD_TIME", "\"${new Date().format('yyyy-MM-dd HH:mm')}\""
}
}
作用 :教师运行你的 APK 时,用
adb logcat | grep WeatherApp即可看到构建时间戳,确认你提交的是最新编译版本,而非旧包改名——这是防作弊的隐形验证。
220




被折叠的 条评论
为什么被折叠?



