
简介毕业设计论文《基于Android的居家养老管理系统APP》是一份面向计算机相关专业毕业生与Java开发学习者的完整论文文档涵盖系统开发背景、技术选型、功能模块设计与实现等核心章节。资源为单个docx格式文件大小约4.19MB包含从摘要、关键词到目录及正文的完整结构可直接用于参考论文撰写与系统设计思路梳理。已有201人浏览学习。文中详细阐述了基于Java、MySQL与Spring Boot框架的B/S架构居家养老管理系统的开发过程包括管理员与用户双角色模块划分、软件组件化设计以及服务预约、健康监测等功能配有清晰的开发技术介绍和系统架构说明能帮助读者快速理解Android端养老管理系统的整体构建逻辑并为毕业设计或相关课题提供切实可行的方案模板。1. 居家养老管理APP到底在解决什么问题老人独自在家血压记录在小本子上药吃没吃没人确认上门服务只能麻烦邻居。子女不是不想管是隔着屏幕看不到老人的真实状态。基于Android的居家养老管理系统APP就是把这堆看不见的照护变成能录入、能提醒、能派单的移动端工具。对毕业设计来说这选题的好处是需求真实答辩时每个页面都能讲出使用场景UI、数据库、后台服务、动态权限、第三方SDK等技术点也全覆盖。范围我一般切成四块健康记录、用药提醒、紧急呼叫、服务工单论文和代码按这顺序同步推进工作量可控。适合用Android Studio从零写整套APP的学生下面按表结构、客户端、定位、打包的顺序展开。2. 需求拆解与表结构把养老场景落成SQLite数据模型动Android Studio之前先把“人、健康记录、提醒、订单、呼叫”这五类数据定下来。毕设最怕边写边改表后面处理数据库升级会非常痛。常见做法是单App多角色数据层先用本地SQLite把流程跑通HTTP接口按同一套字段预留后面接后端不返工。2.1 先定角色边界老人端、家属端还是护工端常见错误是一开始就规划老人端、家属端、护工端三个独立APP时间根本不够。我一般做单App多角色登录接口返回role字段老人 role1 看到健康记录、用药提醒、紧急呼叫和下单入口家属 role2 看到绑定老人的健康汇总与工单进度服务人员 role3 看到待接单列表。页面共用同一套Activity首页四个入口按角色显隐主题色用暖色和冷色区分。这样设计的理由是论文结构好写权限管理、页面复用、数据隔离三个点都能单独成节。数据隔离在SQL层做查询统一带elder_id条件避免家属能看到别人家老人的记录这个细节答辩时基本必问。2.2 核心表结构与关键字段约定用六张表就能覆盖主流程表名用途关键字段tbl_user登录账号user_id, username, password_hash, role, bind_elder_idtbl_elder老人档案elder_id, name, birth_date, address, blood_type, emergency_phonetbl_health_record健康记录record_id, elder_id, record_type, value, systolic, diastolic, measure_timetbl_medication_plan用药计划plan_id, elder_id, medicine_name, dose, time_slot, enabledtbl_sos_record紧急呼叫sos_id, elder_id, location, call_time, statustbl_service_order服务工单order_id, elder_id, service_type, appoint_time, status, client_phone字段约定主键统一用自增编号时间一律存毫秒时间戳展示时再格式化避免“2024-07-01 08:00”这种字符串没法做区间查询状态字段用整型枚举不用字符串防止“已接单/接单中/已接”这类笔误。血压数据拆成收缩压和舒张压两列下面SQL里直接给出可用的建表语句。2.3 建表SQL与数据库版本管理-- 健康记录表血压拆成收缩压/舒张压血糖和心率只用value CREATE TABLE IF NOT EXISTS tbl_health_record ( record_id INTEGER PRIMARY KEY AUTOINCREMENT, elder_id INTEGER NOT NULL, record_type TEXT NOT NULL, -- blood_pressure / blood_sugar / heart_rate value TEXT NOT NULL DEFAULT ,-- 血糖或心率数值 systolic INTEGER, -- 血压收缩压 diastolic INTEGER, -- 血压舒张压 unit TEXT DEFAULT , measure_time INTEGER NOT NULL, -- 毫秒时间戳 remark TEXT DEFAULT ); -- 用药计划表time_slot 固定为 HH:mm CREATE TABLE IF NOT EXISTS tbl_medication_plan ( plan_id INTEGER PRIMARY KEY AUTOINCREMENT, elder_id INTEGER NOT NULL, medicine_name TEXT NOT NULL, dose TEXT DEFAULT , time_slot TEXT NOT NULL, enabled INTEGER DEFAULT 1 ); -- 紧急呼叫记录表status 0待处理 1已处理 CREATE TABLE IF NOT EXISTS tbl_sos_record ( sos_id INTEGER PRIMARY KEY AUTOINCREMENT, elder_id INTEGER NOT NULL, location TEXT DEFAULT , call_time INTEGER NOT NULL, status INTEGER DEFAULT 0 );逻辑说明血压拆成两列后可以按收缩压大于140做高危筛选也可以画趋势图时直接做数值运算比在135/85字符串里split再parse可靠得多。medication_plan的enabled字段用来暂停某条提醒而不是删行老人的用药历史要保留。sos_record的location在拨号前写入家属端才能看到事发位置。代码里用Room时建表以Entity注解为准首次安装会自动建表上面SQL是论文“数据库设计”一节要放的逻辑结构。升级用Migration按版本递增只加列的情况一条ALTER TABLE就够如果涉及表重建要先把旧表数据拷到临时表再rename不要直接DROP。毕设演示时可以用fallbackToDestructiveMigration()兜底但论文里要写明这是演示环境取舍。2.4 数据访问层SQLiteOpenHelper 还是 RoomRoom是Google推荐方案编译期校验SQL、用Flow或LiveData监听表变化代码量比SQLiteOpenHelper少一半SQLiteOpenHelper更贴近教材答辩时可以现场讲Cursor和事务。从实际交稿的角度我推荐Room不用手写ContentValues避免低级拼写错误数据库升级有Migration机制配合ViewModel在进程被杀后重建时不丢数据。依赖配置和SDK版本一起定下来android { compileSdk 34 defaultConfig { minSdk 24 // Android 7.0能覆盖中低端老人手机 targetSdk 34 // 适配 Android 14 的精确闹钟、通知、前台服务约束 } } dependencies { def room_version 2.6.1 implementation androidx.room:room-runtime:$room_version annotationProcessor androidx.room:room-compiler:$room_version }参数说明compileSdk 34表示用Android SDK 34编译targetSdk 34决定运行时行为兼容到Android 14第三章的用药提醒会直接踩中它的限制。minSdk定24是因为目标用户手里的旧机很多还是Android 6/7再低会增加大量API分支判断。Java项目用annotationProcessor编译RoomKotlin项目改成ksp(androidx.room:room-compiler:$room_version)。提示Room首次建表按Entity自动生成之后直接改Entity而不升版本真机升级会崩溃并提示“Room cannot verify the data integrity”这是答辩演示最常见的翻车点。每次改表结构必须写Migration并version加1。3. Android客户端实现登录、健康记录和用药提醒登录解决身份健康记录解决数据录入和展示用药提醒解决后台定时任务。下面每段代码后面的参数改动都备注了理由答辩时照这个思路讲就是完整的一节“系统实现”。3.1 登录态与记住密码SharedPreferences 只存 token 不存明文演示版登录可以直接查本地tbl_user表但为了后面接后端不返工我习惯在登录成功后把token和role写进SharedPreferences界面层只认token是否存在private void saveSession(String token, String role, String elderId) { getSharedPreferences(session, MODE_PRIVATE) .edit() .putString(token, token) .putString(role, role) .putString(elder_id, elderId) .putLong(login_time, System.currentTimeMillis()) .apply(); }逻辑说明apply()是异步落盘不会卡主线程commit()在主线程写磁盘会掉帧不要在启动页调用。elder_id一起存下来后面所有查询都拿它做数据隔离。记住密码的常见误区是明文存密码root过的手机上等于裸奔演示环境也要用加盐哈希后再存或者干脆只存tokenApp重启后判断token未过期就放行。3.2 健康记录列表RecyclerView ListAdapter健康记录一天一两条但列表要支持下拉刷新和增量展示用ListAdapter配合DiffUtil比每次notifyDataSetChanged()整表重绘更稳class HealthAdapter : ListAdapterHealthRecord, HealthAdapter.VH(DIFF) { companion object { private val DIFF object : DiffUtil.ItemCallbackHealthRecord() { override fun areItemsTheSame(a: HealthRecord, b: HealthRecord) a.recordId b.recordId override fun areContentsTheSame(a: HealthRecord, b: HealthRecord) a b } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH { val binding ItemHealthBinding.inflate( LayoutInflater.from(parent.context), parent, false) return VH(binding) } override fun onBindViewHolder(holder: VH, position: Int) { val item getItem(position) holder.binding.tvValue.text if (item.recordType blood_pressure) ${item.systolic}/${item.diastolic} else item.value holder.binding.tvTime.text formatTime(item.measureTime) } class VH(val binding: ItemHealthBinding) : RecyclerView.ViewHolder(binding.root) }逻辑说明areItemsTheSame用主键判断是不是同一条记录areContentsTheSame比较整条内容两者都返回true时这一行不重绘。recordType是血压时拼两列显示血糖心率直接用value。首屏加载时在空布局放一个ProgressBar数据到了再隐藏Android进度条在这个场景是最基础也最好用的反馈控件老人看到它在转就不会以为屏幕死了。想加硬件亮点的同学可以把数据源换成低功耗蓝牙血压计用系统BluetoothLeGatt示例里的GATT回调解析厂商数据写入同一个RepositoryUI层完全不用动这一块作为论文创新点能单独撑一章。3.3 用药提醒AlarmManager 精确闹钟与通知权限适配用药提醒不能等用户打开APP才触发必须用AlarmManager在后台定时发通知。Android 12起精确闹钟要特殊权限Android 13起通知要先申请运行时权限这是整套代码里最容易踩版本坑的地方fun scheduleReminder(context: Context, plan: MedicationPlan) { val intent Intent(context, RemindReceiver::class.java) .putExtra(plan_id, plan.planId) .putExtra(medicine, plan.medicineName) val pi PendingIntent.getBroadcast( context, plan.planId.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) val calendar Calendar.getInstance().apply { val parts plan.timeSlot.split(:) set(Calendar.HOUR_OF_DAY, parts[0].toInt()) set(Calendar.MINUTE, parts[1].toInt()) set(Calendar.SECOND, 0) if (before(Calendar.getInstance())) add(Calendar.DAY_OF_YEAR, 1) } val am context.getSystemService(AlarmManager::class.java) val canExact Build.VERSION.SDK_INT 31 || am.canScheduleExactAlarms() if (canExact) { am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, pi) } else { am.setWindow(AlarmManager.RTC_WAKEUP, calendar.timeInMillis, 10 * 60 * 1000L, pi) } }参数说明requestCode用plan.planId转Int保证不同药品的PendingIntent互不覆盖FLAG_IMMUTABLE是Android 12的强制项漏了高版本直接抛异常。canExact的判断必须带版本下限AlarmManager.canScheduleExactAlarms()只在API 31存在minSdk 24若不判断会崩溃。canScheduleExactAlarms()返回false时降级为setWindow窗口长度给10分钟药晚到十几分钟老人可以接受比静默失败好得多。setRepeating精确重复受Doze限制不要用在药品提醒上每天重新调用scheduleReminder更可靠。通知弹出前要建通知渠道并在Android 13申请POST_NOTIFICATIONS权限if (Build.VERSION.SDK_INT 33 ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) ! PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 1001) }说明通知权限和定位一样属于运行时权限拒绝后提醒功能静默失效。用药提醒页面要做一个开关打开时检测权限没权限就跳设置页否则交付后全是“我设了提醒但没响”的反馈。3.4 紧急呼叫一键拨号与短信双通道SOS页面放一个显眼的红色按钮点击后同时做三件事写sos_record、拨打应急电话、给家属发短信。打电话用ACTION_CALL会直接进入拨号CALL_PHONE是危险权限真机演示前先检查public void sosCall(Context context, String phone) { Intent intent new Intent(Intent.ACTION_CALL, Uri.parse(tel: Uri.encode(phone))); if (context.checkSelfPermission(Manifest.permission.CALL_PHONE) PackageManager.PERMISSION_GRANTED) { context.startActivity(intent); } else { // 未授权时先落库拨号由老人在通话界面手动完成 SosRepository.save(context, elderId, locationText); } }逻辑说明没有权限时不要try-catch吞异常先把事件落库再引导用户去设置页开权限整个流程不丢数据。短信通道用ACTION_SENDTO到sms:协议发一条带“老人紧急呼叫定位”的文本权限是SEND_SMS。涉及的权限整理成一张表论文“权限设计”一节直接照这个写权限用途声明与申请时机CALL_PHONE一键呼叫120/家属运行时申请拒绝降级为落库SEND_SMS短信通知家属运行时申请拒绝不影响主流程POST_NOTIFICATIONS用药提醒通知Android 13运行时申请ACCESS_FINE_LOCATIONSOS与工单定位前台定位见第四章注意Android 14对后台启动Activity限制很严SOS点击后的确认弹窗必须在Activity处于前台时触发如果是后台场景用Notification的fullScreenIntent拉起页面不要直接startActivity否则真机上表现为“点了没反应”。4. 养老APP的地图定位与工单流转把上门服务做成闭环前三章管住了老人自己的数据服务闭环还差两环订单从哪来、服务到哪一步。常见做法是引入地图定位拿到当前位置再用一个工单状态机记录流转变更提示用本地通知完成。先讲定位SDK接入再讲工单状态设计最后给一个不依赖推送SDK的消息方案。4.1 高德定位SDK接入Key、SHA1与包名绑定定位SDK习惯用高德接入步骤固定三步开发者后台创建应用填包名和对应签名SHA1加maven依赖AndroidManifest里声明定位权限和meta-data key。SHA1从签名文件里取keytool -list -v \ -keystore ~/.android/debug.keystore \ -alias androiddebugkey -storepass android参数说明debug签名和release签名是两套SHA1后台要各配一次漏配一个就会出现“真机调试正常打包后鉴权失败”的灵异现象。manifest里的key不要硬编码用占位符替换defaultConfig { manifestPlaceholders [amap_key: project.findProperty(AMAP_KEY) ?: ] }application meta-data android:namecom.amap.api.v2.apikey android:value${amap_key} / service android:namecom.amap.api.location.APSService android:exportedfalse / /application说明AMAP_KEY放在项目根目录gradle.properties里并加进.gitignore论文附录截图不会把key泄露出去。定位代码用LocationClient注册回调拿到latitude和longitude后放进SOS记录或工单地址栏。4.2 服务下单与工单状态机字段用整型枚举工单不能只有“未完成/已完成”两个状态老人端要能看见“待接单→服务中→已完成→待评价”的进度服务端要能处理取消。状态字段用整型状态值含义老人端显示可流转到0待接单等待服务人员接单1, 31服务中服务人员已上门2, 32待评价服务完成可评价43已取消订单取消-防止“已完成单被误取消”的SQL写法UPDATE tbl_service_order SET status 3 WHERE order_id ? AND status IN (0, 1);说明WHERE里限定status只能从0或1流转到取消直接堵住“已完成或已评价订单被取消”的脏数据。演示环境单机跑不出并发但论文里写清这条约束答辩时说“这里考虑了并发下的状态竞争”是加分细节。列表按状态过滤展示ViewModel用LiveData驱动viewModel.orders.observe(this) { orders - binding.rvOrders.submitList(orders.filter { it.status ! 3 }) binding.tvEmpty.visibility if (orders.isEmpty()) View.VISIBLE else View.GONE }逻辑说明submitList交给adapter做差量更新取消的订单直接滤掉空状态用可见性控制而不是整页重绘。老人端下单时地址支持两个来源手动输入或“使用当前位置”后者调4.1里封装好的定位回调。4.3 动态权限统一封装拒绝后引导去设置第三章列了四个运行时权限每个页面都写一遍onRequestPermissionsResult代码会膨胀到没法维护。封装一个Permissions工具类所有页面共用object Permissions { fun request(activity: Activity, permission: String, reason: String) { if (Build.VERSION.SDK_INT 23) return if (ContextCompat.checkSelfPermission(activity, permission) PackageManager.PERMISSION_GRANTED) return if (ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)) { Snackbar.make(activity.findViewById(android.R.id.content), reason, 4000) .setAction(去设置) { activity.startActivity( Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse(package:${activity.packageName}))) }.show() } else { ActivityCompat.requestPermissions(activity, arrayOf(permission), 1002) } } }参数说明shouldShowRequestPermissionRationale返回true表示用户拒绝过此时弹Snackbar解释用途再引导去设置页返回false可能是首次申请也可能是勾了“不再询问”配合一个SharedPreferences标志位区分两种场景。requestCode统一用1002回调里只处理被拒的权限并提示降级逻辑避免每个页面写重复分支。注意Android 10的后台定位ACCESS_BACKGROUND_LOCATION要单独再申请一次不能和前台定位一起弹必须先拿到前台权限再申请后台权限。毕设不做轨迹追踪的话不建议申请后台定位工单和SOS都是前台场景省掉这个权限能让用户少一个拒绝你的理由。4.4 消息触达用WorkManager轮询代替推送SDK第三方推送要做厂商通道适配每家手机厂商一套SDK毕设投入产出比太低。服务人员“有新订单”的提示用WorkManager定时轮询待接单数量就够class OrderCheckWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) { override fun doWork(): Result { val pending OrderRepository.countPending() if (pending 0) NotifyHelper.showOrderHint(applicationContext, pending) return Result.success() } } val request PeriodicWorkRequestBuilderOrderCheckWorker(15, TimeUnit.MINUTES) .setInitialDelay(1, TimeUnit.MINUTES) .build() WorkManager.getInstance(context).enqueueUniquePeriodicWork( order_check, ExistingPeriodicWorkPolicy.UPDATE, request)参数说明15分钟是轮询频率下限再短会受系统省电策略约束并且耗电明显enqueueUniquePeriodicWork保证不会重复注册多个轮询任务。OrderRepository目前查SQLite后续接后端时把countPending()换成网络请求即可Activity完全不用动这就是分层带来的可替换性。5. 养老APP打包自测与适老化检查交一份能当场演示的APK功能和代码写完决定答辩现场观感的是两件事APK能不能在真机上装起来演示时数据是否真实可信。下面这套发布前流程40分钟内能跑完。5.1 签名打包与BuildConfig多环境Android Studio里Build → Generate Signed Bundle / APK → 新建keystore一路下一步就能出包命令行则用./gradlew assembleRelease多环境的技巧是用BuildConfig在编译期区分“演示数据”和“真实接口”android { buildFeatures { buildConfig true } } buildTypes { debug { buildConfigField boolean, MOCK_DATA, true buildConfigField String, BASE_URL, \http://10.0.2.2:8080/\ } release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile(proguard-android-optimize.txt) buildConfigField boolean, MOCK_DATA, false buildConfigField String, BASE_URL, \https://api.example.com/\ } }参数说明AGP 8默认关闭buildConfig生成必须打开buildFeatures.buildConfig。10.0.2.2是模拟器访问宿主机的固定地址真机调试要换成电脑的局域网IP。Repository里判断BuildConfig.MOCK_DATA决定查本地库还是发HTTP请求演示版自带假数据交给评阅的release包走正式接口。特别提醒开了minifyEnabled后要给高德SDK加keep规则否则release包定位回调直接崩规则在SDK接入文档的混淆配置里原样复制到proguard-rules.pro即可。5.2 真机回归adb安装、提权与logcat模拟器上定位、闹钟、通知的行为和真机不一致发布前必须用真机跑一遍。最常用的三条adb命令# 安装并拉起主页面 adb install -r app-release.apk adb shell am start -n com.example.icare/.ui.login.LoginActivity # 直接授予通知权限省去手动点弹窗 adb shell pm grant com.example.icare android.permission.POST_NOTIFICATIONS # 按进程PID过滤本应用的崩溃日志 adb logcat --pid$(adb shell pidof -s com.example.icare) *:E说明pm grant对dangerous权限有效测试时省掉反复点弹窗的时间logcat按PID过滤后只看当前APP的Error比全局日志好定位。回归顺序固定成“首次启动→注册登录→录健康→设提醒→下单→SOS”六步每步截图论文的测试用例表就照这个顺序填。5.3 适老化检查大字模式、桌面图标与触达区域养老APP是给老人用的发布前把系统字体调到最大跑一遍首页四入口文字换行、清单页标题截断这类问题会全冒出来。布局文字一律用sp不用dp系统“字体大小”调整时界面才跟着缩放正文颜色用#333333而不是纯黑和白色背景的对比度对老花眼更友好。Android图标在各厂商桌面会被裁成不同形状关键信息别画在图标四角同时最小触达区域做到48dp以上。SOS按钮建议直接做成独立全屏按钮避免老人误触旁边的返回键。最后跑一遍./gradlew lintRelease把Error级别的问题全部清掉再导出APK这份代码才算真正能交付。本文还有配套的精品资源点击获取