ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Android图书馆订座系统课设全攻略:MVC架构、SQLite与传感器计步实践

Android图书馆订座系统课设全攻略:MVC架构、SQLite与传感器计步实践 简介这是一份面向高校Android课程设计和毕业设计场景的图书馆订座系统完整课程设计报告共计8369字图文并茂。报告围绕大学生图书馆占座难、座位利用率低的问题给出从项目背景、需求分析到开发环境、系统架构的完整方案并详细阐述预订座位、查看座位、个人信息、我的步数四大功能模块的交互流程与数据库设计思路适合需要撰写移动应用开发文档或设计类似预约类系统的开发者参考。资源包为单个doc文档约9.05MB包含完整项目背景、Java与Android Studio开发环境说明、MVC架构设计、SQLite数据库五张表结构以及运行演示与心得体会等章节结构清晰可直接借鉴用于课程设计报告写作。当前已有250人查看学习说明内容具备一定参考价值。读者可通过该文档快速理解图书馆订座系统的模块划分、界面交互和数据存储方案也可对照自己的项目查漏补缺提升课程设计或毕业设计的完整度与规范性。1. Android图书馆订座系统课设报告一篇能直接反推出完整项目的范文每年课程设计季总能看到有人在群里找“图书馆占座”的Android项目。座位资源有限、抢座频繁这个选题确实经典也确实是能在答辩时讲清楚“需求—设计—实现”闭环的题目。这份8369字的课程设计报告把项目背景、开发环境、MVC架构、SQLite数据库、四大功能模块、关键代码和运行演示串成了一条完整链路。对赶课设的学生来说它既是论文范文也是能还原出可运行App的设计蓝本对带实训课的老师它又是一套现成的教学素材。登录注册、按楼层预订座位、查看退订、个人信息维护、加速度传感器计步每个模块都有对应的页面说明和核心代码。无论你是要交文档还是想快速搭一个Android MVC实训项目这份资料都值得先读完再动手。2. 把痛点翻译成功能四模块需求、技术栈与五张表的设计逻辑2.1 从抢座痛点反推需求两个约束条件决定功能边界图书馆订座系统的核心矛盾是“座位有限”和“使用者众多”。报告把需求收敛成四个模块预订座位、查看座位、个人信息、我的步数。仔细看会发现前两个模块才真正解决占座问题后两个模块更多是凑够功能完整度、同时展示Java和Android的常见能力。预订座位模块有一个关键约束每位学生同一时间只能预订一个座位。这个约束直接决定了数据表的写入逻辑——不是在客户端本地判断而是要在数据库层做一次查询确认当前账号没有未退订的记录后再写入。查看座位模块则负责把当前账号的预订记录显示出来并提供退订入口退订的本质是删除该账号在对应楼层表中的记录。个人信息模块用EditText展示和编辑学生的姓名、年龄、电话、学院这里涉及查看模式与编辑模式的切换。我的步数模块则用加速度传感器检测峰值来计算步数顺带通过网络接口拉取每日名言警句。这四块的组合很典型主功能解决业务问题辅助功能展示技术覆盖面。做课设时这种“22”的组合比全部堆业务功能要稳妥答辩时每一块都能讲出实现细节。2.2 技术选型为什么是这套组合Java、Android Studio、MVC、SQLite技术选型部分报告陈述的是标准课程设计组合Java Android Studio MVC SQLite。Java本身跨平台、面向对象、类库丰富Android应用层开发最常用的就是Java或Kotlin课设选Java没有门槛问题。Android Studio作为Google官方的IDE从项目管理、代码编辑、调试器到布局编辑器和模拟器覆盖了从开发到测试的全流程。报告里强调了一点值得注意安装配置时需要确认JDK版本与Android Studio匹配这个坑实际开发中很常见后面我会专门讲。架构层面采用MVC三层职责划分得很清晰model模型层是bean实体类对应Student、User、Seat这类数据对象view视图层是res/layout下的XML布局文件controller控制层就是实现界面跳转和业务逻辑的Activity。这种划分对课设来说足够清楚也方便在论文里画出软件架构图。Activity在MVC里承担了Controller的角色但在实际工程里Activity经常把业务逻辑也写进去这是MVC在Android落地时公认的取舍课设阶段不用过度纠结。数据库选SQLite的理由非常直接轻量级、无服务器、支持标准SQL、支持ACID事务几百KB的体积可以嵌在App内部不需要额外部署。对课设来说SQLite比MySQL省掉了一整套环境配置也不需要网络权限演示时不容易出问题。2.3 数据库设计library.db的五张表与主键约定数据库名是library.db一共五张表表结构完全围绕Bean实体类来设置。看完整张表会发现一个特点账号account被反复用作关联字段但它没有建外键约束表与表之间靠应用层逻辑保证一致性这是课设项目常见且可接受的简化。表名字段类型说明accountaccounttext账号主键唯一非空accountpasswordtext密码Firstidinteger一楼已预定座位编号主键Firstaccounttext预定人账号Secondidinteger二楼已预定座位编号主键Secondaccounttext预定人账号Thirdidinteger三楼已预定座位编号主键Thirdaccounttext预定人账号Studentaccounttext账号主键唯一非空Studentname / age / phone / collegetext姓名、年龄、电话、学院这里有一个可以写入论文的设计点一、二、三楼分别建表而不是在一张表里加floor字段区分楼层。这种设计简单直观查询时不用带WHERE楼层条件但扩展性差——如果后面要加四楼必须新建表并同步修改Java代码。我在实际项目中会把楼层拆成seat表加floor字段但课设阶段按照原报告的分表方案写逻辑上说得通演示也稳定。主键的选择上account表用账号做主键First、Second、Third表用座位编号id做主键Student表同样用账号做主键。预订座位时先查对应楼层表里是否已有当前账号的记录有则提示已订座没有则插入一条这条约束在代码里通过isExist这类方法配合SQL查询实现。3. 登录注册全链路欢迎页跳转、密码校验与结果回传的落地写法3.1 WelcomeActivity延迟跳转Handler.postDelayed为什么比Thread.sleep稳妥WelcomeActivity是应用的欢迎界面逻辑非常简单隐藏默认标题栏3秒后启动LoginActivity并关闭自己。关键代码就一段public class WelcomeActivity extends AppCompatActivity { Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_welcome); if (getSupportActionBar() ! null) { getSupportActionBar().hide(); } // 延迟3秒跳转到登录页期间展示欢迎界面 new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { Override public void run() { Intent intent new Intent(WelcomeActivity.this, LoginActivity.class); startActivity(intent); finish(); } }, 3000); } }这里用Handler.postDelayed而不是直接Thread.sleep是因为sleep会阻塞主线程导致界面卡死、无法响应用户操作。postDelayed把Runnable投递到主线程消息队列只在指定时间后执行主线程在这3秒内仍然可以响应触摸事件。注释里写了Looper.getMainLooper()这是一个容易被忽略的细节。在Android 12及以上版本中无参的Handler构造方法已经被标记废弃强制要求传入Looper课设里如果用的新版Android Studio写不传Looper的版本会看到黄色警告。3秒的延迟是常见启动动画时长不要太长答辩演示时等太久会冷场。如果你想在欢迎页加个动画可以在postDelayed之前启动属性动画延迟时间相应调长到3.5秒或4秒但不建议超过5秒。3.2 LoginActivity的数据库校验流程UserDao的open/close配对和空值兜底登录页的校验逻辑是整套系统里最容易被复用到其他课设的代码。流程是获取账号密码文本trim去掉首尾空格创建UserDao对象并open数据库先判断账号是否存在再比对密码最后关闭数据库连接。核心代码如下private void initView() { // 隐藏标题栏 if (getSupportActionBar() ! null) { getSupportActionBar().hide(); } etAccount findViewById(R.id.et_account); etPassword findViewById(R.id.et_password); btnLogin findViewById(R.id.btn_login); tvRegister findViewById(R.id.tv_register); btnLogin.setOnClickListener(new View.OnClickListener() { Override public void onClick(View v) { String acc etAccount.getText().toString().trim(); String pass etPassword.getText().toString().trim(); // 空值兜底避免用户什么都不输入就点登录 if (acc.isEmpty() || pass.isEmpty()) { Toast.makeText(LoginActivity.this, 账号或密码不能为空, Toast.LENGTH_SHORT).show(); return; } userDao new UserDao(getApplicationContext()); userDao.open(); // 打开数据库连接获取可写实例 if (!userDao.isExist(acc)) { Toast.makeText(LoginActivity.this, 账号不存在请重新输入, Toast.LENGTH_SHORT).show(); } else { if (userDao.getPassword(acc).equals(pass)) { // 密码匹配跳转主界面并把账号带过去 Intent intent new Intent(LoginActivity.this, MainActivity.class); intent.putExtra(ACCOUNT, acc); startActivity(intent); finish(); } else { Toast.makeText(LoginActivity.this, 密码错误请重新输入, Toast.LENGTH_SHORT).show(); } } // 关闭数据库访问对象避免连接泄漏 userDao.close(); } }); }两个细节值得注意。一是open和close必须配对。SQLiteDatabase的实例在重复获取时如果前面的没有close会在日志里打出leak detected警告看起来是小问题答辩时被问到就很尴尬。二是先isExist再getPassword避免对不存在的记录直接取密码导致空指针。getApplicationContext()的写法也要保持——在匿名内部类里写this会指向匿名类而不是Activity取不到正确的Context。如果你想把这段逻辑往工程化的方向改一步可以把账号密码封装到一个User对象里校验逻辑放到UserDao里返回一个状态码Activity只负责展示这样MVC的层次会更干净。3.3 RegisterActivity的setResult回传注册成功为什么返回登录页而不是直接跳转注册页和登录页布局高度相似多了“再次输入密码”的输入框底部有两个按钮注册和取消。注册成功后的处理方式是使用setResult返回登录页而不是直接startActivity跳转主页。原报告的关键代码如下userDao.addUser(user); Toast.makeText(RegisterActivity.this, 注册成功, Toast.LENGTH_SHORT).show(); // 创建意图对象将账号和密码回传给登录页 Intent intent new Intent(RegisterActivity.this, LoginActivity.class); intent.putExtra(acc, acc); intent.putExtra(pass, pass); setResult(RESULT_OK, intent); finish();setResult配合startActivityForResult是一套很经典的Activity间通信模式。注册页把自己关闭并把账号密码通过Intent带回到登录页登录页在onActivityResult里取出来直接填充到输入框。这样做的设计意图是保证登录页是用户进入系统的唯一入口注册流程不产生“绕开登录”的路径。对应的登录页接收代码一般长这样Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode REQUEST_CODE_REGISTER resultCode RESULT_OK data ! null) { // 注册成功回传自动填充账号密码省去手动输入 String acc data.getStringExtra(acc); String pass data.getStringExtra(pass); etAccount.setText(acc); etPassword.setText(pass); } }在注册按钮的点击监听器里除了校验账号是否存在、密码是否为空之外还要做一次“两次输入的密码是否相同”的判断。这些校验条件要写成顺序判断先查账号是否存在再查密码为空最后比对两次密码一致性顺序反了会出现账号已存在时依然提示密码不一致的误导信息。取消按钮直接finish()即可不需要setResult登录页的onActivityResult里判断resultCode不是RESULT_OK时什么都不做。4. 核心模块实现多行座位互斥、自动轮播与传感器计步4.1 RadioGroup嵌套难题多行座位表如何保证同一时刻只选一个座位一楼座位页是这套课设里最值得讲的部分。原报告里提到一个关键背景RadioGroup底层是LinearLayout里面的RadioButton只能全部水平排列或全部垂直排列无法自定义排列方式。要做成一张多行座位表常见做法是用多个RadioGroup并排嵌套每一行是一个独立的RadioGroup里面放2到3个RadioButton外层再套一个纵向的LinearLayout控制总布局。但这样就引入了一个新问题默认情况下不同RadioGroup之间的RadioButton可以同时被选中这违反了“同一时间只能订一个座位”的业务规则。原报告的解决思路是在每个RadioGroup的选中事件里清除其他所有RadioGroup的选中状态radioGroup2.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { Override public void onCheckedChanged(RadioGroup radioGroup, int i) { // 清除其他行的选中状态保证全局只有一个座位被选中 for (int k 0; k radioGroups.length; k) { if (k 1) continue; // 跳过当前这一组 if (radioGroups[k].getCheckedRadioButtonId() ! -1) { radioGroups[k].clearCheck(); } } // 当前选中的座位编号和账号组合成一个Seat对象 seat new Seat(i, account); } });这里radioGroups数组按行顺序存放了所有RadioGroup实例。选中某一行的按钮时遍历所有组只要不是当前组就调用clearCheck()从而保证整个页面同时只有一个选中状态。这种写法是纯控件层面的约束数据库层面再加一道“已存在记录不能重复预订”的校验两层配合才完整。我在实际复用这份代码时有一个习惯把radioGroups数组改成用id数组动态查找。因为XML布局里每行的RadioGroup都有固定的android:id用int[] ids {R.id.group1, R.id.group2, ...}去findViewById比手动新增一个数组条目更不容易漏。如果你要给座位表加一个“已被预订”的状态展示在initSeats()里遍历所有RadioButton与数据库返回的座位编号集合比对命中的调用setEnabled(false)并设置选中背景这样座位表一打开就能直观看到哪些座位被占了。4.2 ViewPager自动轮播setVPListener与Handler循环消息的实现主页面顶部有一个ViewPager轮播图高度300dp下方有对应的小圆点指示器。自动轮播的实现方式是Handler每隔2秒发送一条空消息收到消息后切换页面。原报告给出的setVPListener部分代码如下private void setVPListener() { libraryVp.addOnPageChangeListener(new ViewPager.OnPageChangeListener() { Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // 滑动过程中不需要处理留空即可 } Override public void onPageSelected(int position) { // 切换页面时更新小圆点指示器的选中状态 for (int i 0; i pointList.size(); i) { pointList.get(i).setImageResource(R.drawable.point_normal); } pointList.get(position).setImageResource(R.drawable.point_focus); } Override public void onPageScrollStateChanged(int state) { // 滚动状态变化一般不在课设里处理 } }); }指示器的核心思想是“先全部置为未选中再把当前页置为选中”。point_normal和point_focus是两张不同颜色的小圆点图片通过切换图片资源实现高亮变化。如果你想用更现代的方式可以用ShapeDrawable动态画圆点省掉两张图片资源但课设用图片方式更直白。消息循环的handleMessage逻辑是当前页如果是最后一页就把ViewPager切回第一页否则切到下一页然后再发送一条延迟2秒的消息形成循环。onCreate里通过handler.sendEmptyMessageDelayed(1, 2000)发第一条消息启动循环。这里必须记得在onDestroy或onStop里调用handler.removeCallbacksAndMessages(null)否则Activity销毁后Handler仍然持有引用消息继续循环轻则日志刷屏重则内存泄漏。这个问题我放在第五章避坑里细说。4.3 StepActivity传感器计步峰值检测的简易实现计步模块使用加速度传感器通过检测三轴加速度向量模的变化来判断是否走了一步。传感器部分的注册代码如下sManager (SensorManager) getSystemService(SENSOR_SERVICE); mSensorAccelerometer sManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); sManager.registerListener(this, mSensorAccelerometer, SensorManager.SENSOR_DELAY_UI);SENSOR_DELAY_UI对应大约16ms到20ms的采样间隔适合界面展示型应用。计步核心在onSensorChanged里Override public void onSensorChanged(SensorEvent event) { float x event.values[0]; float y event.values[1]; float z event.values[2]; // 计算三轴加速度向量的模排除方向干扰 float magnitude (float) Math.sqrt(x * x y * y z * z); if (magnitude lastMagnitude) { // 向上加速阶段更新基准值 lastMagnitude magnitude; motiveState true; } else if (motiveState lastMagnitude - magnitude STEP_THRESHOLD) { // 由上升转为下降且下降幅度超过阈值判定为一步 stepCount; tvStep.setText(String.valueOf(stepCount)); lastMagnitude magnitude; motiveState false; } }这个算法的核心是“峰值检测”走路时手机加速度先增大后减小形成一个波峰检测到足够大的下降沿就记一步。STEP_THRESHOLD是精度阈值原报告里叫“精度范围”取值太小会因为手抖、桌面震动误触发取值太大会漏步。我实践下来阈值取2.0到3.0之间比较合适但真机上需要根据手持方式微调。这个方案只是课设级别的简易计步和微信步数那种用系统计步传感器加算法滤波的方案差距很大。但作为课设演示它够用。要注意的是模拟器上没法产生真实的加速度数据这部分功能只能在真机演示答辩前务必准备一台Android真机。4.4 StudentActivity个人信息查看模式与编辑模式的切换个人信息页是整套系统里逻辑最简单、但最容易在UI交互上出彩的模块。核心做法是EditText默认setEnabled(false)禁止编辑点击“修改信息”按钮后切换为可编辑状态同时底部按钮从“修改信息”变成“保存信息”保存时调用studentDao.updateInformation()写库然后恢复查看模式。原报告的disableEditor代码如下private void disableEditor() { // 查看模式禁止编辑 etName.setEnabled(false); etAge.setEnabled(false); etPhone.setEnabled(false); etCollege.setEnabled(false); studentDao.open(); // 获取控件中的文本内容 String name etName.getText().toString().trim(); String age etAge.getText().toString().trim(); String phone etPhone.getText().toString().trim(); String college etCollege.getText().toString().trim(); student new Student(account, name, age, phone, college); // 将数据保存到表中 studentDao.updateInformation(student); studentDao.close(); // 按钮显示切换保存按钮隐藏修改按钮显示 btnSave.setVisibility(View.INVISIBLE); btnUpdate.setVisibility(View.VISIBLE); }这里有个值得注意的小技巧disableEditor里同时做了保存操作。也就是说从编辑模式切回查看模式时顺手把当前EditText的内容写入数据库。这种写法把“保存”这个动作隐含在模式切换里对课设演示来说很流畅但实际工程里应该把保存做成显式按钮触发。我一般在复刻这个模块时会补一段逻辑页面打开时先从数据库读取学生信息填充到EditText。如果学生表里没有当前账号的记录要给一个默认值或者提示先完善信息否则页面上就是四个空输入框演示效果不好看。5. 避坑排查课设演示前最容易翻车的五个细节5.1 座位预订后按钮仍然可点甚至能重复预订先说现象按报告逻辑初始化座位表后已经被预订的座位在界面上依然是可点击状态点击还能弹出预订成功数据库里出现同一个人多条预订记录。问题出在initSeats()只读出了已订座位的列表却没有把这些座位对应的RadioButton设置为不可用。预订成功的界面刷新也不彻底导致用户误以为可以“再订一个”。我的处理是在initSeats()里拿到所有RadioButton实例用座号集合遍历比对命中的直接setEnabled(false)并setTag标记为已订状态预订按钮点击时再判断一次该座位是否已被订双保险。退订后要恢复该RadioButton的enabled状态这个在CheckActivity退订完返回时做一次刷新。5.2 ViewPager自动轮播在退出页面后还在跑现象是主界面退出到登录页后日志里还在打印轮播相关的消息甚至主页销毁后App出现卡顿。原因是Handler持有Activity引用postDelayed的消息没有被移除Activity虽然finish了但消息队列里还挂着循环。解决方法是重写onDestroy或onStop方法调用handler.removeCallbacksAndMessages(null)并确保每条消息发送前检查一下handler是否为空。这条坑在答辩演示时很隐蔽因为你不会主动去看日志但如果是真机运行退出主页后CPU占用会明显升高。5.3 退订座位后回到座位表已订状态不刷新现象是CheckActivity里退订成功后返回FirstFloorActivity原来显示已订状态的座位还是选中的样子颜色没变。原因是座位表Activity的initSeats()只执行一次退订操作发生在另一个Activity里座位表页面没有任何接收回调的机制。解决方式有两种一种是用startActivityForResult发起跳转在onActivityResult里重新调用initSeats()并遍历RadioButton刷新状态另一种是更工程化的做法用ViewModel加上LiveData持有座位数据和选中状态数据变化自动刷新UI。课设阶段用第一种就够了改动最小。5.4 传感器计步在模拟器上无效真机上乱跳现象是模拟器上点击开始计步后步数始终是0换到真机上走几步步数突然跳好几个放在桌上静止不动也会偶尔1。原因是模拟器不产生真实的加速度传感器数据传感器事件不会触发。真机乱跳原因是峰值检测的阈值太小桌面上轻微的震动、拿放手机的动作都会被判定为一步。处理思路有两个方向一是提高STEP_THRESHOLD的阈值我用2.5作为默认值真机调试时用log输出magnitude的实际数值来校准二是加入时间窗口两次计步间隔小于400毫秒的直接忽略能滤掉大部分抖动。演示时建议把手机握在手里自然摆动不要放在桌上等它自己“走”。5.5 数据库文件在DDMS里看不到模拟器重启后数据消失现象是report里说数据库叫library.db但在Android Studio的Device Explorer里找不到或者模拟器重启后之前注册的账号全没了。原因是SQLiteOpenHelper创建的数据库位于应用私有目录/data/data/包名/databases/下面查库文件需要进入package的私有目录才能看到。另外模拟器如果开启了“快速启动”重启不会清理数据但如果手动执行了wipe data应用数据会全部被清掉。处理方法是演示前用adb shell run-as包名 ls databases/确认数据库存在或者直接在应用里写出导出功能不要在真机上尝试把数据库文件拉到电脑里看没有root权限是拉不出来的。真要排查数据我一般是在代码里临时加一个打印日志的调试入口把查询结果打到Logcat里。6. 把报告变成验收清单演示路径与三个加分改造拿到这份报告很多人第一反应是“论文有了代码在哪”。我的做法是先把报告里的运行演示章节抽成一张验收清单每一步对应一个功能点然后再决定代码怎么补。按原报告的顺序启动后进入欢迎页3秒后跳到登录页点“新用户注册”进入注册页输入学号和密码完成注册返回登录页时账号密码自动填充登录成功进入主页面顶部轮播图自动播放下方选择楼层预订座位页面选择某个座位返回主页后进入查看座位页能看到楼层和座位号点击退订后回到座位表该座位恢复可选进入个人信息页点击修改后输入姓名年龄电话学院保存后退出重进数据还在进入我的步数页点击开始计步拿真机走几步步数变化下方名言警句正常展示最后点退出登录回到登录页。这套验收路径跑通一遍Demeo演示就不会翻车。如果你想在答辩时拿点加分项我常用的三个改造方向是第一把座位表从多组RadioButton改成RecyclerView加网格布局。座位数据从数据库动态读取楼层表合并成一张seat表带上floor字段代码量增加但扩展性很强加四楼不用新建表。答辩时能讲清楚“为什么重构”本身就是加分项。第二计步模块增加一个系统计步传感器的分支判断。Android 4.4以上提供了TYPE_STEP_DETECTOR精度远高于加速度传感器峰值检测注册时优先判断系统是否有该传感器有则用它没有则回落到加速度方案。这段兼容性代码很能体现对设备差异的理解。第三给预订座位加一个占座时长限制比如预订后30分钟未确认自动释放。用数据库记录预订时间每次进入查看座位页时做一次时间差判断超时就把记录删除并恢复座位可选。这个功能把“订座系统”从单纯的占座提升到了座位流转管理在答辩里很有说服力。从那以后我每次拿到课设项目第一件事都是先把报告的演示流程在纸上走一遍标出每一步对应的代码文件再决定补什么、改什么而不是先翻代码。报告是骨架代码是血肉对着骨架动手效率比通读源码高得多。希望这套拆解思路对你的课程设计有帮助也祝你在答辩时能把每一个模块都讲得清清楚楚。本文还有配套的精品资源点击获取
返回列表