简介:这是一套面向计算机专业本科生的毕业设计级老年人服药提醒系统,聚焦健康养老场景,采用前后端分离架构,解决老年人漏服、错服药物的实际问题,适用于Android课程设计、期末大作业及SpringBoot+Android双栈毕设选题。资源共6个文件,含3个核心压缩包(后台Java服务、Android客户端源码、演示视频)、2份文本说明(部署与使用指南)及1个MySQL数据库脚本(medication.sql),总大小92.18MB,结构清晰、模块职责分明。已有334人学习下载,配套详细注释代码与分步部署教程(含IDEA+AndroidStudio环境配置、MySQL建库建表、接口联调等关键环节),并提供可直接运行的完整工程目录与SQL初始化脚本,新手可快速上手理解登录、用药计划设置、定时提醒、服药打卡、历史记录查询等核心功能实现逻辑。
1. 项目概述与核心价值
最近几年,我身边不少朋友都开始为家里长辈的服药问题操心。老人家记性不好,降压药、降糖药、保健品,一天吃好几次,还分饭前饭后,漏服、错服是常有的事,轻则影响疗效,重则可能引发危险。市面上提醒类的App不少,但要么功能太复杂,要么广告满天飞,对老年人来说并不友好。正好,我指导的一位学生最近完成了他的Android毕业设计,主题就是“基于Android的老年人服药提醒App”,我全程跟进,也帮他梳理了思路、优化了代码。这个项目麻雀虽小,五脏俱全,从需求分析、UI设计、数据库构建到核心功能实现,完整地走了一遍Android应用开发流程。今天,我就把这个项目的设计思路、实现细节,以及我们踩过的坑、总结的经验,毫无保留地分享出来。无论你是正在寻找毕业设计课题的计算机相关专业学生,还是对Android开发感兴趣的入门者,甚至是希望为自己家人定制一个简单好用工具的朋友,这篇文章都能给你提供一份可以直接“抄作业”的详细指南。项目源码和数据库设计文件我也会附上,你可以直接运行、修改,甚至作为你毕业设计的基础框架。
这个App的核心目标非常明确:极简、清晰、可靠。我们摒弃了一切花哨和不必要的功能,聚焦于“提醒”这一核心。老人打开App,首页就是今天要吃的药,一目了然;到点自动响铃加震动,提醒力度足够;记录服药情况,家人可以通过简单的方式查看。为了实现这些,我们选择了最经典也最稳定的技术栈:Android Studio作为开发环境,Java作为编程语言,SQLite作为本地数据库。整个项目没有用到任何复杂的第三方框架,确保逻辑清晰、易于理解和二次开发。接下来,我就带你一步步拆解这个项目,从设计思路到代码实现,从数据库表结构到提醒服务的保活策略,把每个环节都讲透。
2. 整体设计与思路拆解
2.1 核心需求与用户画像分析
做任何应用,第一步永远是搞清楚“为谁做”和“做什么”。我们这个App的目标用户群体非常清晰:需要长期服药的老年人,以及他们背后关心的子女或护工。因此,需求分析必须从这两个角度出发。
对于老年用户,核心诉求是:
- 操作极其简单:图标要大,文字要少,颜色对比要强烈,避免多层级的复杂操作。
- 提醒必须有效:铃声要响亮、持久,最好能配合震动,防止手机静音时错过。
- 信息直观明了:不需要知道“药品学名”,只需要知道“早上吃的白药片”和“晚上吃的红胶囊”。
- 容错性高:误触了要有明确提示,避免一不小心删除了重要记录。
对于子女/护工用户,核心诉求是:
- 便捷管理:能快速为老人添加、设置药品和提醒。
- 远程查看:能方便地查看老人的服药记录,了解是否按时服药。
- 数据可靠:本地数据要有备份机制,防止意外丢失。
基于这些分析,我们决定App的核心功能模块如下:
- 药品管理:添加、编辑、删除药品,包括药品名称、图片、用法用量。
- 提醒计划管理:为每种药品设置具体的提醒时间(可重复,如每天、每周特定几天)。
- 今日待办视图:首页聚合显示当天所有需要服用的药品及时间,是核心交互界面。
- 提醒通知服务:一个在后台长期运行的服务,负责在指定时间触发提醒(铃声、震动、状态栏通知)。
- 服药记录:每次提醒后,记录用户的“已服用”或“跳过”操作,并存储。
- 数据备份/导出:简单的本地数据导出功能,供子女查看。
2.2 技术选型与架构考量
确定了功能,就要选择实现的技术路径。作为毕业设计,技术选型的原则是:成熟、稳定、易于演示和答辩。
开发语言与IDE:Java + Android Studio
- 为什么是Java?虽然Kotlin现在是谷歌首推,但Java的资料更海量,学校教学也大多以Java为主,对于毕业设计来说,社区支持和可参考案例更多,能降低开发门槛。而且Java的稳定性毋庸置疑。
- 为什么是Android Studio?这是官方IDE,对Android开发的支持最完善,内置的模拟器、布局编辑器、性能分析工具链是其他工具无法比拟的。避免使用Eclipse等过时工具。
本地数据库:SQLite + Android原生SQLiteOpenHelper
- 为什么不用Room或GreenDAO?Room等ORM框架确实能简化数据库操作,但对于一个表结构简单(预计不超过5张表)的毕业设计来说,引入框架会增加复杂度,掩盖了“如何设计表关系”、“如何执行CRUD操作”这些基础且重要的知识点。使用原生的
SQLiteOpenHelper,能更清晰地展示你对数据库原理的理解,这在答辩时是加分项。 - SQLite的优势:轻量级、零配置、无需网络、整个数据库就是一个文件,非常适合本地存储服药计划这种结构化数据。
- 为什么不用Room或GreenDAO?Room等ORM框架确实能简化数据库操作,但对于一个表结构简单(预计不超过5张表)的毕业设计来说,引入框架会增加复杂度,掩盖了“如何设计表关系”、“如何执行CRUD操作”这些基础且重要的知识点。使用原生的
后台服务:AlarmManager + BroadcastReceiver + Service
- 这是实现定时提醒的经典“铁三角”组合。
AlarmManager:负责在系统级别设置一个精确的、即使应用退出了也能唤醒的定时器。这里有个关键点:在Android 6.0(API 23)以后,为了优化电量,AlarmManager的定时可能不精确。我们需要使用setExactAndAllowWhileIdle()方法来确保提醒的准时性,这在服药提醒场景下至关重要。BroadcastReceiver:当AlarmManager的定时器触发时,它会发送一个广播。我们需要注册一个广播接收器来捕获这个广播。Service:广播接收器捕获到事件后,启动一个服务(或直接在该接收器内)来执行具体的提醒逻辑,如播放声音、发送状态栏通知等。考虑到Android 8.0(API 26)的后台限制,我们使用startForegroundService()来启动一个前台服务,确保提醒服务不会被系统轻易杀死。
UI框架:原生View + RecyclerView
- 没有使用复杂的Jetpack Compose。对于这个体量的App,使用传统的XML布局和
RecyclerView来构建列表(如药品列表、今日待办列表)更加直接,控制力也更强,能更好地体现你对Android基础UI组件的掌握。
- 没有使用复杂的Jetpack Compose。对于这个体量的App,使用传统的XML布局和
这个架构看起来传统,但每一块都是Android开发的基石,扎实地实现它们,比盲目追求新技术堆砌更能体现你的能力。
3. 数据库设计与核心表结构解析
数据库是这个App的“记忆中枢”,设计得好不好,直接关系到后续功能开发的复杂度和数据可靠性。我们一共设计了四张核心表,关系清晰。
3.1 表结构设计详解
-- 药品表:存储药品的基本信息 CREATE TABLE medicine ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, -- 药品名称,如“降压药” icon_path TEXT, -- 药品图标或照片的本地存储路径,方便老人识别 dosage TEXT, -- 单次用量,如“1片” notes TEXT -- 备注,如“饭前服用”、“红色胶囊” ); -- 提醒计划表:核心表,关联药品和具体的提醒时间规则 CREATE TABLE schedule ( id INTEGER PRIMARY KEY AUTOINCREMENT, medicine_id INTEGER NOT NULL, hour INTEGER NOT NULL, -- 提醒的小时 (0-23) minute INTEGER NOT NULL, -- 提醒的分钟 (0-59) repeat_days TEXT, -- 重复周期,用字符串表示,如“1,2,3,4,5”代表周一到周五 is_active INTEGER DEFAULT 1, -- 是否激活该提醒 (1激活,0关闭) FOREIGN KEY (medicine_id) REFERENCES medicine(id) ON DELETE CASCADE ); -- 服药记录表:记录每次提醒的实际执行情况 CREATE TABLE record ( id INTEGER PRIMARY KEY AUTOINCREMENT, schedule_id INTEGER NOT NULL, scheduled_time INTEGER NOT NULL, -- 计划服药时间(时间戳) actual_time INTEGER, -- 实际服药时间(时间戳),为空表示未服用或跳过 status INTEGER NOT NULL, -- 状态:0-待处理,1-已服用,2-跳过 FOREIGN KEY (schedule_id) REFERENCES schedule(id) ); -- 备份元数据表(可选):记录每次备份的信息 CREATE TABLE backup_meta ( id INTEGER PRIMARY KEY AUTOINCREMENT, backup_time INTEGER NOT NULL, file_path TEXT NOT NULL );设计思路与难点解析:
- 表关系分离:将
medicine(药品)和schedule(计划)分开,是因为一种药可能有多个服用时间(如早晚各一次)。这种设计避免了数据冗余。 - 重复周期的存储:
schedule表中的repeat_days字段,我们采用了字符串存储,如“1,3,5”代表周一、周三、周五。为什么不直接用七个布尔字段?因为这样在查询“今天是否需要服药”时更灵活。我们可以用SQL的IN语句或程序分割字符串来判断。当然,更规范的做法是用一个单独的“计划-星期”关联表,但考虑到毕业设计的复杂度,字符串方式更直观。 - 级联删除:在
schedule表的外键约束中,我们设置了ON DELETE CASCADE。这意味着,当你删除一种药品时,所有关联的提醒计划也会被自动删除。这符合业务逻辑,但必须谨慎,需要在UI上给用户明确的二次确认提示。 - 记录表的状态设计:
record表的status字段是关键。actual_time只在status为1(已服用)时有值。这种设计便于统计“准时服药率”、“漏服次数”等数据。
3.2 数据库操作类封装
直接在所有Activity里写SQL语句是灾难性的。我们必须封装一个DatabaseHelper类,继承SQLiteOpenHelper,来统一管理数据库的创建、升级和常用操作。
public class DatabaseHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "medicine_reminder.db"; private static final int DATABASE_VERSION = 1; // 单例模式,确保全局只有一个数据库连接 private static DatabaseHelper instance; public static synchronized DatabaseHelper getInstance(Context context) { if (instance == null) { instance = new DatabaseHelper(context.getApplicationContext()); } return instance; } private DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { // 执行上面提到的建表语句 db.execSQL(CREATE_TABLE_MEDICINE); db.execSQL(CREATE_TABLE_SCHEDULE); db.execSQL(CREATE_TABLE_RECORD); db.execSQL(CREATE_TABLE_BACKUP_META); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 简单处理:删除旧表,创建新表。实际项目需要做数据迁移。 db.execSQL("DROP TABLE IF EXISTS medicine"); db.execSQL("DROP TABLE IF EXISTS schedule"); db.execSQL("DROP TABLE IF EXISTS record"); db.execSQL("DROP TABLE IF EXISTS backup_meta"); onCreate(db); } // 封装增删改查方法,例如: public long addMedicine(Medicine medicine) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("name", medicine.getName()); values.put("icon_path", medicine.getIconPath()); // ... 其他字段 long id = db.insert("medicine", null, values); db.close(); return id; // 返回新插入行的ID } public List<Medicine> getAllMedicines() { List<Medicine> medicineList = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); Cursor cursor = db.rawQuery("SELECT * FROM medicine ORDER BY name", null); if (cursor.moveToFirst()) { do { Medicine medicine = new Medicine(); medicine.setId(cursor.getInt(cursor.getColumnIndex("id"))); medicine.setName(cursor.getString(cursor.getColumnIndex("name"))); // ... 从cursor解析其他字段 medicineList.add(medicine); } while (cursor.moveToNext()); } cursor.close(); db.close(); return medicineList; } // ... 其他针对schedule, record表的操作方法 }注意:这里为了清晰,省略了
Medicine、Schedule等实体类(Model)的定义。在实际项目中,你应该创建对应的Java Bean类,并通过Cursor或ContentValues与数据库交互。这是标准的MVC模式实践。
4. 核心功能模块实现详解
4.1 药品与提醒计划管理模块
这个模块对应两个主要界面:药品列表页和药品详情/编辑页。
药品列表页:使用RecyclerView展示所有药品。每个Item显示药品图标、名称和下次提醒时间(需要关联查询schedule表)。这里有一个用户体验细节:在RecyclerView的Adapter中,我们为每个Item设置了长按监听,弹出菜单(删除、编辑)。删除操作必须弹出一个对话框进行二次确认,并执行级联删除。
添加/编辑药品页:这是一个标准的表单页面。核心难点在于药品图片的处理。我们提供了两个选择:从系统相册选择,或者直接拍照。
- 选择或拍摄图片后,我们不能直接保存原始图片的路径到数据库,因为原始图片可能很大,且路径可能变化。
- 正确的做法是,将图片压缩后,保存到App的私有存储目录(
Context.getFilesDir()或getExternalFilesDir()),然后将这个相对路径或文件名存入数据库的icon_path字段。 - 显示时,再根据这个路径加载图片。这样可以有效管理存储空间。
设置提醒时间:在药品编辑页,我们放置了一个“添加提醒时间”的按钮。点击后,弹出一个时间选择器(TimePickerDialog)让用户选择小时和分钟。然后,需要一个界面让用户选择重复周期(每天、工作日、自定义星期几)。这里我们使用一个多选框(CheckBox)列表来表示周一到周日。用户的选择最终会转换成schedule表中repeat_days字段的字符串格式。
数据联动:当保存药品和其关联的多个提醒计划时,需要在一个事务(Transaction)中完成。先插入medicine表,获取生成的medicine_id,再循环插入schedule表。使用事务可以保证数据的一致性,避免只插入了药品却没有计划的情况。
4.2 今日待办视图与首页设计
首页是老人最常看的页面,必须极致简洁。我们使用一个RecyclerView来展示“今天”需要服用的所有药品计划。
核心查询逻辑:
- 获取今天的星期几(Calendar.DAY_OF_WEEK),注意Calendar中周日是1,周一是2,以此类推。我们需要将其转换为我们自定义的格式(如周一为1)。
- 编写SQL查询,关联
medicine和schedule表。查询条件是:schedule.is_active = 1,并且schedule.repeat_days包含今天的星期几(例如,今天周三,数字是3,那么repeat_days字段需要包含‘3’)。 - 对查询结果按
schedule.hour和schedule.minute进行排序,这样药品就会按时间顺序排列。 - 每个Item的布局要清晰:左侧是药品图标和名称,中间是用法用量,右侧是提醒时间(如“08:00”)和一个大的“已服用”按钮。
“已服用”按钮的逻辑: 用户点击后,需要做两件事:
- 更新UI:按钮变为不可用状态,或者改变颜色和文字(如“已完成”)。
- 更新数据库:在
record表中插入一条记录,status设为1(已服用),并记录actual_time(当前时间戳)。同时,可以关联更新这条计划对应的schedule状态,或者不做处理,等待第二天的计划再次生成。
数据刷新:首页的数据需要及时刷新。我们可以在onResume()生命周期方法中重新执行查询并更新列表。更优雅的做法是使用观察者模式,当药品或计划有增删改时,通知首页刷新,但这对于毕业设计来说,onResume()刷新已经足够清晰。
4.3 后台提醒服务的实现与保活策略
这是整个App的技术难点和核心,确保提醒能准时、可靠地触发。
实现流程:
设置定时器:在用户添加、修改或启用一个提醒计划时,我们需要为每一个未来的提醒点设置一个
AlarmManager定时器。AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(context, ReminderReceiver.class); // 指向广播接收器 intent.setAction("ACTION_REMINDER"); intent.putExtra("schedule_id", scheduleId); // 携带计划ID // 使用PendingIntent,使得系统能在未来触发它 PendingIntent pendingIntent = PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); // 计算下一次触发的时间戳 Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minute); calendar.set(Calendar.SECOND, 0); // 如果设定的时间已过今天,则设置为明天 if (calendar.getTimeInMillis() <= System.currentTimeMillis()) { calendar.add(Calendar.DAY_OF_YEAR, 1); } long triggerAtMillis = calendar.getTimeInMillis(); // 关键:使用精确且允许在低电耗模式下触发的API if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); } else { alarmManager.set(AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); }注意:
requestCode需要唯一,通常可以用schedule_id。FLAG_IMMUTABLE是Android 12(API 31)以后的要求,用于声明PendingIntent不可变,增强安全性。接收广播:
ReminderReceiver是一个BroadcastReceiver,当AlarmManager触发时,系统会调用它的onReceive方法。public class ReminderReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if ("ACTION_REMINDER".equals(intent.getAction())) { int scheduleId = intent.getIntExtra("schedule_id", -1); if (scheduleId != -1) { // 启动前台服务来处理提醒逻辑 Intent serviceIntent = new Intent(context, ReminderService.class); serviceIntent.putExtra("schedule_id", scheduleId); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(serviceIntent); } else { context.startService(serviceIntent); } } } } }记住要在
AndroidManifest.xml中静态注册这个Receiver。执行提醒(前台服务):
ReminderService是一个Service。在onStartCommand方法中,我们根据传入的schedule_id从数据库查询药品信息,然后执行提醒操作。- 创建通知渠道(Android 8.0+必需):在服务启动时,创建用于显示提醒通知的渠道。
- 发送高优先级通知:使用
NotificationCompat.Builder构建一个通知,设置醒目的标题(如“服药时间到!”)、内容(药品名和用量),并启用声音、震动和指示灯。 - 启动全屏界面(可选但推荐):对于服药提醒这种强提醒,可以设置一个
PendingIntent,点击通知后跳转到一个全屏的提醒Activity。这个Activity会以对话框形式覆盖在其他应用之上,并持续响铃,直到用户点击“已服用”或“跳过”。 - 调用系统铃声和震动:除了通知,还可以使用
MediaPlayer播放一段本地音频,并使用Vibrator服务触发震动,形成多重提醒。 - 将自己设为前台服务:调用
startForeground(notificationId, notification),这样系统就不会轻易杀死这个服务。notificationId需要是一个非零的常量。
保活与省电的平衡:
- 不要使用永不停止的后台服务:那会极度耗电,且在新版Android上会被系统限制。我们的策略是“按需唤醒”。
AlarmManager只在预定时间唤醒应用,执行完提醒后,服务在适当时候(如用户操作后)自行停止。 - 处理重复提醒:对于每天重复的计划,在用户点击“已服用”后,需要为下一次(通常是明天同一时间)重新设置一个
AlarmManager定时器。 - 设备重启:
AlarmManager设置的定时器在设备重启后会丢失。因此,我们需要注册一个监听BOOT_COMPLETED广播的Receiver,在开机后重新从数据库读取所有激活的计划,并重新设置定时器。这个Receiver同样需要在Manifest中声明,并且需要用户手动授予应用“自启动”权限(不同手机厂商设置路径不同,这点需要在App内提示用户)。
4.4 数据备份与导出功能
这个功能主要是为了方便子女查看。我们实现一个简单的逻辑:将整个SQLite数据库文件(.db)复制到外部存储的公共目录(如下载文件夹)。
public boolean backupDatabase(Context context) { File dbFile = context.getDatabasePath(DATABASE_NAME); if (!dbFile.exists()) return false; // 创建备份目标文件夹,例如 /Download/MedicineReminderBackup/ File backupDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "MedicineReminderBackup"); if (!backupDir.exists()) { backupDir.mkdirs(); } String backupFileName = "medicine_backup_" + System.currentTimeMillis() + ".db"; File backupFile = new File(backupDir, backupFileName); try (FileInputStream fis = new FileInputStream(dbFile); FileOutputStream fos = new FileOutputStream(backupFile)) { byte[] buffer = new byte[1024]; int length; while ((length = fis.read(buffer)) > 0) { fos.write(buffer, 0, length); } // 在backup_meta表中记录这次备份 // ... 记录备份时间、文件路径等 return true; } catch (IOException e) { e.printStackTrace(); return false; } }重要提示:从Android 10(API 29)开始,访问外部公共目录需要申请
MANAGE_EXTERNAL_STORAGE权限,并且应用需要上架Google Play或经过特殊审核。对于毕业设计演示,我们可以将备份文件放在App自身的私有外部存储目录(Context.getExternalFilesDir(null)),这个目录不需要特殊权限,文件可以通过USB连接电脑导出,或者通过App内的文件分享功能发送。在实际需求说明中,要向评委解释这种权限策略的变化。
5. 开发中的常见问题与避坑指南
在实际开发中,我们遇到了不少“坑”,这里总结出来,希望能帮你节省时间。
5.1 权限申请与兼容性处理
- 通知渠道:Android 8.0以上,必须为通知创建渠道,否则通知无法显示。代码中要做好版本判断。
- 后台启动服务:Android 8.0以上,禁止在后台创建服务。必须使用
startForegroundService(),并在创建服务后5秒内调用startForeground(),否则会引发ANR(应用无响应)和崩溃。 - 精确闹钟权限:从Android 12(API 31)开始,使用
setExactAndAllowWhileIdle()等精确闹钟API,需要在AndroidManifest.xml中声明SCHEDULE_EXACT_ALARM权限。在Android 13(API 33)及更高版本,此权限变为运行时权限,需要动态申请。
在代码中检查:<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); if (!alarmManager.canScheduleExactAlarms()) { // 引导用户去设置页面开启权限 Intent intent = new Intent(android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM); startActivity(intent); } } - 开机自启权限:监听
BOOT_COMPLETED广播需要在Manifest中声明权限,并且很多国内厂商手机需要用户手动在“设置->应用->自启动”里打开。这是一个无法通过代码完全解决的问题,需要在App内用图文引导用户。
5.2 定时器(AlarmManager)的坑
- PendingIntent的匹配:
PendingIntent的匹配规则基于Intent的组件、Action、Data等。如果你用同一个requestCode和Intent去设置新的闹钟,它会覆盖旧的。因此,requestCode(我们用了schedule_id)必须唯一,否则可能无法为多个计划设置独立的闹钟。 - 时间计算错误:计算下一次触发时间时,务必考虑“如果现在时间已经过了今天设定的时间,则应该设定为明天”这个逻辑。否则,闹钟会立即触发。
- 低电耗模式:
setExactAndAllowWhileIdle()是保证在低电耗(Doze)模式下也能相对准时触发的关键API,务必使用。
5.3 数据库与多线程
- 数据库连接泄漏:每次调用
getWritableDatabase()或getReadableDatabase()后,在操作完成后一定要记得调用db.close()。更好的做法是使用单例模式的DatabaseHelper,并在一个Activity或Fragment的生命周期内保持一个连接,在onDestroy时关闭。我们示例中的写法(每次操作后关闭)是安全的,但频繁打开关闭会影响性能,对于简单的App可以接受。 - 在主线程执行耗时查询:如果药品数量很多,在UI线程(主线程)执行复杂的数据库查询会导致界面卡顿。应该使用
AsyncTask、Thread+Handler或者更现代的RxJava、Kotlin协程来执行数据库操作。对于毕业设计,使用AsyncTask来演示异步操作是一个不错的选择。
5.4 用户体验细节
- 提醒的防打扰:在深夜(比如23:00到次日7:00)的服药提醒,应该自动调整为静音震动模式,避免惊扰老人休息。可以在设置提醒时让用户选择“免打扰时段”,或者在触发提醒时判断当前时间。
- “跳过”操作的处理:用户可能暂时不想吃这个药,点了“跳过”。这时除了记录状态,还应该设置一个“延迟提醒”,比如30分钟后再提醒一次。这可以通过设置一个新的、一次性
AlarmManager来实现。 - 本地化与无障碍:考虑添加大字体模式的支持(在
AndroidManifest.xml的<application>标签中设置android:supportsRtl="true"并测试),以及为所有ImageButton添加contentDescription,方便视障用户使用读屏软件。这些细节在答辩时能体现你的匠心。
6. 项目部署、测试与答辩准备
6.1 源码结构与运行
项目的源码结构应该清晰:
app/ ├── src/main/ │ ├── java/com/yourdomain/medicineReminder/ │ │ ├── activity/ // 所有Activity │ │ ├── adapter/ // RecyclerView适配器 │ │ ├── database/ // DatabaseHelper及相关实体类 │ │ ├── receiver/ // 广播接收器 (ReminderReceiver, BootReceiver) │ │ ├── service/ // 服务 (ReminderService) │ │ └── utils/ // 工具类 (时间处理、文件操作等) │ ├── res/ // 资源文件 │ └── AndroidManifest.xml确保在AndroidManifest.xml中注册了所有必要的组件(Activity、Service、Receiver)并声明了权限。
6.2 测试要点
- 功能测试:
- 添加、编辑、删除药品和计划。
- 修改系统时间,测试提醒是否能准时触发(注意:模拟器上测试AlarmManager有时不太稳定,真机测试更可靠)。
- 测试“已服用”、“跳过”操作,并检查
record表是否正确记录。 - 测试数据备份功能,检查文件是否生成。
- 兼容性测试:在Android 8.0、10.0、12.0等不同版本的手机或模拟器上运行,重点测试通知、后台服务和权限相关功能。
- 边界测试:
- 添加一个过去时间的提醒,看是否会立即触发或正确设置为明天。
- 设置大量重复提醒(如每小时一次),测试性能和电量消耗。
- 在提醒触发时,强制停止App,看提醒是否依然有效(测试
AlarmManager的独立性)。
6.3 毕业设计文档与答辩
- 论文/报告结构:除了常规的摘要、绪论、需求分析外,重点撰写“系统设计”和“系统实现”章节。详细画出数据库E-R图、系统架构图、核心功能流程图。在“系统实现”部分,不要贴大段代码,而是选择1-2个核心片段(如设置AlarmManager的代码、首页数据查询的SQL)进行讲解,并配以流程图说明。
- 演示准备:
- 准备一个完整的演示脚本:从打开App开始,演示添加药品、设置复杂提醒、等待或模拟时间触发提醒、处理提醒、查看记录、备份数据等一系列操作。
- 准备应对提问:老师常问的问题包括:“为什么用SQLite不用Room?”、“后台服务如何保活?”、“不同Android版本兼容性怎么处理?”、“你的数据库设计如何保证数据一致性?”、“如果用户卸载了App,数据怎么恢复?(可以引申到云备份的设想)”。根据我们上面讨论的内容,你都能给出有理有据的回答。
- 突出亮点和难点:主动介绍你在“精准跨版本提醒实现”、“数据库事务处理”、“前台服务保活”等方面所做的研究和解决方案。
这个项目虽然定位是毕业设计,但它解决的是一个真实且普遍的需求。通过完成它,你不仅能掌握Android应用开发的全流程,更能深入理解移动端在特定场景下的技术挑战和解决方案。希望这份超详细的拆解能为你点亮一盏灯,祝你开发顺利,答辩成功!源码包我已经整理好了,包含了所有上述模块的实现和详细的代码注释,你可以在此基础上进行修改和扩展。
本文还有配套的精品资源,点击获取