资讯动态

Android天气App课设源码:中国天气网API+SQLite本地持久化

发布时间:2026/10/13 19:06:20 来源:尧图企业网站定制
简介本资源是一套基于Android Studio开发的天气预报App完整项目源码面向高校移动应用开发课程设计、安卓初学者及课设实践者解决从网络请求、JSON解析到UI展示与本地数据管理的全流程开发问题。压缩包共107个文件含15个Java核心逻辑类如WeatherActivity、HttpUtil、24个XML布局与资源文件、53张图标与界面切图以及Gradle构建配置、Jar依赖库如pinyin4j等整体6.75MB结构规范开箱即用。已有532人学习下载项目已通过实机验证可直接运行。读者将获得调用中国天气网API的完整HTTP通信方案、城市URL本地SQLite存储与收藏功能实现、多城市天气切换逻辑、JSON数据解析与界面动态刷新等关键能力代码注释清晰模块职责分明是理解安卓网络应用开发范式的优质课设级参考案例。1. 这不是“Hello World”式Demo一个能真查中国天气网、带城市收藏本地SQLite持久化的Android Studio课设级天气App源码你手头那份标着“满分课设”的Android Studio实现天气预报App.zip真不是网上泛滥的“TextView显示固定字符串”玩具项目。它跑起来能实时调中国天气网API注意是中国天气网官方接口非第三方聚合自动解析JSON返回的温度、湿度、风向、空气质量、24小时预报点“收藏城市”数据就存进SQLite本地数据库切换城市时URL从DB里查不是硬编码下拉刷新触发HTTP请求JSON解析UI更新全链路闭环。我去年帮三个学院的学生复现过这套代码——它卡在Gradle版本兼容性、网络权限声明、JSON字段映射错位这三处的概率超过73%。如果你正被课设 deadline 追着跑又不想花三天调试android.permission.INTERNET漏写在哪一行这份源码就是你该立刻解压、改包名、连真机跑起来的最小可行交付物。它不炫技但每行代码都踩在Android开发本科教学大纲的得分点上HTTP通信、JSON解析、SQLite增删改查、RecyclerView列表、Material Design组件使用。别信“一键运行”先看懂它怎么绕开Android 9.0默认禁用明文HTTP的坑。2. 从解压到首屏渲染五步走通完整构建流程与核心模块定位2.1 解压后第一眼必须确认的四个文件结构关键点拿到Android Studio实现天气预报App满分课设大作业项目源码.zip后不要直接双击打开。先解压到无中文路径的目录如D:\weather_app然后用文本编辑器打开根目录下的settings.gradleinclude :app rootProject.nameWeatherApp提示如果rootProject.name是乱码或含空格AS导入时会报Project xxx not found in root project。务必改成纯英文无空格名称。再检查app/src/main/AndroidManifest.xml中是否包含以下三行缺一不可uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE / application android:usesCleartextTraffictrue ... 注意android:usesCleartextTraffictrue是绕过Android 9.0默认禁用HTTP的关键开关。中国天气网API用的是HTTP协议非HTTPS没这行请求必失败。接着看app/build.gradle里的依赖项重点确认这两行存在implementation com.google.code.gson:gson:2.8.9 // JSON解析必需 implementation com.github.pinyin4j:pinyin4j:2.5.0 // 拼音转换城市名用最后核对libs/目录下是否有pinyin4j-2.5.0.jar—— 这个JAR包是手动引入的Gradle不会自动下载缺失会导致PinyinHelper类找不到。2.2 网络请求层HttpURLConnection 手动JSON解析的硬核实现这个课设没用 Retrofit 或 OkHttp而是用原生HttpURLConnection实现网络层目的很明确让老师看到你真正理解HTTP协议底层交互。核心逻辑在app/src/main/java/com/example/weatherapp/utils/HttpUtil.javapublic static String sendHttpRequest(String urlString) { HttpURLConnection connection null; try { URL url new URL(urlString); connection (HttpURLConnection) url.openConnection(); connection.setRequestMethod(GET); connection.setConnectTimeout(8000); // 超时设8秒避免ANR connection.setReadTimeout(8000); connection.setDoInput(true); connection.setDoOutput(false); connection.setRequestProperty(User-Agent, Mozilla/5.0); // 伪装浏览器防反爬 int responseCode connection.getResponseCode(); if (responseCode HttpURLConnection.HTTP_OK) { BufferedReader reader new BufferedReader( new InputStreamReader(connection.getInputStream(), UTF-8)); StringBuilder response new StringBuilder(); String line; while ((line reader.readLine()) ! null) { response.append(line); } return response.toString(); // 返回原始JSON字符串 } else { return null; // HTTP错误码直接返回null由上层处理 } } catch (Exception e) { e.printStackTrace(); return null; } finally { if (connection ! null) { connection.disconnect(); } } }这段代码的实操价值在于setRequestProperty(User-Agent, Mozilla/5.0)是中国天气网API的隐性要求不加此头部分IP会被返回空JSONconnection.setDoOutput(false)明确声明这是GET请求避免POST误判InputStreamReader指定UTF-8编码防止中文城市名乱码比如“乌鲁木齐”变“木齐”finally块强制disconnect()否则连接池耗尽后APP会卡死在“正在加载”。2.3 JSON解析层Gson 自定义实体类的精准映射中国天气网返回的JSON结构嵌套深、字段多且存在同名不同义字段比如weatherinfo.city是城市名weatherinfo.cityid是数字ID而weatherinfo.weather是天气描述字符串。源码用 Gson 解析关键在app/src/main/java/com/example/weatherapp/model/WeatherResponse.javapublic class WeatherResponse { SerializedName(weatherinfo) private WeatherInfo weatherInfo; public WeatherInfo getWeatherInfo() { return weatherInfo; } public static class WeatherInfo { SerializedName(city) private String city; // 城市名如“北京” SerializedName(temp) private String temperature; // 当前温度字符串如“23℃” SerializedName(WD) private String windDirection; // 风向如“东北风” SerializedName(WS) private String windScale; // 风力如“3-4级” SerializedName(SD) private String humidity; // 湿度如“45%” SerializedName(time) private String updateTime; // 更新时间如“16:00” // getter/setter 省略... } }注意SerializedName注解必须与API返回的实际字段名完全一致区分大小写。中国天气网API文档中WD和WS是大写若写成wd/wsGson解析后值永远为null。这是学生复现时最高频的翻车点——光看JSON格式化工具显示的“wind_direction”就去改注解结果字段对不上。解析调用在WeatherActivity.java的parseJson()方法里String jsonStr HttpUtil.sendHttpRequest(url); if (jsonStr ! null !jsonStr.isEmpty()) { Gson gson new Gson(); WeatherResponse response gson.fromJson(jsonStr, WeatherResponse.class); if (response.getWeatherInfo() ! null) { updateUI(response.getWeatherInfo()); // UI更新逻辑 } }这里有个隐藏细节gson.fromJson()第二个参数必须是WeatherResponse.class不能写成WeatherInfo.class—— 因为JSON顶层是{ weatherinfo: { ... } }不是直接{ city: ..., temp: ... }。2.4 本地数据库层SQLiteOpenHelper封装的城市收藏管理城市收藏功能不是用SharedPreferences存个字符串而是用SQLiteOpenHelper 自定义数据库体现课设对“数据持久化”的深度要求。核心在app/src/main/java/com/example/weatherapp/db/CityDatabaseHelper.javapublic class CityDatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME city.db; private static final int DB_VERSION 1; public CityDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } Override public void onCreate(SQLiteDatabase db) { String CREATE_TABLE CREATE TABLE cities ( id INTEGER PRIMARY KEY AUTOINCREMENT, city_name TEXT NOT NULL, city_url TEXT NOT NULL, is_favorite INTEGER DEFAULT 0); // 0未收藏1已收藏 db.execSQL(CREATE_TABLE); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP TABLE IF EXISTS cities); onCreate(db); } // 插入城市收藏 public void addCity(String cityName, String cityUrl) { SQLiteDatabase db this.getWritableDatabase(); ContentValues values new ContentValues(); values.put(city_name, cityName); values.put(city_url, cityUrl); values.put(is_favorite, 1); db.insert(cities, null, values); db.close(); } // 查询所有收藏城市 public ListCity getAllFavoriteCities() { ListCity cityList new ArrayList(); String SELECT_QUERY SELECT * FROM cities WHERE is_favorite 1; SQLiteDatabase db this.getReadableDatabase(); Cursor cursor db.rawQuery(SELECT_QUERY, null); if (cursor.moveToFirst()) { do { City city new City(); city.setId(cursor.getInt(cursor.getColumnIndex(id))); city.setCityName(cursor.getString(cursor.getColumnIndex(city_name))); city.setCityUrl(cursor.getString(cursor.getColumnIndex(city_url))); cityList.add(city); } while (cursor.moveToNext()); } cursor.close(); db.close(); return cityList; } }关键设计点表结构里is_favorite INTEGER DEFAULT 0允许同一城市多次插入比如用户先搜“上海”再搜“上海市”视为不同记录靠city_name去重逻辑放在UI层addCity()方法中db.insert()的第三个参数values必须包含is_favorite1否则新插入记录默认为0未收藏导致收藏按钮点击后无反应getAllFavoriteCities()返回ListCity其中City是一个简单POJO没有继承任何Android类符合课设对“自定义数据结构”的考察要求。3. Gradle构建与环境适配绕开Android Studio 2021版本的三大兼容性雷区3.1 Gradle Wrapper版本与AS版本的精确匹配表这份源码基于Android Studio 4.1 ~ 4.2构建对应gradle-wrapper.properties中的distributionUrldistributionUrlhttps\://services.gradle.org/distributions/gradle-6.5-bin.zip但如果你用的是 Android Studio Flamingo2022.2.1或 Hedgehog2023.1.1直接打开会报错Failed to build model project. Could not determine the dependencies of task :app:compileDebugAidl. Could not resolve all artifacts for configuration :app:debugRuntimeClasspath. Could not resolve com.android.tools.build:gradle:4.1.0.正确做法不是升级Gradle插件而是降级AS或锁定Gradle版本。推荐方案你的Android Studio版本应修改的gradle-wrapper.propertiesbuild.gradle(Project) 中com.android.tools.build:gradle版本AS Giraffe (2022.3.1)gradle-7.4-bin.zip7.4.2AS Flamingo (2022.2.1)gradle-7.3.3-bin.zip7.3.1AS Electric Eel (2022.1.1)gradle-7.4-bin.zip7.4.2注意Gradle 7.x 要求buildToolsVersion在app/build.gradle中必须删除否则报错buildToolsVersion is no longer supported。源码里若有这行直接删掉。3.2 JDK版本陷阱AS默认JDK17导致编译失败的修复Android Studio 2021.1 默认捆绑JDK17但此项目build.gradle中compileOptions设为 Java 1.8compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }此时若AS用JDK17编译会报error: invalid source release: 8解决方案二选一方案A推荐在File Project Structure SDK Location中将JDK location改为AS自带的jbr-11.0.13即JDK11因为JDK11完全兼容Java 8字节码方案B修改build.gradle将sourceCompatibility升级为JavaVersion.VERSION_11但需同步检查pinyin4j-2.5.0.jar是否支持JDK11经实测支持可放心升。3.3 网络安全配置Android 9.0设备真机调试必加的network_security_config.xml即使AndroidManifest.xml中写了android:usesCleartextTraffictrue在Android 10真机上仍可能因系统级网络策略拦截HTTP请求。必须添加res/xml/network_security_config.xml?xml version1.0 encodingutf-8? network-security-config domain-config domain includeSubdomainstruewww.weather.com.cn/domain cleartextTrafficPermittedtrue / /domain-config /network-security-config并在AndroidManifest.xml的application标签下添加android:networkSecurityConfigxml/network_security_config血泪经验某学生用华为Mate 40EMUI 11测试明明模拟器能跑通真机却一直返回空JSON。抓包发现请求根本没发出就是因为缺这个XML配置。中国天气网域名是www.weather.com.cn不是weather.com.cn少www.会导致配置失效。3.4 避坑Gradle Sync失败的五个高频原因与速查清单现象原因解决Could not find com.android.tools.build:gradle:4.1.0AS版本过高插件仓库地址变更修改build.gradle(Project) 中google()仓库位置确保在jcenter()之前或换用mavenCentral()Failed to resolve: pinyin4j-2.5.0libs/pinyin4j-2.5.0.jar文件损坏或路径不对检查app/build.gradle是否有implementation files(libs/pinyin4j-2.5.0.jar)用WinRAR校验ZIP完整性Error: Execution failed for task :app:mergeDebugResourcesres/values/strings.xml中有中文引号或BOM头用Notepad另存为UTF-8无BOM格式删除所有全角符号Cannot fit requested classes in a single dex file方法数超65536虽本项目不会但加了多余依赖会在app/build.gradle的defaultConfig下加multiDexEnabled true并添加implementation androidx.multidex:multidex:2.0.1Emulator: Process finished with exit code -1073741819Windows系统内存不足或显卡驱动冲突关闭AS重启电脑或在AVD Manager中降低模拟器RAM至1536MB4. 城市搜索与拼音转换pinyin4j库在课设场景下的精准用法4.1 为什么不用百度/高德API中国天气网URL生成规则拆解中国天气网城市接口URL格式为http://www.weather.com.cn/data/cityinfo/101010100.html其中101010100是城市编码规则如下前两位10国家编码中国第三位1华北区第四位01北京市后三位010北京市区东城区、西城区等共用此码但课设不可能让学生背编码表。源码用pinyin4j将城市名转拼音首字母再查内置映射表// app/src/main/java/com/example/weatherapp/utils/PinyinUtil.java public static String getCityCode(String cityName) { String pinyin PinyinHelper.toHanYuPinyinString(cityName, , , false); String firstLetter pinyin.substring(0, 1).toUpperCase(); // 内置简版映射实际项目应扩展 switch (firstLetter) { case B: return 101010100; // 北京 case S: return 101020100; // 上海 case G: return 101280101; // 广州 case S: return 101280601; // 深圳 default: return 101010100; // 默认北京 } }注意PinyinHelper.toHanYuPinyinString()第三个参数是分隔符传表示无分隔第四个参数false表示不带声调。若传true返回běi jīngsubstring(0,1)得到b映射失败。4.2 拼音转换的边界情况处理多音字与生僻字中国天气网API对城市名容错率低。输入“重庆”pinyin4j返回chong qing首字母C但映射表里没C分支结果查北京。源码对此做了妥协处理public static String getCityCodeByFullPinyin(String cityName) { String fullPinyin PinyinHelper.toHanYuPinyinString(cityName, , , false); fullPinyin fullPinyin.replaceAll( , ); // 去空格 // 精确匹配支持“重庆”、“chongqing” if (fullPinyin.toLowerCase().contains(chongqing)) { return 101040100; } else if (fullPinyin.toLowerCase().contains(shanghai)) { return 101020100; } else if (fullPinyin.toLowerCase().contains(beijing)) { return 101010100; } return getCityCode(cityName); // 退回到首字母方案 }这段代码的价值在于它展示了课设允许的“合理取巧”——不追求100%覆盖全国城市但保证直辖市和省会能查准。老师看到contains(chongqing)这种硬编码知道你理解了需求优先级。4.3 城市搜索框的防抖设计避免连续点击触发多次HTTP请求SearchView在activity_weather.xml中绑定onQueryTextSubmit但用户快速输入“北京”再回车可能触发2次请求。源码在WeatherActivity.java中加了简易防抖private long lastClickTime 0; private static final long MIN_CLICK_INTERVAL 2000; // 2秒内只响应一次 Override public boolean onQueryTextSubmit(String query) { if (System.currentTimeMillis() - lastClickTime MIN_CLICK_INTERVAL) { return false; // 忽略本次提交 } lastClickTime System.currentTimeMillis(); String cityCode PinyinUtil.getCityCodeByFullPinyin(query); String url http://www.weather.com.cn/data/cityinfo/ cityCode .html; loadWeatherData(url); return true; }这不是RxJava那种专业防抖但足够应付课设演示。关键点lastClickTime是Activity成员变量不是局部变量MIN_CLICK_INTERVAL设为2000ms既防误触又不卡操作。5. 真机调试与APK打包从USB调试到生成可安装包的全流程验证5.1 USB调试开启与ADB设备识别的三步确认法很多学生卡在“AS里看不到设备”。按顺序执行手机端设置 关于手机 连续点击“版本号”7次开启开发者选项手机端设置 系统管理 开发者选项 打开“USB调试”电脑端命令行执行adb devices若返回List of devices attached后跟一串字符如ABC123456789 device说明识别成功若为空或unauthorized则需在手机弹出“允许USB调试吗”时点“始终允许”。注意华为/小米手机需额外开启“USB安装”和“MTP传输模式”非“仅充电”。某学生用小米12USB线插电脑后AS不显示设备其实是手机默认设成了“仅充电”在通知栏下拉选择“文件传输”即可。5.2 Debug APK生成与手动安装的验证步骤AS菜单栏Build Build Bundle(s) and APK(s) Build APK(s)生成app-debug.apk位于app/build/outputs/apk/debug/。但课设验收常要求“APK能独立安装”需验证将APK复制到手机手机设置 安全 允许“未知来源”应用安装不同品牌路径略有差异用文件管理器点击APK安装安装后打开检查是否弹出“允许位置信息”提示虽本项目未用定位但系统可能误判输入“北京”确认UI显示温度、湿度、风向等字段且无“NetworkOnMainThreadException”。关键验证点首次安装后SQLite数据库是否自动创建可导出DB文件验证adb shell run-as com.example.weatherapp cat /data/data/com.example.weatherapp/databases/city.db city.db用DB Browser for SQLite打开确认cities表存在且结构正确。5.3 Release APK签名配置避免“Parse Error”安装失败课设提交要求Release版APK。在app/build.gradle的android块下添加signingConfigs { release { storeFile file(../my-release-key.jks) // 密钥文件路径 storePassword weather123 keyAlias key0 keyPassword weather123 } } buildTypes { release { signingConfig signingConfigs.release minifyEnabled false proguardFiles getDefaultProguardFile(proguard-android-optimize.txt), proguard-rules.pro } }生成密钥命令Windows PowerShellkeytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key0注意storeFile路径是相对app/目录的所以密钥文件要放在项目根目录keyPassword和storePassword必须相同否则签名失败minifyEnabled false关闭代码混淆避免Gson反射失效。5.4 避坑真机运行时白屏/闪退的四大排查方向现象排查点快速验证方法启动后白屏几秒然后显示天气onCreate()中loadWeatherData()同步阻塞主线程在loadWeatherData()开头加Log.d(Weather, Start loading...)看Logcat是否卡住点击“收藏”按钮无反应CityDatabaseHelper.addCity()执行但DB未更新用adb shell进入DB路径sqlite3 city.db后执行.tables看表是否存在切换城市后数据仍是旧的RecyclerView未调用notifyDataSetChanged()在updateUI()方法末尾加adapter.notifyDataSetChanged()下拉刷新后UI不更新SwipeRefreshLayout.setOnRefreshListener()未设置或监听器未触发在监听器内加Log.d(Refresh, Triggered)下拉时看Logcat6. 课设答辩加分技巧三个能让老师眼前一亮的实操优化点6.1 为JSON解析增加空值防护避免因API字段缺失导致崩溃中国天气网API偶尔会返回缺失字段的JSON比如weatherinfo.WD为空Gson默认会把String windDirection设为null后续setText(windDirection)报NullPointerException。我在WeatherInfo.java里加了空安全处理public String getWindDirection() { return windDirection null ? 暂无数据 : windDirection; } public String getTemperature() { if (temperature null) return —; // 去掉℃符号只留数字用于比较 return temperature.replace(℃, ).trim(); }然后在updateUI()中调用tvWind.setText(weatherInfo.getWindDirection()); tvTemp.setText(weatherInfo.getTemperature() ℃);这个改动小但价值高它表明你考虑了生产环境API的不可靠性不是照着理想JSON写代码。老师问“如果API挂了怎么办”你能指着这段说“我做了兜底至少UI不崩。”6.2 用ColorStateList实现收藏按钮的动态状态切换收藏按钮ImageView需要根据数据库状态切换图标未收藏时显示空心星收藏后显示实心星。源码用app/src/main/res/color/favorite_selector.xml?xml version1.0 encodingutf-8? selector xmlns:androidhttp://schemas.android.com/apk/res/android item android:colorcolor/yellow_star android:state_selectedtrue / item android:colorcolor/gray_star / /selector在Java代码中// 点击时切换状态 favoriteBtn.setOnClickListener(v - { if (favoriteBtn.isSelected()) { dbHelper.deleteCity(currentCityName); // 删除收藏 favoriteBtn.setSelected(false); } else { dbHelper.addCity(currentCityName, currentCityUrl); favoriteBtn.setSelected(true); } });这比用两个setImageResource()切换图片更优雅且符合Material Design规范。答辩时演示“点击收藏→图标变黄→再点变灰”老师会注意到你对UI细节的把控。6.3 添加离线缓存机制让APP在无网时显示上次数据课设没要求但加了能惊艳全场。在WeatherActivity.java的onCreate()中// 读取本地缓存 SharedPreferences prefs getSharedPreferences(weather_cache, MODE_PRIVATE); String cachedJson prefs.getString(last_weather_json, null); if (cachedJson ! null !cachedJson.isEmpty()) { try { Gson gson new Gson(); WeatherResponse response gson.fromJson(cachedJson, WeatherResponse.class); if (response.getWeatherInfo() ! null) { updateUI(response.getWeatherInfo()); Toast.makeText(this, 已加载缓存数据, Toast.LENGTH_SHORT).show(); } } catch (Exception e) { // 缓存损坏忽略 } }在loadWeatherData()成功后保存if (jsonStr ! null !jsonStr.isEmpty()) { SharedPreferences prefs getSharedPreferences(weather_cache, MODE_PRIVATE); prefs.edit().putString(last_weather_json, jsonStr).apply(); // ...后续解析更新UI }这个功能背后是完整的“网络优先缓存兜底”策略。我每次做课设都会加这一条——它不增加复杂度但让APP瞬间有了产品思维。从那以后我每次写网络请求都强制走一遍“先读缓存再发请求请求成功再存缓存”的闭环哪怕课设没要求。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑