基于SQLite与AlarmManager的老年人服药提醒App毕业设计实战

发布时间:2026/10/11 19:18:32

基于SQLite与AlarmManager的老年人服药提醒App毕业设计实战 简介这份资源面向计算机相关专业的毕业生与课程设计学习者提供一套基于 Android 与 SpringBoot 前后端分离架构的老年人服药提醒 App 完整实现方案可用于毕业设计、期末大作业或课程设计场景。项目采用 Android 作为前端、SpringBoot 作为后台框架数据存储使用 MySQL开发工具涉及 Maven/Gradle、JDK、IDEA 与 Android Studio代码附有注释新手也能对照理解。压缩包共 6 个文件包含 3 个 zip 源码与演示包、2 个 txt 使用说明及 1 个 sql 数据库脚本整体约 92.18MB目录划分清晰便于按模块查阅。目前已有 340 人学习下载。资源覆盖项目源码、数据库脚本、软件工具与项目说明读者可据此掌握服药提醒类 App 的完整业务逻辑、前后端接口设计与数据库表结构并借助演示视频与说明文档快速完成环境搭建与功能验证为答辩与二次开发提供可复用的参考。1. 老年人服药提醒 App从闹钟到数据库一个毕业设计怎么做到能演示、能答辩给老人做服药提醒最直觉的方案是设闹钟但真上手就会发现闹钟只能解决“响不响”解决不了“吃没吃、吃几次、漏了怎么办”。这个标题指向的是一套完整的 Android 本地应用用 SQLite 存药品和服药记录用 AlarmManager 加通知做定时提醒再配一个能增删改查的数据库层。它适合正在做 Android 毕业设计、需要一套能跑通、能讲清、能现场演示的同学也适合想给家里老人做个小工具的人。核心难点不在界面多花哨而在时间调度和数据库状态的一致性——这两块讲不清楚答辩时老师一问就露馅。2. 先定数据模型药品表、计划表、记录表怎么拆2.1 三张表撑起整个业务老年人服药提醒 App 的数据关系其实不复杂但拆得对不对直接决定后面代码好不好写。我一般会拆成三张表药品表存药名、剂量、备注服药计划表存每种药每天几点吃、吃几次、开始和结束日期服药记录表存每次实际吃没吃、实际吃药时间。为什么要把计划和记录分开因为计划是“应该做什么”记录是“实际做了什么”混在一张表里改一次计划就会把历史记录冲掉这是血泪经验。药品表和计划表是一对多一种药可以对应多个服药时间点。计划表和记录表也是一对多一个计划每天生成一条记录。这样设计的好处是查“今天该吃哪些药”只需要查计划表查“这周漏了几次”只需要查记录表职责清晰。表名主要字段说明medicineid, name, dosage, note药品基础信息planid, medicine_id, hour, minute, repeat_type, start_date, end_date服药计划recordid, plan_id, date, status, actual_time服药记录status 0未服 1已服 2跳过2.2 建表 SQL 与字段类型选择Android 里用 SQLite字段类型不用太纠结INTEGER、TEXT、REAL 够用。日期我习惯存成yyyy-MM-dd的 TEXT时间存成 INTEGER 的分钟数或直接存 hour/minute 两个字段。下面这段建表语句可以直接抄进SQLiteOpenHelper的onCreate里。CREATE TABLE medicine ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, dosage TEXT, note TEXT ); CREATE TABLE plan ( id INTEGER PRIMARY KEY AUTOINCREMENT, medicine_id INTEGER NOT NULL, hour INTEGER NOT NULL, minute INTEGER NOT NULL, repeat_type INTEGER DEFAULT 0, start_date TEXT, end_date TEXT, FOREIGN KEY (medicine_id) REFERENCES medicine(id) ON DELETE CASCADE ); CREATE TABLE record ( id INTEGER PRIMARY KEY AUTOINCREMENT, plan_id INTEGER NOT NULL, date TEXT NOT NULL, status INTEGER DEFAULT 0, actual_time TEXT, FOREIGN KEY (plan_id) REFERENCES plan(id) ON DELETE CASCADE );逻辑说明ON DELETE CASCADE保证删药品时计划跟着删删计划时记录跟着删避免脏数据。参数说明repeat_type用 0 表示每天、1 表示隔天、2 表示每周固定几天扩展时改这一个字段就行。status用整数而不是布尔是为了以后加“跳过”“延迟”状态不用改表结构。提示SQLite 的外键约束默认是关闭的需要在onConfigure里执行db.setForeignKeyConstraintsEnabled(true)否则 CASCADE 不生效。3. 用 AlarmManager 做定时提醒精确闹钟与 Android 版本适配3.1 为什么不用 Handler 或 Timer很多同学第一反应是用 Handler 延时或者 Timer 轮询这在 App 前台时没问题但老人手机经常锁屏、切后台进程随时可能被系统回收Timer 直接失效。AlarmManager 是系统级服务即使 App 被杀只要闹钟还注册着到点依然能拉起广播。这是做提醒类 App 的标配也是答辩时能加分的点。Android 12 之后精确闹钟权限收紧setExact需要SCHEDULE_EXACT_ALARM权限Android 13 又引入USE_EXACT_ALARM。适配思路是先判断canScheduleExactAlarms()能精确就精确不能就退化成setAndAllowWhileIdle保证至少能响不追求秒级精确。3.2 注册闹钟的完整代码public void scheduleAlarm(Context context, Plan plan) { AlarmManager am (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent intent new Intent(context, AlarmReceiver.class); intent.putExtra(plan_id, plan.getId()); PendingIntent pi PendingIntent.getBroadcast( context, plan.getId(), intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); Calendar cal Calendar.getInstance(); cal.set(Calendar.HOUR_OF_DAY, plan.getHour()); cal.set(Calendar.MINUTE, plan.getMinute()); cal.set(Calendar.SECOND, 0); if (cal.getTimeInMillis() System.currentTimeMillis()) { cal.add(Calendar.DAY_OF_MONTH, 1); } if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { if (am.canScheduleExactAlarms()) { am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pi); } else { am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pi); } } else { am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pi); } }逻辑说明PendingIntent的 requestCode 用plan.getId()保证每个计划有独立的闹钟不会互相覆盖。FLAG_IMMUTABLE是 Android 12 强制要求不加会崩。参数说明RTC_WAKEUP表示用绝对时间并唤醒设备适合定时提醒如果换成ELAPSED_REALTIME_WAKEUP关机重启后时间会错乱。3.3 广播接收器里做什么闹钟触发后进入AlarmReceiver这里要做三件事发通知、写一条待服记录、注册下一次闹钟。注意不要在onReceive里做耗时操作超过 10 秒会 ANR。写数据库和发通知都很快可以直接做如果以后要联网同步必须开 WorkManager。Override public void onReceive(Context context, Intent intent) { int planId intent.getIntExtra(plan_id, -1); if (planId -1) return; NotificationHelper.show(context, planId); RecordDao.insertPending(context, planId, DateUtil.today()); Plan plan PlanDao.getById(context, planId); if (plan ! null plan.isRepeating()) { new AlarmScheduler().scheduleAlarm(context, plan); } }逻辑说明先发通知再写记录保证用户感知优先。insertPending写入 status0 的记录用户点“已服”再更新为 1。参数说明isRepeating()判断是否还有后续日期避免过期计划一直注册闹钟。4. 数据库增删改查把 DAO 写清楚答辩不慌4.1 用 SQLiteOpenHelper 还是 Room毕业设计里两种都常见。SQLiteOpenHelper 手写 SQL代码量大但逻辑透明答辩时能一行行讲Room 是官方 ORM注解加接口代码少但需要理解编译期生成。如果时间紧、想少写样板代码用 Room如果想展示对 SQL 的掌握用 SQLiteOpenHelper。我一般建议毕业设计用 SQLiteOpenHelper因为老师更看重你懂不懂底层。4.2 手写 DAO 的增删改查模板public class MedicineDao { private final DBHelper helper; public MedicineDao(Context context) { helper new DBHelper(context); } public long insert(Medicine m) { SQLiteDatabase db helper.getWritableDatabase(); ContentValues cv new ContentValues(); cv.put(name, m.getName()); cv.put(dosage, m.getDosage()); cv.put(note, m.getNote()); return db.insert(medicine, null, cv); } public ListMedicine queryAll() { ListMedicine list new ArrayList(); SQLiteDatabase db helper.getReadableDatabase(); Cursor c db.query(medicine, null, null, null, null, null, id DESC); while (c.moveToNext()) { Medicine m new Medicine(); m.setId(c.getInt(c.getColumnIndexOrThrow(id))); m.setName(c.getString(c.getColumnIndexOrThrow(name))); m.setDosage(c.getString(c.getColumnIndexOrThrow(dosage))); list.add(m); } c.close(); return list; } public int update(Medicine m) { SQLiteDatabase db helper.getWritableDatabase(); ContentValues cv new ContentValues(); cv.put(name, m.getName()); cv.put(dosage, m.getDosage()); return db.update(medicine, cv, id?, new String[]{String.valueOf(m.getId())}); } public int delete(int id) { SQLiteDatabase db helper.getWritableDatabase(); return db.delete(medicine, id?, new String[]{String.valueOf(id)}); } }逻辑说明query方法用getColumnIndexOrThrow而不是getColumnIndex字段名写错时直接抛异常比返回 -1 再静默出错好排查。参数说明orderBy用id DESC让新加的药品排前面符合使用习惯。insert返回行号-1 表示失败调用处要判断。4.3 查“今天该吃哪些药”的联表 SQL这是整个 App 最核心的一条查询答辩大概率会问。思路是从 plan 表里筛出今天在有效期内的计划再关联 medicine 拿药名最后左连接 record 看今天有没有已服记录。SELECT p.id, m.name, m.dosage, p.hour, p.minute, r.status FROM plan p JOIN medicine m ON p.medicine_id m.id LEFT JOIN record r ON r.plan_id p.id AND r.date ? WHERE p.start_date ? AND (p.end_date IS NULL OR p.end_date ?) ORDER BY p.hour, p.minute;逻辑说明用 LEFT JOIN 而不是 INNER JOIN保证即使今天还没生成记录计划也能查出来界面上显示为“待服”。参数说明三个?都传今天的日期字符串格式统一为yyyy-MM-dd。如果 repeat_type 是隔天或每周需要在 WHERE 里再加条件或者在 Java 层过滤。注意日期字符串比较依赖格式统一如果有的地方存2024-1-1有的存2024-01-01排序和比较都会出错。统一用SimpleDateFormat(yyyy-MM-dd)格式化。5. 避坑与排查这五个坑我替你踩过了5.1 闹钟不响通知也不出现现象到点了手机没反应打开 App 才看到记录。原因国产 ROM 的后台限制或者电池优化把 App 冻结了。解决引导用户把 App 加入电池优化白名单代码里用ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS跳转设置页。另外检查PendingIntent的 flagAndroid 12 以上不加FLAG_IMMUTABLE会直接抛异常。5.2 数据库写入报 UNIQUE constraint failed现象同一天同一个计划插入两条记录时崩溃。原因record 表没加唯一约束或者加了但代码没做去重。解决给(plan_id, date)加 UNIQUE 索引插入时用insertWithOnConflict并传CONFLICT_REPLACE。CREATE UNIQUE INDEX idx_record_plan_date ON record(plan_id, date);5.3 时间显示差 8 小时现象设的早上 8 点实际 16 点才提醒。原因Calendar 默认时区问题或者服务器时间用了 UTC。解决统一用Calendar.getInstance()拿本地时区存数据库时存本地时间字符串不要存时间戳。如果一定要存时间戳显示时用SimpleDateFormat并指定Locale.getDefault()。5.4 删了药品计划还在列表里现象药品删了但首页还能看到该药的提醒。原因外键约束没开CASCADE 没生效。解决在DBHelper.onConfigure里调用db.setForeignKeyConstraintsEnabled(true)并且建表时外键声明要写对。如果已经上线补一条手动删除的 SQL 兜底。5.5 老人误触“已服”记录改不回来现象老人点错了把没吃的标成已吃子女看不到真实情况。原因交互没做二次确认或撤销。解决点“已服”后弹一个 Snackbar 带“撤销”5 秒内可回退。数据库层把 status 改回 0并清空 actual_time。这个细节在答辩时讲出来老师会觉得你考虑到了真实使用场景。6. 进阶技巧用日志表做服药依从性统计6.1 为什么加一张操作日志表前面三张表能跑通基本功能但答辩时如果老师问“怎么证明老人按时吃了”只有 record 表不够因为 record 可以被修改。我一般会再加一张operation_log表记录每次状态变更的时间、旧值、新值。这样既能做依从性统计又能防止数据被误改后无法追溯。CREATE TABLE operation_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, record_id INTEGER, old_status INTEGER, new_status INTEGER, op_time TEXT );6.2 依从性统计 SQL 与界面展示统计最近 7 天的按时服药率用一条 SQL 就能算出来。按时定义为 status1 且 actual_time 与计划时间相差不超过 30 分钟。SELECT COUNT(*) AS total, SUM(CASE WHEN status 1 THEN 1 ELSE 0 END) AS taken, ROUND(SUM(CASE WHEN status 1 THEN 1.0 ELSE 0 END) / COUNT(*), 2) AS rate FROM record WHERE date date(now, -7 days);逻辑说明date(now, -7 days)是 SQLite 的日期函数直接算出 7 天前的日期。参数说明rate保留两位小数界面上用进度条展示老人和子女都能一眼看懂。如果要做更细的“分时段依从性”把strftime(%H, ...)加进 GROUP BY 即可。6.3 导出 CSV 给子女看很多子女不在身边App 里看不够还想导出。Android 里写 CSV 到getExternalFilesDir就行不需要存储权限。核心代码三行拼字符串、写文件、用 FileProvider 分享。StringBuilder sb new StringBuilder(日期,药名,计划时间,实际时间,状态\n); for (Record r : records) { sb.append(r.getDate()).append(,) .append(r.getMedicineName()).append(,) .append(r.getPlanTime()).append(,) .append(r.getActualTime()).append(,) .append(r.getStatusText()).append(\n); } File file new File(getExternalFilesDir(null), medication_report.csv); try (FileWriter fw new FileWriter(file)) { fw.write(sb.toString()); }逻辑说明用getExternalFilesDir而不是外部存储根目录免去 Android 10 以上的分区存储适配。参数说明CSV 用逗号分隔如果药名里本身有逗号需要加引号包裹否则 Excel 打开会错列。这个细节我踩过药名“阿司匹林,肠溶”直接导致列错位。6.4 一个我坚持的习惯每次改完数据库相关代码我一定会做两件事一是把 App 卸载重装验证onCreate建表逻辑二是手动改系统时间到明天看闹钟会不会重复注册。这两个动作花不了五分钟但能挡住大部分“演示时翻车”的情况。毕业设计答辩只有一次机会数据库和闹钟这两块稳了整个项目就稳了。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/11 19:13:32

LabelImg图像标注实战指南:从解压启动到YOLO格式转换

简介:labelImg-master.zip 是开源图像标注工具 labelImg 的源代码压缩包,面向计算机视觉与深度学习开发者、研究人员,用于搭建标注环境,生成目标检测、语义分割等模型所需的像素级标注数据。包内共 123 个文件,以 Pyth…

2026/10/11 19:13:32

Docker Desktop 运行 Redis 全攻略:从镜像拉取到持久化配置与排错

直接说结论:用 Docker Desktop 跑 redis,不是“图省事装个软件”那么简单,而是把 redis 的开发、测试、部署环境统一成一个随时可复现的镜像。我帮团队排查过不少 redis 容器使用问题,大多数坑都不是 redis 本身出的,而…

2026/10/11 19:13:32

基于YOLOv8的工业机器人作业区入侵检测系统实践

简介:基于YOLOv8构建的工业机器人作业区域入侵检测系统,是一份集源码、可视化界面、完整数据集与部署教程于一体的深度学习项目资源,面向计算机视觉、自动化、电子信息等专业的在校学生,适用于毕业设计、课程设计或项目初期立项演…

2026/10/11 20:43:40

C# OpenCvSharp DNN 人脸朝向估计:从关键点到欧拉角实战

简介:本资源为C# OpenCvSharp DNN人脸朝向估计完整源码工程,面向具备一定C#基础、希望入门计算机视觉与深度学习部署的开发者。项目通过OpenCvSharp封装库结合DNN模块加载预训练模型,实现从人脸检测、图像预处理、模型前向传播到角度后处理输…

2026/10/11 20:43:40

货架空置缺货检测数据集:4470张双类标签VOC+YOLO格式实战指南

简介:这份资源是面向零售智能化与计算机视觉方向的超市货架空置缺货检测数据集,适用于目标检测模型训练、货架陈列分析及补货预警等场景,适合具备一定深度学习基础、需要真实货架图像做实验或项目落地的开发者与研究人员。压缩包共约2000个文…

2026/10/11 20:43:40

三菱FX3U与信捷触摸屏三轴搬运程序:结构设计与调试全解析

1. 项目概述与整体设计思路1.1 立项背景与核心需求拆解我最早接触三轴搬运这个项目,是很多年前在车间做设备改造的时候。老实说,三轴搬运在工业现场属于最典型的“入门级自动化应用”——三台步进电机、一个PLC、一个触摸屏,就能完成从A点抓料…

2026/10/11 20:43:40

11124张足球运动员检测数据集:VOC与YOLO双格式选择与实战指南

简介:本资源为足球运动员检测数据集,面向计算机视觉方向的学习者、算法工程师及体育视频分析研究者,可用于目标检测模型的训练、验证与迁移学习实验,尤其适合需要同时使用Pascal VOC与YOLO两种标注格式的开发者。压缩包共2000个文…

2026/10/11 20:43:40

基于OpenCLIP知识蒸馏的零标签图像分类实战源码解析

简介:本资源面向计算机视觉方向的研究人员与开发者,提供一套基于OpenCLIP知识蒸馏实现零标签图像分类的完整项目源码,帮助在缺乏标注数据的场景下训练轻量级分类模型。压缩包共16个文件,约1.42MB,以9个Python脚本为核心…

2026/10/11 20:38:39

什么是IT资产自动发现?让CMDB和资产台账不再靠人工维护

IT资产自动发现(Asset Discovery)是指通过扫描网络、读取设备信息等方式,自动识别企业环境中有哪些设备和软件、它们的配置是什么,并把结果同步到资产库的技术手段。 它解决的是 IT资产管理 里最老的一个难题:台账靠人…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/11 0:02:13

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/11 0:02:13

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/11 0:02:13

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/11 0:02:13

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑