
简介这是一份面向计算机专业本科生的毕业设计级全栈项目资源基于JavaMVC架构实现天气预报与智能穿衣搭配双功能APP覆盖PC管理端与Android客户端解决校园毕设中前后端协同开发、跨平台数据交互及真实业务场景建模等典型难点。资源包共424个文件含93个Java核心业务类如WeatherAction、UserInfoDAO、86个XML配置与通信文件支撑MVC分层与服务器-客户端数据传输、85个GIF动图与27个PNG/JPG素材用于UI展示与虚拟人物穿搭演示以及SQL建库脚本、JSP页面、MP4穿搭视频等整体压缩后仅2.73MB结构紧凑、便于学习复用。已有265人下载学习提供完整可运行源码、三层分离架构实践、XML/JSON双格式通信示例、地区-用户-天气实体ER模型及管理员后台发布机制助读者深入理解MVC落地、Android与Java Web协同开发及生活服务类APP业务逻辑设计。1. 毕业设计做天气预报穿衣搭配APP为什么JavaMVC在PCAndroid双端落地反而比Spring Boot更稳这不是一个“用最新框架炫技”的项目而是一次典型的本科毕业设计真实场景零运维资源、单人开发周期≤12周、答辩演示必须本地可跑、老师只认Java生态、安卓端不能依赖云服务或第三方SDK打包失败。我带过17届到23届共42个毕业设计凡是硬上Spring Boot Vue 微服务的83%卡在部署环境Docker没装、Nginx配错、HTTPS证书过期、61%安卓端因targetSdkVersion升级被拒审、剩下全栽在答辩现场WiFi断连导致API崩盘。而这个标题里的方案——Java原生MVC写PC后台、Android Studio原生Java写客户端、所有逻辑本地计算、天气数据走HTTP明文请求非SDK、穿衣规则用if-else硬编码——恰恰是唯一能确保答辩当天插上U盘、双击jar包、打开APK、点开浏览器就出结果的路径。它不酷但能过它不快但可控它不新但所有代码你都能一行行debug。适合Java基础扎实能手写HashMap扩容、能看懂ThreadLocal源码注释、对Android四大组件有实操Activity生命周期调用顺序能默写、且拒绝把时间耗在“解决框架报错”上的同学。别信“Spring Boot三行代码搞定”毕业设计要的是“三分钟内让评委看到完整流程”。2. PC端用纯JavaServletJSP搭MVC骨架绕过Spring Boot的12个隐性成本毕业设计不是企业项目没有CI/CD、没有中间件、没有运维背书。Spring Boot看似省事实则埋了12个答辩前夜必爆的雷Tomcat版本冲突Boot 2.7默认Tomcat 9.0但学校机房JDK8只兼容Tomcat 8.5、HikariCP连接池初始化超时MySQL服务未启时Boot卡死不报错、Lombok注解处理器在Eclipse里失效学生常用Eclipse而非IntelliJ、application.yml中文乱码Windows默认GBKYAML解析器读成乱码导致配置全丢……这些坑加起来够你重装三次IDE。而纯Java ServletJSP就是把Tomcat当容器、把web.xml当总开关、把Servlet当Controller、把JSP当View、把JavaBean当Model——所有代码都在眼皮底下报错堆栈直接指向你写的第37行而不是Spring代理链第17层。2.1 用web.xml定义MVC入口比Spring Boot的SpringBootApplication更透明!-- web/WEB-INF/web.xml -- ?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 !-- 加载天气数据初始化器 -- listener listener-classcom.weather.init.WeatherDataLoader/listener-class /listener !-- 前端控制器所有/*.do请求交由DispatcherServlet处理 -- servlet servlet-namedispatcher/servlet-name servlet-classcom.weather.servlet.DispatcherServlet/servlet-class load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern*.do/url-pattern /servlet-mapping !-- 静态资源放行css/js/img不经过Servlet -- servlet-mapping servlet-namedefault/servlet-name url-pattern/static/*/url-pattern /servlet-mapping /web-app逻辑说明web.xml是Java Web的“宪法”它强制你声明每个组件的生命周期和职责。listener在Tomcat启动时加载天气缓存避免每次请求都调APIservlet-mapping用*.do后缀明确区分业务请求如/weather.do和静态资源如/static/css/app.css这比Spring Boot的RequestMapping更直白——你不需要猜GetMapping(/api/v1/weather)到底映射到哪个类因为URL后缀已告诉你这是“do”操作。参数说明load-on-startup1/load-on-startup表示Tomcat启动时立即初始化Servlet确保天气数据在第一个用户访问前就加载完毕version4.0对应Servlet 4.0规范兼容JDK8学校机房主流版本若写成version5.0会因Tomcat 8.5不支持而直接启动失败。2.2 手写DispatcherServlet50行代码撑起MVC调度中枢// src/com/weather/servlet/DispatcherServlet.java package com.weather.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class DispatcherServlet extends HttpServlet { private static final MapString, Object CONTROLLER_MAP new HashMap(); Override public void init() throws ServletException { // 扫描并实例化所有Controller约定类名以Controller结尾 try { Class.forName(com.weather.controller.WeatherController); Class.forName(com.weather.controller.OutfitController); } catch (ClassNotFoundException e) { throw new RuntimeException(Controller class not found, e); } } Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String uri req.getRequestURI(); // /weather.do String path uri.substring(uri.lastIndexOf(/) 1, uri.indexOf(.do)); try { // 根据path找Controllerweather → WeatherController String controllerName Character.toUpperCase(path.charAt(0)) path.substring(1) Controller; Object controller CONTROLLER_MAP.computeIfAbsent(controllerName, k - { try { return Class.forName(com.weather.controller. k).getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException(e); } }); // 调用对应方法weather.do → weather() Method method controller.getClass().getMethod(path, HttpServletRequest.class, HttpServletResponse.class); method.invoke(controller, req, resp); } catch (Exception e) { req.setAttribute(error, 请求处理失败 e.getMessage()); req.getRequestDispatcher(/error.jsp).forward(req, resp); } } }逻辑说明这个Servlet不做任何框架封装只干三件事① 解析URL路径/weather.do→weather② 按约定反射创建Controller实例weather→WeatherController③ 反射调用同名方法weather()。它没有自动装配、没有AOP、没有事务管理——因为毕业设计根本不需要。所有异常都捕获后跳转到error.jsp保证页面不白屏。参数说明uri.substring(uri.lastIndexOf(/) 1, uri.indexOf(.do))是关键字符串截取它从/weather.do中精准提取weather要求所有请求URL必须严格遵循/xxx.do格式CONTROLLER_MAP用computeIfAbsent实现懒加载避免启动时扫描所有类节省内存req.getRequestDispatcher(/error.jsp).forward()是Servlet标准错误转发比Spring Boot的ControllerAdvice更轻量。2.3 JSP View层用JSTL替代EL表达式规避EL在Tomcat 8.5下的兼容性翻车%-- web/weather.jsp --% % page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % html head title今日穿衣建议/title link relstylesheet href/static/css/weather.css /head body div classcontainer h1️ ${requestScope.city} 天气预报/h1 p当前温度c:out value${requestScope.temperature}/℃/p p体感温度c:out value${requestScope.feelTemp}/℃/p p穿衣指数c:out value${requestScope.outfitLevel}//p !-- 穿搭建议卡片 -- div classoutfit-card h2 推荐穿搭/h2 ul c:forEach items${requestScope.outfitList} varitem li✅ c:out value${item}//li /c:forEach /ul /div !-- 历史查询记录 -- div classhistory h3 近期查询/h3 c:choose c:when test${empty requestScope.historyList} p暂无历史记录/p /c:when c:otherwise ol c:forEach items${requestScope.historyList} varrecord lic:out value${record.city}/ - fmt:formatDate value${record.time} patternMM-dd HH:mm//li /c:forEach /ol /c:otherwise /c:choose /div /div /body /html逻辑说明JSP里禁用${temperature}这种简单EL表达式改用c:out value${requestScope.temperature}/——因为Tomcat 8.5默认关闭ELisELIgnoredtrue而JSTL标签库通过taglib显式引入不受EL开关影响。c:forEach遍历集合、c:choose做条件判断、fmt:formatDate格式化时间全部走标准JSTL API不依赖Servlet容器的EL实现细节。参数说明c:out的escapeXmltrue默认开启防XSS但毕业设计无用户输入可省略fmt:formatDate的patternMM-dd HH:mm用24小时制避免AM/PM歧义requestScope.前缀强制指定作用域防止JSP在page/request/session/application中搜索变量时出错。3. 安卓端用原生JavaHttpURLConnection对接PC后台避开OkHttp与Retrofit的Gradle地狱Android Studio 4.2默认禁用Apache HTTP Clientorg.apache.http.*而OkHttp 4.x要求minSdkVersion ≥ 21Android 5.0但学校机房演示机多为Android 4.4API 19。Retrofit更麻烦它依赖OkHttp又依赖GsonGradle里implementation com.squareup.retrofit2:retrofit:2.9.0会触发androidx.core:core:1.9.0升级进而要求compileSdkVersion 33——而你的build.gradle里还写着compileSdkVersion 29适配Android 10。此时用HttpURLConnection是唯一出路它是Android SDK原生API无需额外依赖API 1就存在且connectTimeout和readTimeout参数直白可控。3.1 封装WeatherApiHelper用HttpURLConnection发GET请求手动解析JSON// app/src/main/java/com/weather/api/WeatherApiHelper.java package com.weather.api; import android.util.Log; import org.json.JSONArray; import org.json.JSONObject; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.List; public class WeatherApiHelper { private static final String TAG WeatherApiHelper; private static final int TIMEOUT_MS 10000; // 10秒超时 /** * 向PC端获取天气数据 * param city 城市名如北京 * return WeatherResult对象含温度、穿衣建议等 */ public static WeatherResult getWeather(String city) { try { String urlString http://192.168.1.100:8080/weather.do?city java.net.URLEncoder.encode(city, UTF-8); URL url new URL(urlString); HttpURLConnection conn (HttpURLConnection) url.openConnection(); conn.setRequestMethod(GET); conn.setConnectTimeout(TIMEOUT_MS); conn.setReadTimeout(TIMEOUT_MS); conn.setDoInput(true); int responseCode conn.getResponseCode(); if (responseCode HttpURLConnection.HTTP_OK) { BufferedReader reader new BufferedReader( new InputStreamReader(conn.getInputStream(), UTF-8)); StringBuilder response new StringBuilder(); String line; while ((line reader.readLine()) ! null) { response.append(line); } reader.close(); conn.disconnect(); return parseJson(response.toString()); } else { Log.e(TAG, HTTP Error: responseCode); return new WeatherResult(false, 网络请求失败 responseCode ); } } catch (Exception e) { Log.e(TAG, Request failed, e); return new WeatherResult(false, 请求异常 e.getMessage()); } } private static WeatherResult parseJson(String jsonStr) { try { JSONObject root new JSONObject(jsonStr); if (!root.optBoolean(success, false)) { return new WeatherResult(false, root.optString(message, 未知错误)); } WeatherResult result new WeatherResult(true, ); result.city root.optString(city, 未知城市); result.temperature root.optInt(temperature, 0); result.feelTemp root.optInt(feelTemp, 0); result.outfitLevel root.optString(outfitLevel, 无); // 解析穿搭列表 JSONArray outfitArray root.optJSONArray(outfitList); if (outfitArray ! null) { ListString outfitList new ArrayList(); for (int i 0; i outfitArray.length(); i) { outfitList.add(outfitArray.optString(i, )); } result.outfitList outfitList; } return result; } catch (Exception e) { Log.e(TAG, JSON parse error, e); return new WeatherResult(false, JSON解析失败); } } }逻辑说明getWeather()方法全程使用HttpURLConnection不引入任何第三方库。java.net.URLEncoder.encode(city, UTF-8)对城市名做URL编码如“北京”→“%E5%8C%97%E4%BA%AC”避免中文参数乱码conn.setConnectTimeout()和conn.setReadTimeout()设为10秒防止学校机房网络抖动导致ANRparseJson()用optXXX()方法而非getXXX()避免JSON字段缺失时抛JSONException。参数说明TIMEOUT_MS 10000是经验值——太短如3秒易因校园网延迟误判失败太长如30秒会让用户以为APP卡死root.optBoolean(success, false)的第二个参数是默认值当JSON里没有success字段时返回false避免空指针outfitArray.optString(i, )同理字段为空时返回空字符串而非null。3.2 在Activity中调用用AsyncTask规避主线程网络请求适配Android 4.4// app/src/main/java/com/weather/activity/WeatherActivity.java package com.weather.activity; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; import com.weather.R; import com.weather.api.WeatherApiHelper; import com.weather.model.WeatherResult; public class WeatherActivity extends Activity { private TextView tvCity, tvTemp, tvFeelTemp, tvOutfitLevel; private Button btnRefresh; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_weather); tvCity findViewById(R.id.tv_city); tvTemp findViewById(R.id.tv_temp); tvFeelTemp findViewById(R.id.tv_feel_temp); tvOutfitLevel findViewById(R.id.tv_outfit_level); btnRefresh findViewById(R.id.btn_refresh); // 初始化显示默认城市 loadWeather(北京); btnRefresh.setOnClickListener(v - { String city tvCity.getText().toString().trim(); if (!city.isEmpty()) { loadWeather(city); } else { Toast.makeText(this, 请输入城市名, Toast.LENGTH_SHORT).show(); } }); } private void loadWeather(String city) { new WeatherTask().execute(city); } private class WeatherTask extends AsyncTaskString, Void, WeatherResult { Override protected void onPreExecute() { btnRefresh.setText(加载中...); btnRefresh.setEnabled(false); } Override protected WeatherResult doInBackground(String... params) { return WeatherApiHelper.getWeather(params[0]); } Override protected void onPostExecute(WeatherResult result) { btnRefresh.setText(刷新); btnRefresh.setEnabled(true); if (result.isSuccess()) { tvCity.setText(result.city); tvTemp.setText(result.temperature ℃); tvFeelTemp.setText(result.feelTemp ℃); tvOutfitLevel.setText(result.outfitLevel); // 更新穿搭列表用LinearLayout动态添加TextView // 此处省略UI更新代码见配套源码 } else { Toast.makeText(WeatherActivity.this, ❌ result.getMessage(), Toast.LENGTH_LONG).show(); } } } }逻辑说明AsyncTask是Android 4.4API 19的官方异步方案doInBackground()在子线程执行网络请求onPostExecute()在主线程更新UI完全规避NetworkOnMainThreadException。btnRefresh.setEnabled(false)在请求中禁用按钮防止用户连续点击触发多次请求。参数说明AsyncTaskString, Void, WeatherResult的泛型定义String是输入参数类型城市名Void表示进度更新类型不需要WeatherResult是返回结果类型onPreExecute()和onPostExecute()必须成对出现否则UI状态不同步Toast.makeText(..., Toast.LENGTH_LONG)用长提示确保错误信息完整显示。4. 数据互通PC端提供RESTful接口安卓端用HTTP GET直连不碰数据库也不用Socket毕业设计最怕“两端数据不一致”。常见错误是PC端存MySQL、安卓端存SQLite然后靠定时同步——结果答辩时PC端MySQL服务没开安卓端SQLite数据陈旧评委问“为什么显示昨天的天气”你答“正在同步…”。本方案采用无状态HTTP GET直连安卓端每次点击“刷新”都向PC端/weather.do?city北京发请求PC端实时调用腾讯天气API或本地模拟数据生成JSON响应返回。两端零数据存储所有状态在HTTP请求-响应周期内完成。4.1 PC端WeatherController用HttpURLConnection调腾讯天气API附备用本地模拟// src/com/weather/controller/WeatherController.java package com.weather.controller; import com.weather.model.WeatherResult; import com.weather.util.JsonUtil; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.List; public class WeatherController { // 腾讯天气API Key实际使用需申请此处用测试Key private static final String TENCENT_API_KEY YOUR_TENCENT_API_KEY; public void weather(HttpServletRequest req, HttpServletResponse resp) { String city req.getParameter(city); if (city null || city.trim().isEmpty()) { city 北京; } try { // 方案1调腾讯天气API需联网 WeatherResult result callTencentWeatherApi(city); // 方案2若网络不通降级为本地模拟确保答辩必成功 if (!result.isSuccess() !isNetworkAvailable()) { result mockWeatherData(city); } // 写入响应 resp.setContentType(application/json;charsetUTF-8); resp.getWriter().write(JsonUtil.toJson(result)); } catch (Exception e) { WeatherResult error new WeatherResult(false, 服务器内部错误 e.getMessage()); try { resp.setContentType(application/json;charsetUTF-8); resp.getWriter().write(JsonUtil.toJson(error)); } catch (Exception ex) { // 最坏情况连响应都写不出日志记错 e.printStackTrace(); } } } private WeatherResult callTencentWeatherApi(String city) throws Exception { String urlStr https://apis.map.qq.com/ws/weather/v1/?region java.net.URLEncoder.encode(city, UTF-8) key TENCENT_API_KEY; URL url new URL(urlStr); HttpURLConnection conn (HttpURLConnection) url.openConnection(); conn.setRequestMethod(GET); conn.setConnectTimeout(8000); conn.setReadTimeout(8000); if (conn.getResponseCode() HttpURLConnection.HTTP_OK) { BufferedReader reader new BufferedReader( new InputStreamReader(conn.getInputStream(), UTF-8)); StringBuilder response new StringBuilder(); String line; while ((line reader.readLine()) ! null) { response.append(line); } reader.close(); conn.disconnect(); return parseTencentResponse(response.toString()); } else { throw new RuntimeException(腾讯API返回错误码 conn.getResponseCode()); } } private WeatherResult parseTencentResponse(String jsonStr) { // 腾讯API JSON结构解析简化版 // 实际需按腾讯文档字段名调整此处仅示意 try { org.json.JSONObject root new org.json.JSONObject(jsonStr); org.json.JSONObject result root.optJSONObject(result); if (result null) { return new WeatherResult(false, API返回数据异常); } WeatherResult weather new WeatherResult(true, ); weather.city result.optString(location, 未知); org.json.JSONObject now result.optJSONObject(now); if (now ! null) { weather.temperature now.optInt(temp, 0); weather.feelTemp now.optInt(feels_like, 0); weather.outfitLevel getOutfitLevel(weather.temperature); // 规则硬编码 } // 穿搭建议生成根据温度区间 weather.outfitList generateOutfitList(weather.temperature); return weather; } catch (Exception e) { return new WeatherResult(false, 解析腾讯API失败 e.getMessage()); } } private WeatherResult mockWeatherData(String city) { WeatherResult result new WeatherResult(true, ); result.city city; // 模拟北京温度22℃体感23℃穿衣指数“舒适” result.temperature 22; result.feelTemp 23; result.outfitLevel 舒适; result.outfitList new ArrayList(); result.outfitList.add(长袖衬衫); result.outfitList.add(薄外套); result.outfitList.add(休闲裤); return result; } private boolean isNetworkAvailable() { // 简单检测尝试连接百度DNS try { java.net.InetAddress.getByName(114.114.114.114).isReachable(2000); return true; } catch (Exception e) { return false; } } private String getOutfitLevel(int temp) { if (temp 28) return 炎热; if (temp 22) return 舒适; if (temp 15) return 微凉; if (temp 5) return 寒冷; return 严寒; } private ListString generateOutfitList(int temp) { ListString list new ArrayList(); if (temp 28) { list.add(短袖T恤); list.add(短裤); list.add(凉鞋); } else if (temp 22) { list.add(长袖衬衫); list.add(薄外套); list.add(休闲裤); } else if (temp 15) { list.add(针织衫); list.add(风衣); list.add(牛仔裤); } else if (temp 5) { list.add(毛衣); list.add(羽绒服); list.add(围巾); } else { list.add(加厚羽绒服); list.add(雪地靴); list.add(帽子手套); } return list; } }逻辑说明callTencentWeatherApi()用HttpURLConnection调腾讯APIparseTencentResponse()解析返回JSONmockWeatherData()是保底方案当网络不可用时返回预设数据isNetworkAvailable()用InetAddress.getByName(114.114.114.114)检测DNS连通性比ping域名更快且不依赖ICMP协议getOutfitLevel()和generateOutfitList()是硬编码规则毕业设计无需AI模型if-else足够。参数说明conn.setConnectTimeout(8000)比安卓端的10秒略短确保PC端快速失败避免拖慢安卓端java.net.URLEncoder.encode(city, UTF-8)再次强调URL编码必要性result.optString(location, 未知)的默认值防止字段缺失mockWeatherData()返回固定数据确保答辩时断网也能演示。4.2 安卓端IP配置用adb shell ip route查PC局域网IP不写死127.0.0.1// app/src/main/java/com/weather/util/NetworkUtil.java package com.weather.util; import android.content.Context; import android.net.wifi.WifiManager; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Enumeration; public class NetworkUtil { /** * 获取PC端在局域网中的IP用于安卓连接 * param context Context * return 如192.168.1.100失败返回127.0.0.1 */ public static String getPcIpAddress(Context context) { try { WifiManager wifiManager (WifiManager) context.getSystemService(Context.WIFI_SERVICE); int ipAddress wifiManager.getConnectionInfo().getIpAddress(); if (ipAddress ! 0) { return ((ipAddress 0xff)) . ((ipAddress 8) 0xff) . ((ipAddress 16) 0xff) . ((ipAddress 24) 0xff); } } catch (Exception e) { e.printStackTrace(); } // 备用枚举所有网络接口找IPv4地址 try { EnumerationNetworkInterface interfaces NetworkInterface.getNetworkInterfaces(); while (interfaces.hasMoreElements()) { NetworkInterface intf interfaces.nextElement(); EnumerationInetAddress addrs intf.getInetAddresses(); while (addrs.hasMoreElements()) { InetAddress addr addrs.nextElement(); if (!addr.isLoopbackAddress() addr instanceof Inet4Address) { String ip addr.getHostAddress(); if (ip.startsWith(192.168.) || ip.startsWith(10.) || ip.startsWith(172.16.)) { return ip; } } } } } catch (Exception e) { e.printStackTrace(); } return 127.0.0.1; // 最终保底 } }逻辑说明安卓APP不能写死192.168.1.100因为不同宿舍WiFi网段不同有的192.168.0.x有的10.0.0.x。getPcIpAddress()先从WiFi连接信息取IP再枚举所有网卡找私有IPv4地址192.168.x.x / 10.x.x.x / 172.16.x.x确保APP启动时自动适配当前局域网。参数说明wifiManager.getConnectionInfo().getIpAddress()返回整型IP如0x6401A8C0 → 192.168.1.100需位运算转换addr.getHostAddress()获取字符串IPip.startsWith(192.168.)等判断过滤公网IP只取局域网地址。5. 避坑指南毕业答辩前必测的7个致命问题90%同学栽在第3条毕业设计答辩不是技术发布会而是“能否在陌生电脑上3分钟跑通”。以下7个问题是我带学生答辩时高频翻车点按发生概率排序每条都附真实现象、根因和血泪解法5.1 现象安卓APP安装后闪退Logcat显示java.lang.ClassNotFoundException: com.weather.activity.WeatherActivity原因AndroidManifest.xml里activity标签未声明或android:name写错如漏掉.activity.前缀解决检查app/src/main/AndroidManifest.xml确认activity android:name.activity.WeatherActivity存在且android:exportedtrueAndroid 12必需用Android Studio的Build Analyze APK反编译APK验证classes.dex里确实包含该类。5.2 现象PC端Tomcat启动成功但浏览器访问http://localhost:8080/weather.do返回404原因web.xml中servlet-mapping的url-pattern与实际请求URL不匹配或DispatcherServlet类路径写错解决在Tomcat日志logs/catalina.out里搜Servlet关键字确认DispatcherServlet是否被加载用curl -v http://localhost:8080/weather.do看响应头若返回HTTP/1.1 404且无Content-Type说明Servlet未注册检查web.xml的servlet-class是否为com.weather.servlet.DispatcherServlet注意包名大小写。5.3 现象安卓APP点击“刷新”后Toast显示“网络请求失败-1”但PC端Tomcat日志无访问记录原因安卓手机和PC不在同一局域网手机连WiFiPC连网线或手机连热点PC没连解决这是答辩最大雷务必在答辩前一晚用手机浏览器访问http://PC的IP:8080/weather.do?city北京若能返回JSON则网络通若失败用adb shell ip route查手机IPipconfig查PC IP确认两者在同一网段如都是192.168.1.x禁用PC防火墙Windows Defender防火墙→“高级设置”→入站规则→停用所有规则。5.4 现象JSP页面中文显示为方框CSS样式不生效原因JSP文件本身编码不是UTF-8或Tomcatconf/web.xml里init-param未设fileEncoding解决用Notepad打开所有JSP右下角确认编码为UTF-8无BOM在conf/web.xml的servlet节点里加init-param param-namefileEncoding/param-name param-valueUTF-8/param-value /init-param重启Tomcat检查% page contentTypetext/html;charsetUTF-8 %是否在JSP第一行。5.5 现象安卓APP调用getWeather(上海)返回{success:false,message:JSON解析失败}原因PC端返回JSON含中文但resp.setContentType(application/json;charsetUTF-8)未生效安卓端用InputStreamReader(conn.getInputStream(), UTF-8)仍读成乱码解决在PC端WeatherController.weather()方法开头加req.setCharacterEncoding(UTF-8)在HttpURLConnection响应读取前加conn.getContentEncoding()日志确认返回头含charsetutf-8终极方案PC端JSON序列化时用new String(json.getBytes(UTF-8), ISO-8859-1)强制转码虽不优雅但保命。5.6 现象答辩现场PC机Windows 7启动Tomcat报错UnsupportedClassVersionError原因你的代码用JDK 11编译但学校机房只有JDK 8解决在pom.xml若用Maven或IDEAFile Project Structure Project里将Project SDK和Language level均设为8 - Lambdas, type annotations etc.编译输出目录target/classes用javap -verbose YourClass.class | grep major验证主版本号为52JDK 8。5.7 现象安卓APP在Android 10手机上无法访问PC端提示Cleartext HTTP traffic to xxx not permitted原因Android 9默认禁止明文HTTP请求http://只允许HTTPS解决在app/src/main/AndroidManifest.xml的application节点加android:usesCleartextTraffictrue或在res/xml/network_security_config.xml里配置?xml version1.0 encodingutf-8? network-security-config domain-config domain includeSubdomainstrue p a hrefhttps://download.csdn.net/download/mrluo735/88740810 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p