老年人服药提醒App:Android闹钟调度与SQLite完整实现

发布时间:2026/10/13 5:24:29
老年人服药提醒App:Android闹钟调度与SQLite完整实现
简介这套源码面向Java毕业设计选题实现的是老年人服药提醒App适用于需要完成完整移动端与服务端项目的计算机专业学生。项目基于Java 1.8与Android开发涉及Activity、Service等四大组件后端采用MySQL 5.7管理用户、药物与服药计划数据并配有SQL初始化脚本和环境部署说明便于快速搭建运行环境。压缩包共包含299个文件约48.17MB其中有56个Java源码、86个依赖Jar包、58个Class文件以及Vue页面、XML配置、JSP、SQL脚本、MP3提醒音频等类型目录结构清晰基本覆盖从前端交互到后端逻辑的完整工程。目前已有222人学习下载适合作为毕业设计参考或Java进阶练习。通过阅读源码可深入理解服药提醒的业务流程、数据库表设计及前后端协作方式同时也能积累环境配置、版本控制与代码调试的实战经验是一份理论与实践并重的学习资料。1. 老年人服药提醒App先把这个毕业设计拆成能落地的功能清单家里老人一天要吃三四种药有的饭前、有的饭后年轻人上班一走老人就容易搞混或者干脆忘掉。这个“java毕业设计之老年人服药提醒app的设计与实现源码.zip”要解决的就是这种高频刚需用药计划建档、到点提醒、服药记录留痕。它的核心逻辑并不复杂——一张用药表存“什么药、吃多少、几点吃”一个闹钟调度器到点触发通知一张记录表把每次服药行为存下来。技术上就是Android原生Java加SQLite难点不在功能多而在提醒的准时率和App被系统杀后台之后的存活率。现在网上能下到的这类源码包很多但能把闹钟和通知做稳的并不多。这篇笔记我会从工程导入开始把闹钟调度、通知栏提醒、数据库设计和真机坑位一次讲透适合正在做这个选题的毕业生也想给转行做Android开发的人一个完整参考。2. 技术选型与工程结构为什么是Android原生Java加SQLite2.1 选型理由跨平台框架在这个选题上并没有优势很多人在开题时会犹豫要不要用Flutter或者uni-app来做我的看法是如果是毕业设计老老实实走Android原生Java是最稳妥的路线。原因有三点第一答辩老师最常问的问题是“闹钟是怎么实现的”“数据存在哪里”原生方案里AlarmManager和SQLiteOpenHelper都是教科书级答案你讲得清楚第二原生工程导出的APK可以直接装到真机上演示不需要额外搭打包环境第三网上现成的参考源码绝大多数是原生Java工程zip包解压后导入Android Studio就能跑改造成本最低。这个项目本质上是一个标准的单机App不需要服务端。用户信息、用药计划、服药记录全部存在本地SQLite里。服务端不是必需的——老人独自使用时数据不上传反而少一层隐私风险。2.2 从zip包到能跑的工程源码导入与三个关键文件拿到老年人服药提醒app的设计与实现源码.zip之后先不要急着双击解压。用解压到当前文件夹而不是直接打开压缩包避免中文路径和特殊字符导致Gradle解析失败。mkdir elderly-medication-reminder cd elderly-medication-reminder unzip ../老年人服药提醒app的设计与实现源码.zip解压后确认根目录结构。一个标准的Android Studio工程应该有settings.gradle、build.gradle、app/src/main三部分。如果解压出来只有一个文件夹嵌套说明作者打包时多套了一层目录你需要手动把内层文件上移才能用Android Studio打开。用Android Studio的“Open”直接选到含settings.gradle的目录等待Gradle同步。这里最常见的翻车点是Gradle版本不匹配工程里的gradle-wrapper.properties指向的版本和你本地的SDK对不上。打开这个文件distributionUrlhttps\://services.gradle.org/distributions/gradle-7.4-bin.zip如果你本地装的是Gradle 8.x建议直接改成7.4或7.5。不要在这个版本问题上纠结太久语法兼容性上7.4对Java 8和Android Gradle Plugin 7.x是最稳的组合。同步完先看app/src/main/AndroidManifest.xml这个文件决定了App的权限和组件声明。一个合格的服药提醒App至少要声明这三样uses-permission android:nameandroid.permission.RECEIVE_BOOT_COMPLETED / uses-permission android:nameandroid.permission.POST_NOTIFICATIONS / uses-permission android:nameandroid.permission.SCHEDULE_EXACT_ALARM /RECEIVE_BOOT_COMPLETED用于开机后恢复闹钟POST_NOTIFICATIONS是Android 13以上动态申请通知权限SCHEDULE_EXACT_ALARM是Android 12以上精准闹钟的声明。缺了任何一个后面都会遇到“提醒不出现”或“重启后所有闹钟丢失”的问题。这三个权限是判断一个源码包作者有没有真正做过真机适配的分水岭。3. 用药提醒核心模块AlarmManager闹钟调度与通知栏提醒的完整实现3.1 闹钟注册setExactAndAllowWhileIdle与PendingIntent的正确用法提醒App的核心是对“某个时间点触发一次通知”的调度。Android里做这件事的类是AlarmManager但直接调用它的set()方法在国产ROM上几乎等于没设。原因是Doze模式和厂商省电策略会拦截非精确闹钟。我一般这样封装闹钟注册方法public void scheduleReminder(Context context, int reminderId, long triggerAtMillis) { AlarmManager alarmManager (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent intent new Intent(context, AlarmReceiver.class); intent.putExtra(reminderId, reminderId); intent.setAction(ACTION_TAKE_MEDICINE); PendingIntent pendingIntent PendingIntent.getBroadcast( context, reminderId, // requestCode 用提醒id确保每个提醒对应独立的PendingIntent intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE ); if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { // Android 12及以上需要先检查是否拥有精准闹钟权限 if (alarmManager.canScheduleExactAlarms()) { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); } } else { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); } }这段代码里有几个参数是踩过坑才知道关键的。requestCode直接用提醒记录的主键id这样每个PendingIntent是独立的取消时不会误伤别的提醒。FLAG_IMMUTABLE是Android 12的强制要求不加会直接崩溃。RTC_WAKEUP表示触发时会把设备从休眠中唤醒这是服药提醒绝对不能省的flag——如果用RTCApp在Doze模式下很可能等闹钟时间过了才收到回调。3.2 服药通知NotificationChannel与前台服务保活闹钟触发后的落地动作是发通知。Android 8.0之后通知必须绑定Channel否则静默失败。这里给出AlarmReceiver的具体实现public class AlarmReceiver extends BroadcastReceiver { Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(ACTION_TAKE_MEDICINE)) { int reminderId intent.getIntExtra(reminderId, -1); String medicineName DBHelper.getInstance(context).getMedicineName(reminderId); // Android 13及以上需要动态申请通知权限 if (Build.VERSION.SDK_INT 33 ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) ! PackageManager.PERMISSION_GRANTED) { return; } showMedicineNotification(context, reminderId, medicineName); } } }showMedicineNotification方法里Channel的importance参数直接决定通知是否弹窗提醒。我见过不少源码直接用了NotificationManager.IMPORTANCE_MIN结果通知是发了但根本不出横幅提醒老人完全感知不到。这里要用IMPORTANCE_HIGHprivate void showMedicineNotification(Context context, int reminderId, String medicineName) { NotificationManager manager (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); String channelId medicine_reminder_channel; if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { NotificationChannel channel new NotificationChannel( channelId, 服药提醒, NotificationManager.IMPORTANCE_HIGH // 高优先级锁屏也能弹出横幅 ); channel.setDescription(定时提醒老人服药); channel.enableVibration(true); manager.createNotificationChannel(channel); } Notification notification new NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_medicine) .setContentTitle(服药提醒) .setContentText(该服用 medicineName 了) .setContentIntent(buildPendingIntentToDetail(context, reminderId)) .setAutoCancel(true) .setPriority(NotificationCompat.PRIORITY_MAX) .setFullScreenIntent(null, true) .build(); manager.notify(reminderId, notification); }这里有一个很多同学不会注意的点manager.notify()的第二个参数notificationId如果和PendingIntent的requestCode用同一个值那么每次到达提醒时间会覆盖之前的通知而不是叠加。如果同一个用药时间设置了多个药建议notificationId用reminderId * 10 药序号避免通知互相覆盖。3.3 用药清单的增删改查RecyclerView加SQLite的操作闭环闹钟只是一个调度器用药数据从哪来还需要一个完整的用药清单管理界面。整个流程是用户在列表页添加药品信息保存到SQLite同时调用scheduleReminder注册闹钟删除药品时也要做两件事——删数据库记录加取消闹钟。很多源码只做了数据库删除而忘了移除PendingIntent这会导致药品已经删了到点仍然响铃。public void deleteMedication(int reminderId) { DBHelper.getInstance(context).deleteMedication(reminderId); AlarmManager alarmManager (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent intent new Intent(context, AlarmReceiver.class); PendingIntent pendingIntent PendingIntent.getBroadcast( context, reminderId, intent, PendingIntent.FLAG_NO_CREATE | PendingIntent.FLAG_IMMUTABLE ); if (pendingIntent ! null) { alarmManager.cancel(pendingIntent); pendingIntent.cancel(); } }注意FLAG_NO_CREATE如果这个requestCode对应的PendingIntent不存在返回null不会额外创建一个空闹钟。这是一个很典型的“删数据但不删闹钟”的补救写法。4. 数据库设计与服药记录从建表到按日统计的完整链路4.1 两张核心表medication和take_record的字段设计服药提醒App的数据库不用很复杂两张表就够了。第一张是medication表存的是用药计划即“什么药、吃多少、什么时候吃”。第二张是take_record表存的是实际执行情况即“用户到底吃了没有、几点吃的”。这里给出建表语句CREATE TABLE medication ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, -- 药品名称 dosage TEXT, -- 单次剂量如 “1片”或“5ml” times_per_day INTEGER, -- 每日几次 reminder_time TEXT NOT NULL, -- 提醒时间存成 “08:00,12:00,20:00” start_date TEXT, -- 开始日期 end_date TEXT, -- 结束日期 is_active INTEGER DEFAULT 1, -- 1启用0停用 create_time TEXT DEFAULT (datetime(now,localtime)) ); CREATE TABLE take_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, medication_id INTEGER NOT NULL, -- 关联medication表 plan_time TEXT NOT NULL, -- 计划服药时间 actual_time TEXT, -- 实际确认服药时间null表示未服 status INTEGER DEFAULT 0, -- 0未服 1已服 2跳过 FOREIGN KEY (medication_id) REFERENCES medication(id) );字段设计上有几个值得注意的决策。reminder_time用逗号分隔三个时间点而不是建一张子表极大简化了增删改逻辑代价是如果每天提醒次数不同解析时要多做一步split。is_active字段用于临时停用某一种药比如老人出差了比直接删记录更安全——删了之后历史服药记录就变成孤儿数据了。4.2 服药记录查询日期范围内依从率的SQL写法服药记录模块的价值不在于存数据而在于生成统计。大部分带源码的毕业设计在这个模块只做了增删改查但答辩时最能撑场面的是“依从率”统计——某段时间内该吃的药里实际吃了多少比例。SELECT m.name, COUNT(r.id) AS total_plans, SUM(CASE WHEN r.status 1 THEN 1 ELSE 0 END) AS taken_count, ROUND(100.0 * SUM(CASE WHEN r.status 1 THEN 1 ELSE 0 END) / COUNT(r.id), 1) AS compliance_rate FROM medication m LEFT JOIN take_record r ON m.id r.medication_id WHERE m.is_active 1 AND date(r.plan_time) BETWEEN ? AND ? GROUP BY m.id;注意这个SQL里用了LEFT JOIN而非INNER JOIN因为如果新增药品后还没生成服药记录INNER JOIN会把这条药品漏掉。日期参数用占位符而不是拼接字符串避免SQL注入问题。生成服药记录的时间点是“闹钟触发时插入当天记录”而不是“用户确认服药时”。如果等用户点确认按钮才插记录那用户一直没点统计里就永远缺数据。正确做法是闹钟广播一响就为这条提醒记录插入一条status0的记录用户点了“已服”再更新状态为1。public void onAlarmFired(int reminderId, String planTime) { SQLiteDatabase db this.getWritableDatabase(); ContentValues values new ContentValues(); values.put(medication_id, reminderId); values.put(plan_time, planTime); values.put(status, 0); db.insert(take_record, null, values); // 此时通知栏弹出提醒等待用户点击“确认服用” }这个设计思路本质上把“计划数据”和“执行数据”分开了统计口径才清晰。5. 服药提醒App避坑指南闹钟不响与通知消失的四个常见问题排查5.1 现象一通知栏没有提醒日志也没有任何报错原因很可能是Android 13以上你没有动态申请POST_NOTIFICATIONS权限。很多源码在AndroidManifest.xml里写了权限声明但MainActivity里没有运行时请求代码。Android 6以上危险权限需要在运行时弹窗申请用户拒绝后通知功能静默失效不会崩溃也不会打印错误。解决方法是进入App时主动请求if (Build.VERSION.SDK_INT 33) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ! PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.POST_NOTIFICATIONS}, 1001); } }同时要在onRequestPermissionsResult里处理拒绝分支——告诉用户“关闭通知将无法收到服药提醒”并引导去设置页手动开启。这一步不做App在部分手机上就是“装上了但永远不提醒”的黑匣子。5.2 现象二闹钟确实触发了但App进程已经被系统杀掉这是国产ROM上最典型的翻车现场。小米、华为、OPPO等系统的省电策略会清理后台进程BroadcastReceiver如果没有在清单里静态注册进程被杀后闹钟广播根本发不到你的代码。检查AndroidManifest.xml里接收器是否静态注册receiver android:name.AlarmReceiver android:exportedfalse intent-filter action android:nameACTION_TAKE_MEDICINE / /intent-filter /receiverAndroidManifest.xml里静态注册的receiver可以在进程被杀后由系统重新拉起。如果你的工程里用的是Context.registerReceiver动态注册那App一被杀所有闹钟全部失效。更稳妥的做法是引导用户在系统设置里把App加入“电池白名单”或“自启动管理”。代码层面可以这样跳转Intent intent new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); intent.setData(Uri.parse(package: getPackageName())); startActivity(intent);但要注意跳转并不代表用户一定点了允许最好在整个主界面顶部常驻一个“未优化电池”的提示条直到用户真正设置完再隐藏。5.3 现象三Android 12以上setExactAndAllowWhileIdle直接抛异常如果你把targetSdkVersion升到了31或更高setExactAndAllowWhileIdle会要求App先获得SCHEDULE_EXACT_ALARM权限。普通uses-permission声明并不保证一定授予——用户可以去设置里关闭精准闹钟权限那样你的精确闹钟调用就会抛SecurityException。代码里要用canScheduleExactAlarms()先检查AlarmManager am (AlarmManager) getSystemService(Context.ALARM_SERVICE); if (Build.VERSION.SDK_INT Build.VERSION_CODES.S) { if (!am.canScheduleExactAlarms()) { // 跳转设置页让用户手动授权或者退化为setWindow不保证精确 Intent intent new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); intent.setData(Uri.parse(package: getPackageName())); startActivity(intent); return; } }这个判断不能省。很多源码包在模拟器上跑得好好的一到真机上就崩溃就是漏了这个检查。5.4 现象四模拟器上一切正常真机上提醒时间误差超过十分钟模拟器不会模拟Doze模式所以你在模拟器上验证的“准时提醒”不具参考价值。真机上系统进入Doze后即使setExactAndAllowWhileIdle也只能保证“尽量准时”尤其在深睡阶段部分厂商的省电策略会强行延迟广播。我一般会在AlarmReceiver的onReceive里打一行日志Log.i(AlarmDebug, alarm fired at: System.currentTimeMillis() , plan time was: planTime);然后把手机放一晚上第二天看日志里实际的触发时间和计划时间的差值。如果误差超过2分钟说明你的App被系统纳入了更严格的省电策略需要回到5.2节引导用户加白名单。这一步一定要做不然答辩现场真机演示万一闹钟迟到了场面会很难看。6. 从能用到可靠验证提醒可靠性与往深做的一个方向6.1 用“服药日志打点”验证提醒链路是否真的走通要把这个App做得可信光靠肉眼等通知是不够的。我会建议你在AlarmReceiver里不只发通知同时写一条日志到SharedPreferences记录每次闹钟实际触发的时间。三天后对比计划时间和实际触发时间就能看出闹钟链路有没有被系统偷懒。这个验证方法在答辩时特别好讲——你可以直接亮出这段数据说“我用7月1日到7月3日的真实记录验证了提醒准时率”。SharedPreferences sp getSharedPreferences(alarm_log, Context.MODE_PRIVATE); String log sp.getString(log, ); log System.currentTimeMillis() , planTime \n; sp.edit().putString(log, log).apply();把这份日志导出成CSV在电脑上打开就能画出触发时间差曲线。如果某个时段连续误差超过5分钟基本可以判定系统在整你而不是你的代码有问题。6.2 想拿高分加一个家属端的服药报告如果你的答辩时间充裕我强烈建议在这个源码基础上加一个低成本的亮点把每日服药记录用FileProvider导出成CSV或者干脆生成一张简单的统计图。不需要引入MPAndroidChart这种重型图表库用Canvas画一个最近7天的条形图代码量不大但演示效果比纯数据列表好得多。以CSV导出为例File file new File(getExternalFilesDir(null), medication_report_ date .csv); FileOutputStream fos new FileOutputStream(file); fos.write(日期,药品名,计划时间,实际时间,状态\n.getBytes(UTF-8)); SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm, Locale.CHINA); // 遍历take_record表逐行写入实际数据 fos.flush(); fos.close(); // 用Intent.ACTION_SEND分享给家属这部分不用过度设计核心是让答辩评委看到你思考过“如何把这个工具变成一个能持续使用的系统”而不是做完一个CRUD就交差。最后说一个我自己带学生的血泪经验这个项目最贵重的代码不是列表页不是数据库而是那个你反复测试过的闹钟调度器。我见过太多人把时间花在换主题、做动画上结果核心的闹钟逻辑在真机上根本没跑通。先把提醒链路从注册、触达、通知到记录完整验证一遍再把界面做得干净一点这个设计就能从“能运行”变成“真的能用”。希望帮到你。本文还有配套的精品资源点击获取