基于Android+XAMPP+MySQL的家校互动平台:从CS架构到联调避坑全指南
简介基于AndroidXAMPPMySQL的家校互动平台项目包面向计算机相关专业学生和移动开发爱好者目标是解决在家校沟通场景中搭建客户端、服务端与数据库协同工作的完整流程问题。项目采用C/S架构前端为Android应用后端基于XAMPP集成环境运行使用MySQL存储家长与学校的互动数据可从零理解账号管理、消息推送、后台维护等模块。压缩包整体约151.66MB涵盖项目全套源码与完整设计文档源码经测试校正可百分百成功运行便于读者直接导入开发环境进行编译与部署同时可根据文档梳理数据表结构、接口调用及界面布局适合作为毕业设计或课程设计的参考蓝本。已有293人学习源码目录结构清晰配有详细说明文字对希望完整复现该平台并在此基础上扩展功能的开发者有较高参考价值。1. 当Android、XAMPP和MySQL放在同一个标题里这不是又一个“登录注册”Demo而是一条能落地的CS架构路线看到“基于AndroidXAMPPMySQL的家校互动平台”这个标题很多人的第一反应是“老、简单、课程设计味太重”。但把技术栈拆开看它其实是把CS架构、数据库设计、Android联网交互一次讲透的最小闭环Android只负责界面和交互XAMPP里的Apache和PHP承担业务接口MySQL管持久化数据家校互动则定义了“公告、留言、成绩、考勤”这类真实业务。这个组合能解决的问题很直接——用最小成本搭建一个可演示、可扩展、每层代码都能说清归属的完整应用。它适合正在做课程设计或毕业设计的学生也适合刚接触Android联网开发、想在前后端边界上补课的开发者。2. 先立架构再写代码Android、XAMPP、MySQL在CS模式下的三层分工与一次完整请求链路不少开发者的习惯是打开Android Studio就写界面写到一半才发现不知道数据从哪来。家校互动平台这种带账号、带角色权限、带消息流转的系统最忌讳的就是没有架构直接上手。CS架构在这里的价值不是“听起来正经”而是强制你把客户端、服务端、数据库拆成三个能独立替换的层每一层的职责一旦清晰后续所有接口设计和排错都会变得有据可循。2.1 CS架构的三层边界Android、ApachePHP、MySQL各管哪一块先明确标题里的“CS架构”是什么。它指Client/Server结构Android端是ClientXAMPP里跑起来的ApachePHPMySQL整体是Server。Server内部还可以再往下拆但站在Android的视角整个后端就是一个HTTP服务客户端只认URL和JSON。Android端是最轻的一层它只做三件事渲染界面、接收用户输入、把输入变成HTTP请求发出去。它不直接访问MySQL也不该在代码里出现数据库账号密码。很多初学者第一次翻车就是在这里——在Java代码里拼一个“jdbc:mysql://...”的字符串试图让手机直连数据库。这种做法在技术上不是完全不能通但它等于把数据库用户名密码发到了每一台手机上任何抓包工具都能看到而且MySQL默认也不允许远程主机直连。常见做法是Android拿着一个URL去请求Apache服务端返回什么JSON客户端就展示什么数据库对手机完全不可见。服务端是XAMPP里跑起来的ApachePHP。Apache负责接收HTTP请求按URL路径把请求交给对应的PHP脚本PHP脚本负责检查参数、连接MySQL、执行SQL、把结果整理成JSON返回。这一层是业务逻辑的归属地角色权限判断、参数校验、数据组装都在这里完成。为什么用PHP而不是在Android里直接拼数据因为服务端可以统一控制“谁能看什么”而客户端代码一旦发出去就不受你控制了。MySQL在最下面只管数据的持久化存储。它不关心请求从哪来也不关心Android界面长什么样只接受SQL语句并返回数据集。家校本里的用户、班级、公告、留言最终都要落到表里。三个层级分清楚之后整个系统变成一条单向调用链Android - ApachePHP - MySQL每一层都可以独立替换换数据库不影响Android代码换Android界面也不动后端逻辑。2.2 为什么选XAMPP而不是单独装MySQL环境一致性省下的时间会在排错时加倍还给你XAMPP是一键打包的集成环境把Apache、PHP、MySQL实际是MariaDB、phpMyAdmin装到了一起。标题里写的是MySQL很多初学者第一次打开XAMPP会发现数据库叫“MariaDB”以为是装错了。这里提前说清楚MariaDB是MySQL的分支兼容MySQL协议SQL语法和连接方式基本一致phpMyAdmin里操作方式也几乎相同你可以把它当成MySQL用不要在这个问题上纠结。为什么这个方案里普遍选XAMPP而不是手动装Apache和MySQL三个原因。第一环境一致性XAMPP的目录结构和配置文件位置是固定的换一台电脑、跟着教程走一遍就能把服务端跑起来这对需要演示和答辩的场景非常关键不至于因为本机环境特殊而在现场翻车。第二排错半径小服务起不来、端口被占、配置文件改错XAMPP的控制面板会把Apache和MySQL的状态直接显示出来问题出在服务端还是客户端一眼就能区分。第三phpMyAdmin提供了可视化建表和查数工具调试接口时能直接在网页上看表内容比在命令行里敲SQL直观得多。但这个选择也有明确的边界。XAMPP默认配置面向开发环境Apache的并发能力有限MySQL的默认配置也没有针对高并发调优所以它适合课程设计、校内实训、小规模快速原型不适合直接拿去做公网生产服务。如果你将来要把这个家校互动平台真正部署给一所学校用需要换成云服务器上的正式环境、上HTTPS、做并发和备份策略。但在“把方案跑通、把逻辑讲清”这个阶段XAMPP就是性价比最高的选择。2.3 数据怎么流动一次“家长查公告”的完整请求链路把架构落到具体场景里看一次普通的“家长查询公告列表”操作完整链路是这样的家长打开App的公告页Android根据当前用户班级信息拼出请求URL例如http://192.168.1.100:8080/notice_list.php?class_id3。Android通过OkHttp或HttpURLConnection发起GET请求这个请求经过Wi-Fi路由到达电脑的Apache端口。Apache收到请求根据URL后缀notice_list.php找到对应PHP文件交给PHP解释器执行。PHP脚本先加载数据库连接配置校验参数class_id是否合法然后执行参数化SQL查询。MySQL执行查询把命中的公告记录返回给PHPPHP逐条读取并组装成JSON数组。PHP通过响应头把JSON返回给ApacheApache再原样发送给Android。Android收到响应后判断状态码和业务码解析JSON把公告标题刷新到RecyclerView上。这条链路里每一步都可能出问题。Android拼错URL请求到不了ApacheApache端口被占服务根本起不来PHP的SQL写错返回不了数据JSON字段名和Android解析不一致页面就显示空白。所以后面做联调时我习惯先把第5步和第6步用浏览器或curl单独验证确认接口通了再去查Android端的问题否则很容易在两层之间来回猜。这个排查顺序后面第6章还会再展开。3. 数据层落地MySQL表结构设计与PHP REST接口的最小可用实现架构立住之后动手顺序应该是先做数据层再做接口层最后写Android界面。很多团队把这个顺序反过来结果就是界面写完了等接口接口写完了发现表不对表改了接口又得跟着改来回返工。正确做法是先想清楚“系统里有哪些实体、实体之间什么关系”把表建好接口和界面就都有了依据。3.1 四张核心表用户、班级、公告、留言家校互动平台听着功能很多通知公告、班级留言、考勤反馈、成绩查询但落到数据上核心其实就四类实体谁在使用系统、用户属于哪个班级、系统要发布什么通知、用户之间产生了什么消息。围绕这四类实体把表建好剩下的功能都是在这些表上做组合查询。以下是一套最小可用的表设计角色用role字段区分班级用class_id关联。target_class_id为0时表示公告面向全部班级这是家校本里很常见的一种设计能少建一张“公告与班级关系表”对初期项目来说足够实用。表名职责关键字段classes年级班级信息id, grade_name, class_name, head_teacher_idusers用户、角色、所属班级id, username, password_hash, role, real_name, class_idnotices教师或管理员发布的公告id, title, content, publisher_id, target_class_id, created_atmessages家长与教师之间的留言id, from_user_id, to_user_id, content, created_at, is_readusers.role用枚举值区分admin、teacher、parent、student四种角色比用数字更直观phpMyAdmin里也能直接看到含义。classes.head_teacher_id存的是users表里某位教师的id初期可以不设外键约束靠应用层保证数据正确降低建表失败的概率。以下给出users和notices两张表的建表SQL作为初始化脚本的起点CREATE TABLE classes ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, grade_name VARCHAR(20) NOT NULL COMMENT 年级如2024级, class_name VARCHAR(20) NOT NULL COMMENT 班级如3班, head_teacher_id INT UNSIGNED NULL DEFAULT NULL COMMENT 班主任用户ID, PRIMARY KEY (id), UNIQUE KEY uk_grade_class (grade_name, class_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE users ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password_hash VARCHAR(255) NOT NULL COMMENT password_hash()生成, role ENUM(admin,teacher,parent,student) NOT NULL DEFAULT parent, real_name VARCHAR(50) NOT NULL, class_id INT UNSIGNED NULL DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE notices ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, title VARCHAR(100) NOT NULL, content TEXT NOT NULL, publisher_id INT UNSIGNED NOT NULL, target_class_id INT UNSIGNED NOT NULL DEFAULT 0 COMMENT 0表示全校, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_target_class (target_class_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;建表时有三个细节值得注意。一是字符集统一用utf8mb4而不是utf8防止家长留言里出现特殊字符或表情时插入失败。二是created_at用DATETIME DEFAULT CURRENT_TIMESTAMP让数据库自动写时间避免PHP和Android各自传时间导致格式不统一。三是password_hash字段长度给到255因为后面要用的password_hash()函数生成的字符串长度不固定太短会直接报错。messages表结构与此类似字段为from_user_id、to_user_id、content、created_at、is_read加一个idx_to_user_read索引即可支撑“查询我收到的未读留言”这类高频查询。初期不要再加更多冗余字段等接口真正需要时再补表结构太复杂只会让接口调试变慢。3.2 第一个PHP接口连接配置与JSON输出格式表建好后先写一个所有接口都会用到的数据库连接配置把字符集、时区、JSON输出函数统一收口。这样后面每写一个接口只需要require_once config.php不用每张表重复拼连接代码。以下是一个最小可用的config.php?php // 统一响应格式与数据库连接所有接口文件都引入本文件 header(Content-Type: application/json; charsetutf-8); mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT); $conn new mysqli(127.0.0.1, root, , school_platform, 3306); $conn-set_charset(utf8mb4); date_default_timezone_set(Asia/Shanghai); function json_out($code, $msg, $data null) { echo json_encode([ code $code, msg $msg, data $data ], JSON_UNESCAPED_UNICODE); exit; } ?这段代码做了几件事第一行强制所有接口的响应都按JSON格式输出并用UTF-8编码这是中文不乱码的关键之一第二行开启mysqli的严格报错模式SQL写错了会直接把错误抛出来而不是返回空结果让Android端猜第三行建立数据库连接school_platform就是数据库名请改成你自己建的库名。json_out()函数统一封装返回格式所有接口返回体都是code、msg、data三个字段Android端解析JSON时只需要处理这一套结构。有了配置再写公告列表接口就很快。这个接口接收一个可选的class_id参数返回该班级可见的公告列表?php require_once config.php; // class_id 允许为空空值或0表示查询全校公告 $classId isset($_GET[class_id]) ? intval($_GET[class_id]) : 0; $sql SELECT id, title, publisher_id, created_at FROM notices WHERE target_class_id 0 OR target_class_id ? ORDER BY created_at DESC LIMIT 20; $stmt $conn-prepare($sql); $stmt-bind_param(i, $classId); $stmt-execute(); $result $stmt-get_result(); $list []; while ($row $result-fetch_assoc()) { $list[] $row; } json_out(0, ok, $list); ?这里必须说明两个关键选择。第一为什么用prepare预编译而不是把变量直接拼进SQL串因为class_id来自客户端请求是可被篡改的输入直接拼接等于把SQL注入漏洞开给所有调用者。用bind_param(i, $classId)绑定参数i表示整数类型既省去了手工转义的麻烦也堵住了注入路径。第二为什么LIMIT 20家校互动平台的公告会随时间增长一次返回全部数据会让列表越来越慢分页是迟早要做的事。初期写死LIMIT 20Android端上拉加载时再传page和offset就是后面的扩展点。把notice_list.php放到XAMPP的htdocs目录下浏览器访问http://127.0.0.1:8080/notice_list.php?class_id1能看到JSON返回就说明服务端链路通了。如果没有返回数据先检查school_platform库里是否导入了测试数据再检查mysqli连接的用户名密码是否和config里一致这两个小问题占了新环境调试的大多数时间。3.3 登录接口与token校验为什么不能用纯MD5最小可用方案怎么做登录接口是家校互动平台的入口也是安全上最容易踩坑的地方。很多老教程里直接存MD5密码甚至明文密码理由是本地演示不需要那么安全。但家校本里有家长手机号、学生成绩、考勤记录一旦数据库泄露或被同学抓包后果就不只是课程设计扣分的问题了。最小可用且正确的做法是PHP原生的password_hash()和password_verify()。注册或初始化用户时用password_hash($password, PASSWORD_DEFAULT)生成密码哈希存入users.password_hash字段。登录时取出该字段用password_verify()比对?php require_once config.php; $input json_decode(file_get_contents(php://input), true); $username trim($input[username] ?? ); $password $input[password] ?? ; $sql SELECT id, role, real_name, class_id, password_hash FROM users WHERE username ?; $stmt $conn-prepare($sql); $stmt-bind_param(s, $username); $stmt-execute(); $user $stmt-get_result()-fetch_assoc(); if ($user password_verify($password, $user[password_hash])) { // 登录成功生成一次性 token 并返回给客户端 $token bin2hex(random_bytes(16)); // 实际项目中把 token 写入 token 表或存到 session // 这里先返回 tokenAndroid 端后续请求带上即可 json_out(0, ok, [ token $token, role $user[role], real_name $user[real_name], class_id $user[class_id] ]); } else { json_out(1, 用户名或密码错误); } ?password_verify()会自动识别哈希里的算法标识不需要你关心具体是bcrypt还是argon2。bin2hex(random_bytes(16))生成一个32位的随机十六进制字符串作为tokenToken本身是随机的不携带用户信息就算被截获也无法反推出密码。请把生成token后的持久化逻辑补上常见做法是建一张user_tokens表字段为user_id、token、expire_at每次请求时校验token是否存在且未过期。对课程设计来说把token存到$_SESSION里也能跑通但Session在移动端使用体验差还是建议用token表。这里多提一句如果你在别人的源码里看到“md5(md5($password))”这种写法不要照着抄进自己的项目。MD5固定散列在撞库工具面前几乎是透明的这不是“增加几层循环”能解决的事。安全最终目标是让攻击者即使拿到数据库也拿不到明文密码password_hash就是PHP官方给出的那条路径不需要你自己发明算法。4. Android端对接登录、token与公告列表的实现细节服务端接口跑通之后Android端的工作就变成了“发请求、收JSON、渲染界面”三件事。但这三步里有不少细节网络线程、超时时间、JSON字段映射、明文HTTP限制每一个单独看都不难串在一起就能把一个新手卡住一整天。4.1 网络层选型OkHttpGson还是HttpURLConnectionAndroid发HTTP请求有两种常见路线一种是系统自带的HttpURLConnection一种是OkHttp加Gson的组合。做家校互动平台这种有登录态、有列表展示、后续还要上传图片的项目我建议直接用OkHttpGson而不是用HttpURLConnection自己拼解析逻辑。HttpURLConnection的优势是不引入第三方依赖但它的API偏底层连接池、超时重试、JSON解析都要自己写代码量不小且容易出边界问题。OkHttp把连接池、超时、拦截器都封装好了Gson一行代码就能把JSON字符串映射成Java对象这套组合是Android社区里最主流的做法遇到问题也最容易搜到解决方案。在build.gradle的dependencies里加上依赖dependencies { // 版本号请按你创建项目时的最新稳定版为准 implementation com.squareup.okhttp3:okhttp:4.x implementation com.google.code.gson:gson:2.x }加完依赖记得Sync一下。OkHttp 4.x是Kotlin写的但对Java完全兼容直接按Java方式调用即可。Gson版本不用刻意追新稳定版就够用。如果你在Android 6以下的设备上测试还需要在AndroidManifest.xml里声明INTERNET权限Android 6及以上安装时自动授权但清单里仍要声明这条权限uses-permission android:nameandroid.permission.INTERNET /漏掉这个权限时运行时不会崩但所有网络请求都会走onFailure回调错误信息是“Permission denied”排查时容易走弯路。4.2 登录请求与token存储主线程与回调必须分开登录页是最典型的联网场景。用户输入用户名密码点击登录Android把数据POST到login.php拿到token后存起来跳转到首页。下面这个示例只保留核心逻辑重点是展示OkHttp的调用方式和token保存方式private void doLogin(String username, String password) { OkHttpClient client new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .build(); RequestBody body new FormBody.Builder() .add(username, username) .add(password, password) .build(); Request request new Request.Builder() .url(http://192.168.1.100:8080/login.php) .post(body) .build(); client.newCall(request).enqueue(new Callback() { Override public void onFailure(Call call, IOException e) { // 网络异常主线程弹Toast提示 } Override public void onResponse(Call call, Response response) throws IOException { String json response.body().string(); // 用Gson解析拿到code和data // 如果code0把token存到SharedPreferences // 然后切换到主线程跳转首页 } }); }这里有两个必须记住的约束。第一OkHttp的enqueue()本身就是异步的回调方法运行在子线程所以不能在回调里直接更新UI或弹Toast必须通过runOnUiThread()或Handler切回主线程。Android对不能在主线程做网络请求这件事是强制检测的直接在UI线程里调用execute()会抛NetworkOnMainThreadException。第二超时时间的设置方式是Builder链式调用三个超时分别针对连接建立、读取响应、写入请求。本机联调时10秒绰绰有余如果手机连的是公网服务器或弱网环境可以放宽到15到20秒太短会误伤慢网络太长会让用户一直盯着空白页。Token保存用SharedPreferences最简单登录成功后写入getSharedPreferences(app, MODE_PRIVATE)之后每次请求都在HTTP请求头里加Authorization: Bearer tokenPHP端在受保护的接口里校验这个token。不要把token保存在数据库或文件里SharedPreferences对当前项目的安全级别已经足够等做到生产级别再上加密存储。4.3 公告列表Gson泛型解析与空态处理公告列表页是APP里最常见的“列表加载”场景。服务端返回的JSON结构长这样{ code: 0, msg: ok, data: [ { id: 1, title: 期中考试安排通知, publisher_id: 3, created_at: 2025-05-12 09:30:00 } ] }Android端定义一个Notice类字段名要和JSON里的key一一对应。Gson解析data数组时因为泛型擦除问题不能直接写new TypeTokenListNotice(){}.getType()以外的写法。常见的错误是把data直接强转成ListNotice运行时报ClassCastException原因就是没告诉Gson内部元素的具体类型Gson gson new Gson(); JsonObject root gson.fromJson(json, JsonObject.class); int code root.get(code).getAsInt(); if (code 0) { Type listType new TypeTokenListNotice(){}.getType(); ListNotice notices gson.fromJson(root.get(data), listType); // 更新RecyclerView的Adapter数据 }字段名映射是另一个高频坑。PHP端返回的字段是created_atJava类里如果定义成createdAtGson默认反射匹配时会因为名字对不上而赋null程序不报错页面就显示空。解决方式有两个把Java字段名改成和JSON完全一致比如就用created_at或者在Java字段上加SerializedName(created_at)注解。我建议直接改Java字段名代码可读性更好也少一处映射关系。下拉刷新时清空列表数据再重新加载注意notifyDataSetChanged()要等数据源更新完后调用否则会出现刷新后旧数据残留的视觉问题。空态处理是列表页最容易被忽视的部分。MySQL里一张空表接口返回data为空数组Android端解析后List为空此时界面上如果只显示一个空白RecyclerView用户会以为App坏了。在做项目演示前至少要处理两种空态一种是接口正常但没有任何数据显示一个居中TextView提示“暂无公告”另一种是网络失败显示“加载失败点击重试”。这两种状态用ListView或RecyclerView搭配View切换即可代码量不大但对答辩演示和实际体验的影响非常大。5. 联调避坑实录连不上、乱码、明文HTTP被拦这五个问题占掉90%排错时间这个标题之所以值钱是因为它踩过的坑全在“联调”这两个字里。单个看Android、XAMPP、MySQL都没问题一旦把它们串起来环境差异、编码差异、端口冲突全冒出来了。下面五条是我做这类项目时反复踩过的每一条都按现象、原因、解决三层说清楚希望能帮你把排错时间从三天压缩到三小时。5.1 连不上模拟器能通真机死活连不上现象Android模拟器里访问http://10.0.2.2:8080一切正常换成自己的手机安装同一个APK请求全部超时。原因10.0.2.2是Android模拟器专门用来访问宿主机也就是你电脑的保留别名真机上根本不存在这个地址。真机要访问你电脑上的XAMPP服务必须使用电脑在局域网里的真实IP比如192.168.1.100而且手机和电脑必须连着同一个Wi-Fi或同一个局域网。除此之外电脑的防火墙默认会拦截外部设备对Apache端口的访问真机请求到了防火墙这一层就会被丢掉。解决把接口地址统一收敛到一个常量里比如在Java里定义一个BASE_URL字段全部替换成http://192.168.1.100:8080。获取电脑IP在Windows上执行ipconfig在macOS或Linux上执行ifconfig找到IPv4地址填进去。防火墙处理上Windows可以在控制面板的防火墙设置里允许Apache的专用网络访问或者用管理员权限执行一条命令开放端口比如开放8080端口供局域网访问。改完后用手机浏览器直接访问http://192.168.1.100:8080/notice_list.php能出JSON再装App能省掉大量反复安装调试的时间。5.2 中文乱码数据库正常接口返回“???”现象phpMyAdmin里看数据表中文显示完全正常浏览器访问PHP接口中文变成一堆问号或乱码Android端接到的数据同样乱码。原因这是一条链路上的多数组装问题任何一个环节编码不一致都会出乱码。常见的有三种PHP文件本身被存成了非UTF-8编码mysqli连接MySQL时没指定字符集MySQL表或字段是latin1字符集。这三个问题只要有一个存在中文就可能在“PHP - MySQL - JSON - Android”的某一跳上坏掉。解决按顺序排查。第一步PHP文件一律用UTF-8无BOM编码保存特别是config.php它定义了所有接口的响应头。第二步MySQL连接后执行$conn-set_charset(utf8mb4)确保读写都走UTF-8这一步在config.php里已经写进去了从自己代码里搜一下是否被删掉。第三步建表时所有表都用utf8mb4如果表已经建成了用ALTER TABLE notices CONVERT TO CHARACTER SET utf8mb4;转换。最后接口响应头加上header(Content-Type: application/json; charsetutf-8)Android端用response.body().string()拿到的是原始UTF-8字符串不要手动再转码。这四步做到位中文乱码基本绝迹。5.3 Android 9明文HTTP被拦请求没出错但报CLEARTEXT现象App装到Android 9及以上的手机或模拟器上所有http://开头的接口请求都失败Logcat里能看到CLEARTEXT communication to 192.168.1.100 not permitted by network security policy。原因Android 9开始系统默认禁止应用使用明文HTTP流量只有https://才能直接访问。XAMPP默认跑的是HTTP所以请求在系统安全策略这一层就被拦下了不是代码逻辑的问题。解决开发调试阶段最直接的办法是在AndroidManifest.xml的application标签上加android:usesCleartextTraffictrue表示允许整个应用使用明文流量。这一步做完重新编译安装请求立刻恢复。但要注意这个属性会让所有HTTP流量都放行生产环境不应这样用。更规范的做法是配置networkSecurityConfig只对指定的开发域名放行明文流量Android官方文档有完整示例这里不做展开。对于课程设计或本地演示usesCleartextTraffictrue是效率最高的选择等部署到正式环境、换成HTTPS后再收紧。5.4 时间差8小时数据库时间对App显示少8小时现象MySQL里created_at显示2025-05-12 09:30:00浏览器里看接口也是这个值但Android端显示2025-05-12 01:30:00整整差了8小时。原因这个8小时差通常不在MySQL而在Android端解析时间时用了系统时区。中国大陆是UTC8默认时区在北京时区时不会出错很多人第一次遇到是因为手机或模拟器时区被设成了非中国时区或者代码里用SimpleDateFormat没指定时区直接按设备默认时区解析时间就偏了。解决两个方向都能修。服务端方向已在config.php里写了date_default_timezone_set(Asia/Shanghai)确保PHP输出的时间就是东八区时间。客户端方向解析created_at时建议用带时区的格式化方式在SimpleDateFormat里套上TimeZone.getTimeZone(GMT08:00)而不是依赖设备当前时区。另一个更稳妥的做法是让PHP接口直接返回时间戳比如用strtotime($row[created_at])把created_at转成整型时间戳Android端解析成Date后自己决定格式化样式这样数据库怎么存都不影响展示。初期项目推荐后一种时间显示问题一旦做进协议层后面所有接口都能收益。5.5 端口被占XAMPP启动面板Apache或MySQL红着现象打开XAMPP控制面板Apache或MySQL的状态不是绿色而是红色偶尔Apache是绿的但浏览器访问http://127.0.0.1却进了另一个服务的页面。原因80端口和3306端口是很多软件的默认占用目标。Apache默认监听80端口如果有IIS、某些网盘客户端或其他Web服务已经把80占了Apache就起不来。MySQL的3306同理本机如果装了其他数据库服务端口冲突会让MySQL启动失败。解决xampp控制面板上直接修改端口Apache的httpd.conf里把Listen 80改成Listen 8080同时把ServerName localhost:80改成localhost:8080MySQL端口如果冲突在my.ini里改port3307。改完后所有接口URL里的端口都要对应调整。排查占用情况时Windows上执行netstat -ano | findstr :80能看到占用PID再去任务管理器确认是哪个程序macOS上执行lsof -i :80。这是典型的一次配置改三处的问题改了Apache端口却忘了改Android端的BASE_URL也会让联调继续失败所以端口修改后一定要全局搜索一遍旧端口号。6. 从能跑到能用接口验证顺序与三个值得做的进阶动作项目能跑和能演示之间隔着一条验证链条。我见过太多人写完代码直接装App页面白屏就开始改Android代码最后发现是服务端接口压根没通。正确的验证顺序一定是先验证接口再接入客户端。用浏览器或curl直接访问http://127.0.0.1:8080/notice_list.php?class_id1看到合法JSON后再往下走。如果是POST接口用curl模拟一次curl -X POST http://127.0.0.1:8080/login.php \ -H Content-Type: application/json \ -d {username:parent01,password:123456}返回里能拿到code、token、role说明服务端正常此时Android端再报错问题一定在客户端解析或权限配置上。维护一份接口文档也相当值当每写完一个接口就记录URL、请求参数、返回示例这份文档既是给自己留的排错地图也是这类项目交付时“文档”部分最实在的内容。接口定义表里写清字段名和类型能避免Android端一个字段名对不上而排查半天。三个进阶动作比较值得做。第一上HTTPSXAMPP自带Apache的SSL模块配置一张自签证书后Android端访问https://地址即可这是从开发环境走向真实环境的必经一步。第二把token改成带过期时间的方案在user_tokens表里加expire_at字段每次请求校验有效期过期要求重新登录这比“永久有效token”更接近生产标准。第三把本地XAMPP迁到云服务器时导出SQL、上传PHP文件、改数据库连接配置、在云控制台放行端口这几步做完同一个App就可以从手机远程访问演示的说服力完全不一样。我早期做这类项目总是先写界面再调接口结果有一次在答辩现场Demo翻车原因不是App崩溃而是服务端压根没启动页面一直转圈。后来我固定了习惯先建表再写接口用curl把每个接口验一遍最后才写Android页面每次换机器或换网络先用手机浏览器访问一遍接口地址再装App。这套流程看着慢实际是整个项目里最省时间的部分。希望这些踩坑记录能帮你在做家校互动平台这个方向时少走几段弯路也祝你把这条CS架构路线真正跑通、讲透。本文还有配套的精品资源点击获取