简介:一套面向高校学生群体的智慧校园Android客户端及管理系统,覆盖校园资讯浏览与互动、生活学习记录、任务提醒与进度管理、团队建设与任务协作等场景,适合计算机相关专业学生用于毕业设计、课程设计或项目初期演示。资源包含配套文档说明,代码经完整测试,运行成功,可直接启动体验。整份资料共763个文件,其中含260个PNG界面素材、218个Java源码、210个XML布局与配置,另有so动态库、jar依赖库等工程部件,压缩包整体约26.8MB,目录结构清晰,便于按模块定位学习。目前已有106人学习下载,既适合快速复现完整业务流程,也便于在现有工程基础上二次修改,实现个性化功能扩展。资源中还保留了gradle构建脚本与签名文件等工程要素,可辅助读者理解Android客户端与管理系统的协作机制,对深入掌握项目整体架构具有一定参考价值。
1. 智慧校园 Android 客户端:一份跑通后才能说值的毕设源码
“智慧校园 Android 客户端”这类项目,在毕设选题里一直很热,但很多同学下载后第一个反应是:文件名看着没问题,代码也齐全,就是跑不起来。我花了一个晚上把这份基于 Java 的智慧校园客户端及管理系统源码完整过了一遍,结论是它是那种结构完整、前后端都有的实操型项目,不是只给你一个界面壳子。学生端覆盖了校园资讯浏览、点赞评论分享、日程与任务提醒、团队建队与任务派发;管理端负责资讯审核、分类维护和团队数据管理,两边通过接口联动。适合两类人来下:一是做毕设、课设需要一份能拿出来演示、能讲清楚模块关系的 Java+Android 项目;二是刚学完 Java 基础、想看看客户端和服务端怎么对接的新手。下面我从工程结构开始拆,重点放在怎么跑通和哪里会翻车。
2. 项目底座:从 build.gradle 到 config.gradle,先把工程结构看明白
2.1 解压后先看什么:README、gradlew 与工程目录
下载解压之后,第一件事不是急着往 Android Studio 里拖,而是先把目录结构和文档看一遍。这份源码的根目录里至少有 gradlew.bat、channel、多个 .gitignore、build.gradle、config.gradle 这几个关键文件。很多刚接触 Android 的人会忽略这些文件,直接去找 MainActivity,结果往往是被一堆报错劝退。其实这几个文件才是判断项目能否跑通的第一道关口。
我把这些文件的作用整理成一张表,照着看就行:
| 文件/目录 | 作用 | 操作建议 |
|---|---|---|
| gradlew.bat | Windows 下的 Gradle 包装脚本,负责按指定版本拉取 Gradle | Windows 上用命令行编译走它 |
| gradlew | 同上的 Unix/Linux/macOS 版本 | 在 Mac 或 Linux 下运行要给执行权限 |
| build.gradle | 工程根级构建脚本,声明插件与依赖仓库 | 打开看 plugins 与 dependencies |
| config.gradle | 抽取公共配置的文件,如版本号、渠道、签名信息 | 改版本号时先看这里 |
| channel | 渠道相关配置,通常配合多渠道打包脚本使用 | 不需要多渠道时不用动 |
| .gitignore | Git 忽略规则 | 与运行无关,但能看出工程结构 |
这些文件单独看没有意义,组合起来就是一个典型的 Android Gradle 工程骨架。跟 Eclipse 时代的 Android 项目不同,现在拿到一份源码,判断它能不能跑,主要看三个东西:Gradle 版本、SDK 版本、依赖仓库。所以打开 build.gradle 和 config.gradle 之后,先确认这三项,再决定要不要继续花时间。
2.2 build.gradle 和 config.gradle 里的关键配置
常见做法是 config.gradle 里写 ext 块,集中管理版本号。打开它会看到类似这样的配置:
ext { // 工程统一版本号,改版本时只改这一处 compileSdkVersion = 28 minSdkVersion = 21 targetSdkVersion = 28 versionCode = 1 versionName = "1.0" }这段配置决定了整个工程的编译基础。compileSdkVersion 是编译时用的 SDK 平台版本,设得比依赖库要求低,编译会直接报错;设得比你本机安装的 SDK 版本高,又会出现 SDK 找不到的问题。我一般先按源码原值编译,报错再根据第 5 章的排查方法处理。minSdkVersion 决定最低支持的 Android 版本,21 表示 Android 5.0 起就能运行,对毕设项目来说覆盖了绝大多数测试手机。
依赖方面,智慧校园客户端一般会引入网络请求库、图片加载库、下拉刷新库和数据库工具,常见组合是 OkHttp/Volley、Glide/Picasso、Gson/FastJson。配置文件里看到这些,业务层的接口调用方式基本就能判断出来。比如用了 OkHttp,网络请求基本都是 Callback 回调风格;用了 Gson,JSON 解析时实体类字段名就要和接口返回的字段一一对应。这个信息在后面改代码时很重要,不然很容易在解析环节踩坑。
根级 build.gradle 里的 repositories 决定依赖从哪里下载,国内环境经常卡在这一步,后续在第 5 章“Gradle 依赖下载卡住”会详细说怎么换阿里云镜像。app 模块下的 build.gradle 里声明依赖和 buildTypes,打开它能看到源码引用到的具体库版本,比如用的是 support 库还是 androidx。这一点直接决定了 Android Studio 打开工程后要不要做迁移——如果源码还在用老版 support 库,而你电脑上的 Android Studio 版本较新,IDE 可能会弹窗提示迁移到 androidx,这一步操作不当就会引发一整片编译错误。
2.3 客户端与管理系统的数据边界
标题里的“客户端及管理系统”,拆开看是两部分:Android 客户端是用户入口,管理系统负责内容与数据维护。学生端发资讯、建团队、派任务,管理端审核资讯、管理分类、查看统计。两边的数据通过后端接口同步,客户端不直接操作数据库。这种设计决定了你跑通项目时要做两件事:先把后端服务起起来,再让客户端指向这个服务。
后端通常是一个独立的 Java Web 工程,数据库脚本单独放在 sql 文件里。先导入数据表,再按 README 的说明启动服务,最后才轮到客户端。如果你下载的包里面没有明显的后端目录,那么管理系统可能以压缩包形式单独附带,或者在客户端工程的 assets 目录里有接口说明文档。拿到包后先在 README 里找“服务端”“数据库”这两个关键词,能省下后面不少排查时间。整个项目的数据链路是:客户端发送 HTTP 请求 → 后端处理业务逻辑 → MySQL 读写数据 → 返回 JSON 给客户端渲染。第 3 章、第 5 章的排查全部围绕这条链路展开。
3. 导入与首跑:在 Android Studio 里复现这份智慧校园客户端
3.1 环境准备:JDK、Android Studio 与 SDK 版本匹配
先讲环境,这部分不准备好,后面全是报错。Java 是这份项目的地基,Android 开发要求 JDK 8 以上,但毕设项目大多数是基于 JDK 8 开发的,所以优先用 1.8,不要一上来装最新的 JDK 21。JDK 版本过高,Gradle 版本匹配不上,编译时会报 Unsupported class file major version 之类的错误,这个坑几乎每个跑旧项目的人都会碰到。
在命令行里验证一下当前 Java 环境:
java -version javac -version输出里有 1.8 或 8 字样,就说明没问题。如果输出的是 17、21 这类版本,建议先装一个 JDK 8,并在 Android Studio 的 Project Structure 里把 SDK Location 和 JDK Location 指到对应路径。注意 Android Studio 里有两个 Java 相关配置:一个是 Gradle JVM,一个是 IDE 自己的 JVM。编译走的是 Gradle JVM,所以改的时候要改 Gradle JVM 那一项,改错了仍然会报版本错误。
然后是 Android Studio 版本。源码里如果有 gradle/wrapper/gradle-wrapper.properties 文件,打开看 distributionUrl 写了哪个 Gradle 版本,常见是 5.4.1、6.5 这些,对应 Android Studio 3.x 或 4.x。版本差太远,AS 会提示升级 Gradle,这时先不要点自动升级。自动升级表面上省事,实际上会把整个工程的依赖版本都推到新坐标系里,老代码一个接一个地报错。宁可手动调整 SDK 版本,也比全量迁移公积金稳妥。
3.2 导入工程的两种方式与首次编译
导入工程有两条路。第一种是直接 Open。打开 Android Studio,选择 Open 或 File → Open,定位到解压目录里的根 build.gradle,AS 会把它识别为一个 Gradle 工程并开始 Sync。注意不要选错文件,选成模块下的 build.gradle,AS 会以单模块方式打开,很多东西会显示不出来。第二种是用命令行构建,在项目根目录执行:
# Windows 下用 gradlew.bat,Mac/Linux 用 ./gradlew ./gradlew assembleDebug如果第一次执行报权限错误,先跑 chmod +x gradlew 给脚本加执行权限。这个命令的作用是让 Gradle Wrapper 自动下载项目指定的 Gradle 版本,然后进行 Debug 包的完整编译。编译成功后会生成 app-debug.apk,屏幕输出 BUILD SUCCESSFUL。如果输出 BUILD FAILED,日志里一般会直接告诉你缺什么、错在哪。
解释一下两种方式的关系:AS 的 Sync 和命令行构建用的是同一套 Gradle 配置,命令行适合排查问题,AS 适合日常开发调试。先跑命令行有一个好处,能看到完整的编译日志,报错信息比 IDE 弹窗更具体。我第一次拆这个项目时就是直接用命令行编译,日志里把缺的依赖、错的语法、SDK 问题全部列出来了,后面再去 IDE 里修就很快。Sync 和编译都通过之后,再点 Run 按钮装到设备上,这时候才进入业务联调阶段。
3.3 后端管理系统怎么联动:先跑起来再谈业务
客户端能编译不代表业务能走通,因为资讯列表、登录、团队数据都来自后端。这份源码的管理系统部分,通常是一个 Java Web 工程,可能带数据库脚本。常见做法是先导入数据库脚本到 MySQL,再启动后端服务。假如后端是 Spring Boot,直接在根目录执行 mvn spring-boot:run;假如是 SSM 结构,就用 Tomcat 部署。具体方式以你下载到的 README.md 为准,源码包不同,目录结构会有差别。
我一般会先确认三件事:数据库名和账号密码、后端端口号、接口前缀。这三项的配置位置一般在后端工程的 application.properties、jdbc.properties 或 config 包下。端口号大多是 8080,数据库地址大多是 localhost:3306。改完之后,浏览器先访问一下接口路径,比如登录接口或资讯列表接口,确认能返回 JSON 再启动 Android 客户端。这一步能省掉很多联调时间,因为客户端跑起来后如果报网络错误,你至少知道问题不在后端。
这里最容易踩的坑是数据库连接失败和端口被占用。日志里看到 Connection refused 说明数据库没起来或密码不对;看到 Port 8080 was already in use 说明有另一个服务占了 8080,换个端口或杀掉占用进程就行。还有一点:后端启动成功后,不要关掉那个终端窗口,因为服务是挂在里面的进程,窗口一关服务就停了。
4. 业务模块拆解:资讯流、任务提醒、团队协作的实现套路
4.1 资讯流与点赞评论:列表 + 异步请求的典型写法
资讯流是这份客户端最核心的页面。信息包括活动比赛公告、社团班级资讯,操作包括点赞、评论、分享。这类页面在 Android 里最常见的实现组合是 RecyclerView + 异步请求,数据从接口拉回来以后填充到列表中。直接给一个资讯适配器的骨架,这份源码里很容易找到对应位置:
public class NewsAdapter extends RecyclerView.Adapter<NewsAdapter.ViewHolder> { private final List<News> newsList; private final Context context; // 构造时传入列表和上下文,Adapter 本身不关心数据从哪来 public NewsAdapter(Context context, List<News> newsList) { this.context = context; this.newsList = newsList; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(context) .inflate(R.layout.item_news, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(ViewHolder holder, int position) { News item = newsList.get(position); holder.title.setText(item.getTitle()); holder.publisher.setText(item.getPublisher()); holder.likeCount.setText(String.valueOf(item.getLikeCount())); // 点赞要发异步请求,避免直接在主线程写网络调用 holder.btnLike.setOnClickListener(v -> likeNews(item.getId(), position)); } private void likeNews(Long newsId, int position) { // 这里一般走 OkHttp 或项目中封装的 HttpUtil // 请求体里带 newsId 和当前用户 id,成功后刷新当前 item 的点赞数 } }这段代码的逻辑并不复杂:onCreateViewHolder 负责创建 item 的视图容器,onBindViewHolder 负责把数据绑定到控件上。onCreateViewHolder 只在需要新视图时调用,onBindViewHolder 则在每个 item 进入可视区域时刷新内容,所以滑动列表时数据更新会很及时。值得注意的有两点:一是 Adapter 自身不发起列表数据的网络请求,数据是在 Activity/Fragment 里请求好之后通过 setAdapter 传进来的;二是点赞按钮放在 Adapter 里做监听,但真正的请求要交给网络层执行。参数上,likeNews 的第一个参数是资讯主键,第二个是列表索引,这样回调成功后可以直接用 notifyItemChanged(position) 更新对应 item,不用重新全量刷新列表。
评论和分享通常是同一个套路。评论走一个带输入框的弹窗或新页面,接口路径一般是 /news/comment,参数带资讯 id 和评论内容;分享则调用系统分享或项目内封装的分享 SDK。拿到这份源码后,建议优先读资讯模块,因为它把列表加载、异步请求、界面刷新、用户交互全串起来了,其他模块都是这个套路的变形。把资讯流啃下来,任务列表和团队任务列表基本不用从头看代码就能上手改。
4.2 任务提醒与进度管理:实体类设计与本地持久化
项目定位里写得很清楚:方便用户记录生活、工作、学习上的大小事,可设置任务提醒、管理任务进度。这个模块在 Android 端一般由三部分组成:任务列表界面、新增/编辑界面、本地数据库或服务端存储。我给出一个典型的任务实体类设计:
public class Task { private Long id; // 任务主键 private String title; // 任务标题 private String description; // 任务描述 private Integer status; // 0 未开始 1 进行中 2 已完成 private String remindTime; // 提醒时间,格式 yyyy-MM-dd HH:mm private String teamId; // 所属团队,空则属于个人 // getter 和 setter 略 }status 用整型而不用字符串,好处是状态流转方便,后端做统计时直接按数字分组。remindTime 用字符串保存完整的日期时间,比分开存日期和时间更简单,排序时字符串也能保证正确顺序。和任务提醒配合的还有通知逻辑,常见做法是 AlarmManager 或 WorkManager 定一个定时任务,到点发通知。如果项目里用的是轮询检测,每秒扫一遍任务表,实时性会差一些,但对毕设演示来说完全够用,而且实现起来更直观。
任务进度管理依赖于保存动作。如果项目使用 SQLite,你需要维护一张任务表;如果项目把任务同步到后端,客户端只做本地缓存,那么表的字段要和接口返回的 JSON 字段保持一致。判断方式很简单:看有没有 dao 包或 db 包,有则是本地数据库路径,没有则认为数据全部走接口。改造时注意,只要动了实体类字段,接口路径和数据库表结构可能都要跟着改,否则会出现 JSON 序列化失败或字段映射不上。我见过有一个同学把 status 从 Integer 改成 String 后,接口返回的 0、1、2 全都解析成了 ASCII 码对应的字符,列表直接显示乱码。
4.3 团队任务分发与申请:核心数据关系
团队模块是这份项目区别于单纯资讯 App 的地方。团队成员可以安排任务、接任务,其他机构可以申请团队做某个任务,团队还能发布资讯和管理成员。这个模块牵扯的表比资讯多,典型的表结构至少包含:团队表、成员表、任务表、申请记录表。前面刚看到 Task 实体里有个 teamId,它就是把个人任务和团队任务串起来的字段:teamId 为空是个人任务,不为空则属于团队,任务列表默认只显示当前用户所在团队的任务。
团队任务的派发逻辑通常是这样:队长创建任务并指派给成员,成员的客户端在任务列表看到新任务,接单后状态从 0 变成 1,完成后再更新为 2。申请逻辑则是:其他机构提交任务申请,队长在管理端或客户端审核,通过后该申请转为正式任务。数据关系上,一个团队有多个成员,一个任务只属于一个团队,一条申请记录关联一个任务和申请方,整体是一对多加多对一的关系。后端接口的设计一般会提供三个核心入口:任务列表接口、任务状态更新接口、申请审核接口。
这段内容放到源码里看,重点是找到任务列表接口的请求参数。一般会传 userId 或 teamId,后端据此返回该用户可见的任务。我建议拿到代码后先在管理端把一个团队建好、分配几个成员进去,然后客户端用队长账号登录,发一个任务给成员,再切换到成员账号看能不能在列表里看到。整个流程走通,团队模块的代码你也就顺带读明白了。如果你要扩展功能,比如加一个“我发起的申请”列表,本质上就是对申请记录表做一次按申请人的查询,接口和客户端各加一个入口就行。
5. 避坑与排查:从导入到答辩的 5 个高频翻车现场
跑这份毕设源码,坑基本集中在导入、编译和网络连接三块。我把拆解过程中遇到最多的问题整理成 5 条,每条按“现象 → 原因 → 解决”的顺序来写,方便你对照排查。
5.1 gradlew 在 Windows 上运行报错
现象:按 README 里的命令在 Windows 上执行 gradlew assembleDebug,提示“无法将‘gradlew’项识别为 cmdlet、函数、脚本文件或可运行程序的名称”;Mac 或 Linux 下执行 ./gradlew 则提示 Permission denied。
原因:Windows 的命令行工具默认不识别无扩展名文件,应该运行的是 gradlew.bat;Unix 系下缺少可执行权限。
解决:Windows 用.\gradlew.bat assembleDebug;Mac/Linux 先执行chmod +x gradlew然后./gradlew assembleDebug。如果 PowerShell 还报“因为在此系统上禁止运行脚本”,执行Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope CurrentUser。这是 Windows 脚本执行策略的问题,不是源码问题,改完后重新打开终端再跑一次。
5.2 Gradle 依赖下载卡住
现象:导入工程后,Sync 一直停在 Downloading 阶段,等很久都不动,翻日志能看到连接超时的字样。
原因:Gradle 官方分发源和部分依赖仓库在国内网络环境下不稳定,属于访问问题,不是工程代码的问题。
解决:先把 gradle-wrapper.properties 的 distributionUrl 换成阿里或腾讯镜像,再把根级 build.gradle 和模块 build.gradle 的仓库地址加上阿里云镜像:
buildscript { repositories { maven { url 'https://maven.aliyun.com/repository/google/' } maven { url 'https://maven.aliyun.com/repository/public/' } google() jcenter() } }放好之后重新同步,Android Studio 菜单 Build → Clean Project,然后 Rebuild Project。镜像仓库和官方仓库的区别只是下载源不同,走的还是同一个依赖坐标系,所以不用担心兼容性。注意一个细节:阿里云镜像地址不要手打,容易漏斜杠或拼错域名,直接复制官方文档里的地址,不然会 404。
5.3 编译时报 SDK 版本找不到
现象:构建时输出类似 Failed to find target with hash string 'android-28' 或 Failed to find Build Tools revision 28.0.3 的报错。
原因:源码里 compileSdkVersion 写的是 28,但你本机只装了更高版本的 SDK,编译时找不到对应平台。
解决:两个办法,优先用第一个。打开 Android Studio 的 SDK Manager,在 SDK Platforms 勾选对应的 API Level,在 SDK Tools 勾选对应版本的 Build Tools,应用安装后重新构建。如果只是想快速验证,也可以把 config.gradle 里的 compileSdkVersion、targetSdkVersion 改成你现有的版本。但要注意,个别旧库在高版本 SDK 下会警告或报错,所以优先安装 SDK 版本,第二选择才是改编译版本。改完版本号后记得同步更新 targetSdkVersion,只改 compileSdkVersion 不改 targetSdkVersion,运行时可能仍按旧规则处理。
5.4 真机访问后端失败与明文流量限制
现象:真机安装 App 后,资讯列表加载失败,logcat 里出现 CLEARTEXT communication 相关的异常。
原因:Android 9.0 及以上默认禁止明文 HTTP 流量,而毕设后端大多是本地 HTTP 服务,没有上 HTTPS。代码层面访问的是 192.168.x.x:8080 这类地址,直接被系统拦掉。
解决:在 AndroidManifest.xml 的 application 节点加一行:
<application android:usesCleartextTraffic="true" android:networkSecurityConfig="@xml/network_security_config">其中 networkSecurityConfig 是可选方案,只放行特定域名比全局放行更安全,对毕设来说直接 usesCleartextTraffic 最简单。改完后需要重新构建安装,不是简单重启 App 就行。另外真机的网络请求地址要从模拟器的 10.0.2.2 改成电脑的局域网 IP,手机上和服务端保持同一 Wi-Fi。这个点几乎每个跑这份源码的人都会遇到,答辩前建议提前在演示手机上配好。
5.5 模拟器访问本机服务的地址映射
现象:用 Android 模拟器运行,接口地址写 localhost 或 127.0.0.1,请求始终失败,后端明明已经启动了。
原因:模拟器里的 localhost 指向模拟器自己,而不是开发机。开发机上跑着后端服务,模拟器访问要通过固定映射:10.0.2.2 指向宿主机的 127.0.0.1。
解决:把客户端里的 baseUrl 从 http://localhost:8080 改成 http://10.0.2.2:8080 即可。如果你用的是真机加 adb 调试,也可以执行adb reverse tcp:8080 tcp:8080,这样手机上的 localhost:8080 会转发到电脑的 8080 端口,就不用改 IP 了。注意 adb reverse 在设备重启后失效,需要重新执行。这个技巧对演示场景特别好用,现场不用切 Wi-Fi,只要连上 USB 就能用。
| 问题 | 核心原因 | 解决入口 |
|---|---|---|
| gradlew 运行报错 | 平台脚本名/权限问题 | 用 gradlew.bat 或 chmod +x |
| Gradle 下载卡住 | 官方源访问不稳定 | 换阿里云镜像仓库 |
| SDK 版本找不到 | 本机 SDK 与工程版本不一致 | 安装对应 API Level 或改 compileSdkVersion |
| 真机访问失败 | 明文流量被拦截 | 开 usesCleartextTraffic |
| 模拟器连不上服务 | localhost 指向错误 | 用 10.0.2.2 或 adb reverse |
6. 从跑通到会改:给答辩演示设计一套可替换的配置
对毕设来说,项目跑通只是及格,答辩时最好能展示出你会改代码。第一个常改的地方是接口地址。源码里很多地方直接写死了 baseUrl,找起来费劲。我一般会先建一个常量类:
public class ApiConfig { // 模拟器用 10.0.2.2,真机改成电脑局域网 IP public static final String BASE_URL = "http://10.0.2.2:8080"; public static final String NEWS_LIST = BASE_URL + "/api/news/list"; public static final String TASK_LIST = BASE_URL + "/api/task/list"; }然后在网络层统一引用 ApiConfig,而不是在 Activity 里拼字符串。这样换环境时只改一个文件。改的时候注意,地址末尾不要带斜杠,拼接接口路径时保持一致,否则后端路由解析会 404。把地址抽出来之后,你会发现测试真机时换个 IP 只需要 30 秒,比满工程搜索 localhost 高效得多。
答辩前换上自己的包名和应用名是常规操作。应用名在 res/values/strings.xml 里改 app_name。包名不建议手动换,流程是:在 Android Studio 里右键包名,Refactor → Rename,选 Rename package 和 applicationId,改完后再检查一遍 AndroidManifest 里的 Application 和 Activity 声明。如果需要脚本批量处理,可以这样替换 Java 目录下的包声明:
# 把 com.example.campus 替换成 com.yourname.campus,注意带原后缀避免误伤子包 find app/src/main/java -type f -name "*.java" -exec sed -i '' 's/com\.example\.campus/com.yourname.campus/g' {} + # 同步替换 manifest 中的包引用 sed -i '' 's/com\.example\.campus/com.yourname.campus/g' app/src/main/AndroidManifest.xmlsed -i 后面的空字符串参数是 macOS 的用法,Linux 上要写 sed -i 不带后缀。替换完必须重新完整构建,因为 R 文件包名和资源的引用是编译期生成,漏一处就会找不到类。这里有个血泪经验:曾经只改了 java 目录没改 manifest,结果安装时报 INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,排查了半天才发现是包名不一致。
最后说答辩验证的事。我一般建议按三条链路走一遍:第一条是数据链路,后端启动、数据库导入、接口能返回 JSON;第二条是客户端链路,App 登录、资讯列表、任务新增、团队派单各走一遍,确保没有崩溃;第三条是异常链路,断网或后端停掉时,App 有没有友好提示,防止答辩现场因为服务没启动而白屏。从那以后我每次改完代码都强制走一遍命令行构建,用最干净的包去演示,因为 IDE 增量编译偶尔会留下旧资源,命令行构建能暴露真实问题。这个习惯也分享给你,希望帮到你。
本文还有配套的精品资源,点击获取