简介:这是一份面向Android开发学习者与毕业设计/课程设计人员的家校互动平台项目资料,基于Android客户端、XAMPP服务端与MySQL数据库实现,采用CS架构完成家校通知、成绩查询、互动留言等核心功能,适用于相关项目设计及Android与服务端联调场景。压缩包约151.66MB,包含Android客户端源代码、XAMPP服务端部署文件、MySQL数据库脚本以及完整设计文档,可支撑从环境部署到功能调试的全过程。已有293人学习下载,适合正在开展相关项目设计或希望掌握Android+PHP+MySQL技术栈的读者。其中项目源码经测试校正、运行可靠,配套文档覆盖需求分析、数据库设计及模块实现思路,能帮助读者快速上手项目,降低从零搭建与排错的成本。
1. 基于Android+XAMPP+MySQL的家校互动平台:源码资源到底能不能跑
做Android课程设计或者毕业设计,很多人卡在同一个地方:App界面画出来了,但数据从哪来?老师的通知怎么推给学生?家长怎么查作业?这套基于Android+XAMPP+MySQL的家校互动平台,正好解决这个数据链路问题。它采用CS架构,手机端是独立的Android应用,服务端跑在XAMPP的Apache+PHP环境里,数据落在MySQL,Android端通过HTTP请求调用PHP接口读写数据库。说白了,这就是一个前后端分离的移动应用骨架,把登录、消息、作业、通知这类家校场景串成了一条能跑通的业务线。
这套资源适合三类人:一是做课程设计需要完整可演示项目的学生,二是想搞明白Android怎么跟PHP服务端联调的人,三是想拿现成源码改造成自己项目的开发者。它最大的价值不是某个算法多高明,而是把Android客户端、PHP中间层、MySQL存储三层全部打通,附带的文档还能直接当设计说明书的底稿。在下手之前,我建议你先把我后面讲的环境搭建、联调顺序、踩坑记录过一遍,能省很多时间。
2. 先把服务端跑起来:XAMPP环境搭建与PHP接口设计
这套平台的根基在服务端,Android端只是个“壳”,数据全靠PHP接口从MySQL里取。所以动手第一步,不是打开Android Studio,而是先把XAMPP这套环境装好、跑通。
2.1 XAMPP安装与Apache端口冲突排查
XAMPP这东西就是把Apache、PHP、MySQL、phpMyAdmin打成一个包,装完就能在本地拉起一个完整的Web服务环境。到官网下载对应你操作系统的版本,Windows装完一般在C:\xampp,Linux装完在/opt/lampp。
装完启动XAMPP Control Panel,按顺序启动Apache和MySQL两个服务。这里有一个超高频的翻车点:Apache的80端口被系统或其他程序占用了。最常见的是IIS、Nginx,或者某些软件自己带了Web服务。
提示:启动失败时,先点击Apache行的“Logs”,打开error.log查看具体端口冲突信息。确认是端口问题后,可以改Apache的监听端口。
改端口的方式:打开C:\xampp\apache\conf\httpd.conf,找到Listen 80,改成Listen 8080。同时要把httpd-ssl.conf里的Listen 443一并改掉,避免HTTPS端口也冲突。这样改完,你访问服务端的地址就要带上端口号,比如http://192.168.1.100:8080。Android端在模拟器里访问宿主机时,地址里也要带上这个改过的端口。
MySQL端口默认3306一般不会被占,但如果你本机装了独立的MySQL服务,XAMPP里的MySQL会启动失败。这种情况下,要么停掉本机MySQL服务,要么把XAMPP的MySQL改成3307端口,在C:\xampp\mysql\bin\my.ini里改port=3307。
2.2 PHP接口的三层设计:入口、业务、返回格式
这套资源的PHP接口文件,结构上大致分为三类:数据库连接文件、业务处理文件、公共返回封装。建议拿到源码后先按这个思路把目录理一遍,不要着急去读每一个文件。
先说数据库连接,典型的写法是一个db.php,用mysqli做连接:
<?php // 数据库连接配置 $host = 'localhost'; $user = 'root'; $pass = ''; $dbname = 'school_platform'; // 连接MySQL $conn = new mysqli($host, $user, $pass, $dbname); if ($conn->connect_error) { die(json_encode(['code'=>500, 'msg'=>'数据库连接失败'])); } // 统一UTF-8,防止中文乱码 $conn->set_charset('utf8mb4'); ?>这段代码里有几个关键点。host用localhost是因为PHP和MySQL跑在同一台机器上,属于本地回环连接,不走网络协议栈。user和pass默认是root和空密码,这是XAMPP安装后的默认值。set_charset('utf8mb4')这行极其关键,如果你在Android端查出来的中文是乱码,八成是这里没设置字符集,或者数据库表的字符集不是utf8mb4。
再看一个登录接口的典型实现:
<?php require_once 'db.php'; // 接收Android端POST过来的参数 $username = $_POST['username'] ?? ''; $password = $_POST['password'] ?? ''; if (empty($username) || empty($password)) { echo json_encode(['code'=>400, 'msg'=>'参数不能为空']); exit; } // 查询用户 $sql = "SELECT id, username, role FROM users WHERE username=? AND password=?"; $stmt = $conn->prepare($sql); $stmt->bind_param("ss", $username, $password); $stmt->execute(); $result = $stmt->get_result(); if ($row = $result->fetch_assoc()) { echo json_encode(['code'=>200, 'msg'=>'登录成功', 'data'=>$row]); } else { echo json_encode(['code'=>401, 'msg'=>'用户名或密码错误']); } $conn->close(); ?>这里必须说明两点。第一,prepare预处理是必须的,不要用字符串拼接SQL,否则SQL注入风险很高。第二,password直接存明文是课程设计常见的简化做法,但如果你要拿这套东西做真实项目,建议改成password_hash和password_verify,否则数据库一旦泄露,所有账号密码全暴露。
返回格式统一是code + msg + data三段式。code=200表示成功,400是参数错误,401是认证失败,500是服务端异常。Android端解析JSON时只需要判断code字段,就可以决定弹Toast还是跳转页面,不用每个接口单独写一套解析逻辑。
2.3 用Postman或浏览器先验证接口
写完接口不要急着联调Android端,先用浏览器或Postman把接口自测一遍。打开浏览器访问http://localhost:8080/api/login.php,如果能返回参数为空的JSON,说明Apache和PHP已经正常工作了。
然后用Postman模拟POST请求,在Body里填username=teacher01&password=123456,点击Send。看返回结果是不是预期的JSON。这一步的价值在于:把网络层问题和服务端逻辑问题隔离开。如果Postman里能通,说明服务端没问题,后面Android端连不上,问题一定在Android端或者网络配置上。
注意:在Windows的Apache和Linux的Apache下,PHP对POST数据的解析没有区别。但如果你用GET方式传参,URL里不要出现中文,浏览器和App对中文URL的编码方式不一致,会出现参数丢失或乱码。
3. Android端怎么对接:从网络权限到JSON解析
服务端接口跑通之后,真正的战斗才刚开始。Android端这边需要处理网络权限、子线程访问网络、JSON解析、UI更新这几件事。很多课程设计翻车就翻在这一步:HTTP请求写在了主线程,一执行就闪退。
3.1 网络权限与HTTP明文流量配置
Android 6.0(API 23)以后,访问网络需要动态权限;Android 9.0(API 28)以后,默认禁止明文HTTP流量。这套家校互动平台用的是http://协议访问XAMPP服务端,所以必须做两件事。
先在AndroidManifest.xml里加权限声明:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />然后在res/xml/目录下创建一个network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">192.168.1.100</domain> <domain includeSubdomains="true">10.0.2.2</domain> <domain includeSubdomains="true">localhost</domain> </domain-config> </network-security-config>再回到AndroidManifest.xml的application节点里引用它:
<application android:networkSecurityConfig="@xml/network_security_config" android:usesCleartextTraffic="true"> </application>这段配置解决的是一个问题:App允许访问非HTTPS的明文地址。domain-config里放的是你服务器IP,10.0.2.2是Android模拟器访问宿主机的专用IP,192.168.1.100是真机调试时你电脑的局域网IP。如果你只用模拟器调试,只需要10.0.2.2就够了;如果真机调试,必须是你电脑在局域网里的实际IP。
3.2 封装一个HttpUtils工具类
Android端我不建议每个Activity里都写网络请求代码,太散。按这个资源的场景,拿一个工具类来做集中封装就够用。下面是基于HttpURLConnection的POST请求封装,不依赖第三方库:
public class HttpUtils { private static final int CONNECT_TIMEOUT = 5000; private static final int READ_TIMEOUT = 5000; public static String postJson(String urlStr, String jsonBody) { HttpURLConnection conn = null; try { URL url = new URL(urlStr); conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setConnectTimeout(CONNECT_TIMEOUT); conn.setReadTimeout(READ_TIMEOUT); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); // 写入请求体 OutputStream os = conn.getOutputStream(); os.write(jsonBody.getBytes("UTF-8")); os.flush(); os.close(); int responseCode = conn.getResponseCode(); if (responseCode == 200) { InputStream is = conn.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } return sb.toString(); } else { return "{\"code\":" + responseCode + ",\"msg\":\"HTTP错误\"}"; } } catch (Exception e) { e.printStackTrace(); return "{\"code\":500,\"msg\":\"网络异常: " + e.getMessage() + "\"}"; } finally { if (conn != null) { conn.disconnect(); } } } }这个封装里有几个细节值得注意。setConnectTimeout和setReadTimeout都是5秒,课程设计场景这个值够用。Content-Type设置为application/json;charset=UTF-8,保证中文参数能正确传输。这里我默认传JSON字符串,如果你的PHP接口是用$_POST['username']接收参数的,就要做改造:PHP端用file_get_contents('php://input')读原始请求体再json_decode,或者Android端用application/x-www-form-urlencoded传键值对。两个技术方案,按源码里写的来选择。
注意:如果你发现PHP端
$_POST拿到的是空数组,基本上是Content-Type不匹配。PHP的$_POST只认表单格式的请求体,不认JSON字符串。这是新手最容易踩的深坑。
3.3 子线程请求与UI刷新
Android不允许在主线程执行网络请求,否则会抛NetworkOnMainThreadException。所以网络请求必须放到子线程,拿回结果后再切回主线程更新UI。
new Thread(new Runnable() { @Override public void run() { String url = "http://10.0.2.2:8080/api/login.php"; String json = "{\"username\":\"teacher01\",\"password\":\"123456\"}"; String result = HttpUtils.postJson(url, json); runOnUiThread(new Runnable() { @Override public void run() { try { JSONObject obj = new JSONObject(result); int code = obj.getInt("code"); if (code == 200) { // 登录成功,跳转主界面 Toast.makeText(MainActivity.this, "登录成功", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(MainActivity.this, obj.getString("msg"), Toast.LENGTH_SHORT).show(); } } catch (JSONException e) { e.printStackTrace(); } } }); } }).start();这种写法在课程设计里很常见,但有个致命问题:Activity销毁后,如果线程还在跑,runOnUiThread里的代码操作了一个不存在的界面,就会崩溃。更严谨的做法是用AsyncTask、Handler或者现在主流的ViewModel + LiveData做生命周期感知。但如果你是拿这套源码交作业,保持原有封装结构,只在界面退出时用一个标志位控制UI更新就可以。
private boolean isDestroyed = false; @Override protected void onDestroy() { super.onDestroy(); isDestroyed = true; } // 在runOnUiThread的回调里先行判断 runOnUiThread(new Runnable() { @Override public void run() { if (!isDestroyed) { // 更新UI操作 } } });这个isDestroyed标志位是线程安全问题的妥协方案,不算完美,但足够应对课程设计场景。它解决了Activity销毁后回调还在执行的问题,也是我见过很多项目里实际在用的处理方式。
3.4 解析JSON时注意嵌套结构
服务端返回的JSON结构是{code, msg, data}三层。有的接口data是一个对象,比如用户信息;有的接口data是一个数组,比如通知列表。解析方式要区分开:
// data为对象的解析 JSONObject data = obj.getJSONObject("data"); String username = data.getString("username"); String role = data.getString("role"); // data为数组的解析 JSONArray array = obj.getJSONArray("data"); for (int i = 0; i < array.length(); i++) { JSONObject item = array.getJSONObject(i); String title = item.getString("title"); String time = item.getString("create_time"); }这里最容易出的问题是在getString的时候抛JSONException,原因是PHP端某个字段返回了null,而Android端这里按字符串处理。PHP端返回数据前统一做一次空值处理比较稳妥,比如$row['title'] ?? '',保证每个字段至少是空字符串而不是null。
4. 家校业务核心:数据库表设计与数据流梳理
要说这套家校互动平台的核心价值,不在于Android端代码写得多花哨,而在于数据库表设计是否匹配真实家校场景。拿到源码后先别急着跑,把数据库的表结构和关联关系理清楚,后面改功能、加模块就顺手了。
4.1 用户角色与权限设计
家校互动平台至少有三种角色:管理员、教师、家长(或学生)。在数据库设计里,最常见的方式是单表加角色字段,而不是拆成三张用户表。这样做的好处是登录逻辑简单,坏处是可扩展性差。这个源码的场景,单表是合理的选择。
CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(255) NOT NULL, real_name VARCHAR(50) NOT NULL, role TINYINT NOT NULL DEFAULT 2 COMMENT '0=管理员,1=教师,2=家长', phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );role字段用TINYINT存数字,比字符串更省空间,查询效率也高。Android端拿到用户信息后,根据role值切换主界面。比如教师端看到的是发布作业和发通知的入口,家长端看到的是查看作业和接收通知的入口,这是家校平台的基本逻辑。
4.2 核心业务表:作业、通知、消息
一个完整可演示的家校平台,作业、通知、消息这三张表是必备的。这三张表撑起了整个App的数据来源。
-- 作业表 CREATE TABLE homework ( id INT AUTO_INCREMENT PRIMARY KEY, teacher_id INT NOT NULL, title VARCHAR(100) NOT NULL, content TEXT, deadline DATETIME, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (teacher_id) REFERENCES users(id) ); -- 通知表 CREATE TABLE notice ( id INT AUTO_INCREMENT PRIMARY KEY, publisher_id INT NOT NULL, title VARCHAR(100) NOT NULL, content TEXT, notice_type TINYINT DEFAULT 0 COMMENT '0=班级通知,1=学校通知', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (publisher_id) REFERENCES users(id) ); -- 消息表 CREATE TABLE message ( id INT AUTO_INCREMENT PRIMARY KEY, from_id INT NOT NULL, to_id INT NOT NULL, content TEXT, is_read TINYINT DEFAULT 0 COMMENT '0=未读,1=已读', create_time DATETIME DEFAULT CURRENT_TIMESTAMP );三张表设计上的共性是:都记录了操作者ID、内容、时间,以及一个状态字段。FOREIGN KEY做外键约束,保证了数据完整性。但这套源码在PHP删除用户时可能会因为外键约束导致失败,所以你如果改到用户删除功能,要先处理关联表的级联删除逻辑。
作业表里有一个容易被忽略的设计点:没有设计“提交作业”相关的表。也就是说,教师可以发布作业,但家长/学生能不能拍照上传提交,要看源码里是否单独做了提交表。如果没有,这个平台的重点更偏向通知和信息展示,作业提交只是单向发布。
4.3 从数据库到Android端的完整数据流
本位来把一次数据请求的全链路串一遍。教师在Android端填写作业标题、内容、截止时间,点“发布”。
这条请求走的路径是:Android端的Activity收集表单数据,打包成JSON,通过HttpUtils发送POST请求到PHP的homework_add.php;PHP脚本接收JSON,json_decode后拼接参数,通过预处理方式insert到homework表;成功后返回code=200,Android端Toast显示“发布成功”。
家长端拉取作业列表时,流程刚好反过来:Android端发起GET请求到homework_list.php,PHP查询homework表中最新的20条记录,把结果集转成JSON数组,塞进data字段返回;Android端解析JSON数组,填充到RecyclerView里。
在联调阶段,我会用一个土办法验证完整链路:先手动往MySQL里插一条记录,然后用浏览器直接访问PHP接口看返回JSON,最后Android端再请求同一个接口。三步下来,哪一环出问题一目了然。
建议:拿到源码后,先重置数据库结构再改密码。XAMPP默认的MySQL密码为空,如果你之前设过密码,要在
db.php里同步修改,否则所有接口返回数据库连接失败。
5. 联调血泪史:六个必踩的坑与解决记录
我拿到这套源码环境的时候,本以为装个XAMPP、拉个Android Studio就能跑,结果陆陆续续折腾了大半天。下面这些坑,每一个都是我实际踩过的,按“现象、原因、解决”写清楚,你照着排雷就行。
5.1 模拟器连不上宿主机:10.0.2.2变成连接失败
现象:Android模拟器里访问http://localhost:8080/api/login.php,一直报连接超时或拒绝连接。
原因:模拟器里的localhost是模拟器自己,不是你的电脑。模拟器访问宿主机有专有IP,localhost指代整个模拟器系统,10.0.2.2才是宿主机的一个别名。
解决:把Android端所有请求地址里的localhost改成10.0.2.2。例如http://10.0.2.2:8080/api/login.php。同时,在network_security_config.xml里要放行10.0.2.2这个域名的明文流量。改完记得清掉App的缓存再重新运行,不然旧的连接缓存还会干扰。
5.2 真机调试连接不上:地址和WIFI环境双重限制
现象:手机和电脑连同一个WiFi,Android端请求http://192.168.1.100:8080超时,或者偶尔能通偶尔超时。
原因:这有两层问题。第一层,IP地址必须是电脑在当前局域网的实际IP,可以在CMD里输ipconfig(Windows)或ifconfig(Mac/Linux)查。第二层,XAMPP的Apache默认可能只监听了localhost,没有监听局域网IP。
解决:在C:\xampp\apache\conf\httpd.conf里找到Listen相关配置,确认Apache监听的是0.0.0.0:8080或80,而不是127.0.0.1。同时,Windows防火墙会拦截入站请求,需要在“允许应用通过防火墙”里放行Apache进程,或者临时关闭防火墙测试。
5.3 Android 9及以上HTTP明文被拦截
现象:App请求不报错,但Logcat里能看到CLEARTEXT communication to ... not permitted by network security policy之类异常,返回结果是null或异常。
原因:Android 9(API 28)开始,默认禁止应用使用明文HTTP流量,这是系统级安全策略,不配置就直接拦截。
解决:按我第3章写的方式,在AndroidManifest.xml里声明usesCleartextTraffic="true",或者配置networkSecurityConfig定向放行。两者取其一即可,不建议两个都放,保持最小配置。
5.4 PHP端收不到POST参数
现象:Android端明明传了JSON字符串,PHP端$_POST打印出来是空数组。
原因:PHP的$_POST只识别application/x-www-form-urlencoded或multipart/form-data格式的请求体。Android端设置Content-Type: application/json传JSON字符串,PHP自然取不到。
解决:两个方案择一。方案A:PHP端不用$_POST,改用$data = json_decode(file_get_contents('php://input'), true);读原始请求体。方案B:Android端把Content-Type改回application/x-www-form-urlencoded,用username=teacher01&password=123456这种键值对格式传参。看源码用的是哪种,保持一致。这也是最容易被忽视的“前后端不联通”原因。
5.5 中文乱码:从MySQL到Android端全线排查
现象:数据库里中文是正常的,但App ListView/RecyclerView里显示的是一串问号,或者PHP返回的JSON中文变成了乱码。
原因:字符集不一致的链路传递问题。MySQL表字符集不是utf8mb4,或PHP连接没设UTF-8,或Android读取InputStream时没用UTF-8解码,三处只要有一处不对,中文就乱。
解决:逐层排查。MySQL建表时执行CREATE TABLE ... DEFAULT CHARSET=utf8mb4,PHP连接后立即$conn->set_charset('utf8mb4'),Android端在new InputStreamReader(is, "UTF-8")里显式指定UTF-8。三处全对了,中文就不会乱。
5.6 RecyclerView列表不显示数据,但接口返回正常
现象:用Postman请求接口有JSON返回,App里列表却是空的,Logcat也没异常。
原因:大多数情况是解析data字段时类型判断错了。服务端返回的data是数组,Android端用getJSONObject去取,抛了异常被catch吞掉,界面停留在初始状态。
解决:先在代码里加自定义Log,在catch处打一份e.toString(),把异常暴露出来。然后确认JSON结构:{"code":200,"data":[...]}对应getJSONArray,{"code":200,"data":{...}}对应getJSONObject。还有就是注意data是null的情况,PHP端查不到记录时,不要json_encode(null),要返回空数组json_encode([])。
注意:把这些坑全部踩过一遍之后,我才意识到联调顺序的重要性。先浏览器后App,先本机后真机,先单个接口后全流程,这个顺序能帮你过滤掉大部分低级错误。
6. 验证、改造与上线前的进阶习惯
项目能跑通,不等于可以交差。如果说前面几章解决的是“从零到一”,这一章就是把“能跑”提升到“能演示、能答辩、能上线”的思路和方法。
先说验证方法。整个平台跑通以后,不要只看“登录成功”就完事,系统性地把核心链路走一遍:用不同角色登录,确认权限裁剪生效;教师发布一条作业,家长端刷新能看到;发一条通知,确认消息列表时间排序正确;尝试提交一个空的表单,看服务端是否正确返回参数错误。每次操作完看一眼MySQL里的记录,确认状态字段正确。这套验证流程做完,你才能说这个项目是真正可使用的。
再谈改造方向。如果你想把这套源码做出差异化,最简单的切入点是加一个“作业提交”功能。在homework表旁边加一张homework_submit表,字段设计为:
CREATE TABLE homework_submit ( id INT AUTO_INCREMENT PRIMARY KEY, homework_id INT NOT NULL, student_id INT NOT NULL, submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, image_path VARCHAR(255) COMMENT '提交图片地址', status TINYINT DEFAULT 0 COMMENT '0=未批改,1=已批改', score DECIMAL(5,2), comment TEXT, FOREIGN KEY (homework_id) REFERENCES homework(id), FOREIGN KEY (student_id) REFERENCES users(id) );然后写一个submit_add.php接收图片上传,一个submit_list.php供教师端查看。这样就补齐了家校互动里家长最在意的“作业闭环”场景,功能上比原来的单向通知完善很多。
最后一个进阶习惯是关于数据上报和构建的。我发现课程设计项目里最容易被忽略的就是签名和网络配置。无论你后面用APK打包还是别的分发方式,用正式签名打包是基本要求,不要用debug签名直接交演示包。签名生成的命令,在Android Studio里操作是 Build → Generate Signed App Bundle/APK,按向导选Create New,填好证书路径、密码、别名,后面打包都统一用这一个keystore。如果你要走命令行打包,可以用:
keytool -genkey -v -keystore my-release.jks -keyalg RSA -keysize 2048 -validity 10000 -alias myalias这个命令生成一个有效期约27年的签名文件,keyalg RSA和keysize 2048是当前默认安全等级。后面打包的时候命令行里指定签名信息就行。
从那以后,我每次拿到一个源码项目,都强制自己先过一遍“环境默认值、网络配置、字符集、签名状态”这四件事,再开始改代码。这四件事决定了项目能不能在别人的电脑上复现、能不能演示、能不能过审。这套Android家校互动平台,大概率也会遇到类似的问题,你按这个顺序排查,最早晚上之前就能把App跑起来。希望帮到你。
本文还有配套的精品资源,点击获取