☰
基于Android的社区居家养老服务APP源码设计与实现
2026/10/11 13:05:21 网站建设 项目流程

简介:这是一套面向Android开发初学者与移动应用课程设计者的社区居家养老服务APP完整源码,围绕居家养老场景整合了注册登录、上门服务预约、送药配送、营养餐推荐、健康数据记录、医护聊天及个人信息管理等核心模块,适合作为毕业设计、课程大作业或Android综合练习的参考项目。压缩包共约2000个文件,整体89.43MB,以304个java业务代码、535个xml布局与配置、429个flat编译缓存、268个json数据文件为主,另含205张png与94张jpg界面素材、30个so库及少量jsp、js、jar等辅助文件,工程结构相对完整。目前已有151人学习下载。读者可从中获取模块划分思路、页面跳转逻辑、预约与配送流程实现方式,以及血压、血糖、甘油三酯等健康数据记录的界面组织参考,便于快速理解项目骨架并二次改造。

1. 社区居家养老服务 APP:从 Android 端到后台,一套能跑通的源码该长什么样

做社区居家养老这个方向,很多人第一反应是「不就是个预约加商城吗」。真上手才发现,它跟普通电商 APP 的差别全在细节里:老人端字号要大、按钮要粗、流程要短,家属端要能代下单、代看健康数据,社区端要能派单、核销、管补贴,后台还要对接民政口径的服务分类。标题里说的「基于 Android 的社区居家养老服务 APP 的设计系统源码下载」,本质是一套已经把这些角色和流程拆好的 Android 客户端加服务端骨架。它解决的不是「有没有界面」,而是「服务工单怎么从家属下单流转到助老员上门再回传」这条链路。适合两类人:想快速搭一个养老类 MVP 的独立开发者,以及需要给社区项目做技术验证的团队。下面按我实际搭过一遍的顺序,把选型、跑通、踩坑讲清楚。

2. 先定角色和工单模型:养老 APP 和普通商城差在哪

2.1 四类角色决定了你的表结构

普通商城只有买家和卖家,养老 APP 至少四类:老人(或被监护人)、家属、助老员/服务商、社区管理员。这四类角色不是权限开关那么简单,它直接决定数据归属。比如一条服务工单,下单人可能是家属,享受人可能是老人,执行人是助老员,审核人是社区。如果你按普通电商的user_id -> order建表,后面加「代下单」和「补贴核销」时必然要重构。

我一般会先落三张核心表:elder(老人档案,含身份证脱敏、能力等级)、service_order(工单,含下单人、享受人、服务项、补贴金额、状态机)、service_item(服务项字典,对应社区公布的服务清单)。状态机是重点,常见的是:待接单 → 已接单 → 服务中 → 待确认 → 已完成 → 已核销。少一个状态,后面补贴对账就说不清。

-- 工单核心表,注意下单人和享受人分离 CREATE TABLE service_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE, booker_id BIGINT NOT NULL COMMENT '下单人(家属/老人)', elder_id BIGINT NOT NULL COMMENT '服务享受人', worker_id BIGINT DEFAULT NULL COMMENT '接单助老员', item_id INT NOT NULL COMMENT '服务项ID', status TINYINT NOT NULL DEFAULT 0 COMMENT '0待接单 1已接单 2服务中 3待确认 4已完成 5已核销', subsidy_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT '补贴抵扣金额', pay_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT '自付金额', service_time DATETIME COMMENT '预约上门时间', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_elder (elder_id), INDEX idx_worker_status (worker_id, status) ) COMMENT='居家养老服务工单';

这段建表的关键在booker_id和elder_id分开,以及subsidy_amount单独存。参数上status用 TINYINT 而不是枚举字符串,是为了后面状态机流转时做数值比较和索引更省事。idx_worker_status这个联合索引是给助老员端「我的待办」列表用的,没有它,助老员一多列表就慢。

2.2 服务项字典要能对上社区口径

服务项不能自己拍脑袋写「保洁」「陪诊」,要能映射到社区实际公布的服务分类,否则核销时对不上。常见做法是字典表加一个gov_code字段,存社区或民政那边的分类编码。这样后台导出对账单时,直接按gov_code聚合就行。

CREATE TABLE service_item ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64) NOT NULL, gov_code VARCHAR(32) COMMENT '对接社区/民政的服务分类编码', unit VARCHAR(16) DEFAULT '次', ref_price DECIMAL(10,2) COMMENT '参考价', need_cert TINYINT DEFAULT 0 COMMENT '是否需要资质(如医疗类)', enabled TINYINT DEFAULT 1 );

need_cert这个字段容易被忽略。陪诊、康复这类服务,助老员是需要持证的,下单时如果不校验,出了事责任说不清。我一般在下单接口里加一层校验:服务项need_cert=1时,检查接单助老员的资质表里有没有对应证书且未过期。

2.3 为什么不用现成的电商开源改

很多人想省事,拿一套商城源码改。血泪经验是:商城改养老,改到「代下单 + 补贴 + 工单状态机」这三块时,基本等于重写。商城的订单模型是「谁买谁用」,养老是「谁买谁用不一定」,这个根本差异会渗透到支付、退款、对账每一处。所以更稳的路径是:拿一套轻量的后台管理框架(比如 Spring Boot + MyBatis 那类常见组合)做底座,业务表自己按上面三张核心表扩,前端 Android 端单独写。这样后面加「长护险对接」「政府补贴结算」时不会推倒重来。

3. Android 端跑通最小闭环:登录、下单、接单三屏

3.1 环境与依赖:别一上来就追新版本

Android 端我一般用 Android Studio 建一个空 Activity 项目,minSdkVersion定在 24。为什么是 24 而不是更低?因为养老 APP 的用户手机很多是子女淘汰下来的旧机,但 24(Android 7.0)已经能覆盖绝大多数,再往下兼容成本陡增,尤其是权限和通知。targetSdkVersion跟当前稳定版走即可。

网络层用 Retrofit + OkHttp,图片加载 Glide,状态管理用 ViewModel + LiveData。这几个是养老类 APP 里最不容易翻车的组合,社区里资料也全。依赖在app/build.gradle里加:

dependencies { implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3' implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.1' implementation 'androidx.lifecycle:lifecycle-livedata:2.5.1' implementation 'com.github.bumptech.glide:glide:4.13.2' }

版本号这里给的是我实际跑通过的组合,不是越新越好。Retrofit 2.9 和 OkHttp 4.9 搭配稳定,再往上有些版本对旧 Gradle 插件不友好,容易在 sync 阶段就报错。如果你android studio 下载的是最新版,Gradle 插件版本记得和 AGP 对齐,否则第一步就卡住。

3.2 登录与角色分流

登录接口返回的不只是 token,还要带角色标识,因为同一个 APP 装在不同人手机上,进去的首页不一样。老人/家属看到的是「下单」,助老员看到的是「接单」。

// LoginActivity 里处理登录结果 api.login(phone, code).enqueue(new Callback<LoginResp>() { @Override public void onResponse(Call<LoginResp> call, Response<LoginResp> resp) { if (resp.isSuccessful() && resp.body() != null) { LoginResp data = resp.body(); // 存 token,后续请求头带上 SPUtils.put("token", data.getToken()); // 按角色跳不同首页,这是养老 APP 和普通 APP 最大的区别 switch (data.getRole()) { case "ELDER": case "FAMILY": startActivity(new Intent(LoginActivity.this, OrderCreateActivity.class)); break; case "WORKER": startActivity(new Intent(LoginActivity.this, TaskListActivity.class)); break; default: startActivity(new Intent(LoginActivity.this, MainActivity.class)); } finish(); } else { Toast.makeText(LoginActivity.this, "登录失败,请重试", Toast.LENGTH_SHORT).show(); } } @Override public void onFailure(Call<LoginResp> call, Throwable t) { Toast.makeText(LoginActivity.this, "网络异常", Toast.LENGTH_SHORT).show(); } });

逻辑说明:role字段是服务端在登录时根据账号类型下发的,客户端只做跳转,不做权限判断。参数上phone和code走短信验证码,养老场景里老人记不住密码,验证码登录比密码登录接受度高得多。注意onFailure里不要只打日志,要给用户可见提示,老人对「没反应」的容忍度极低。

3.3 下单页:把流程压到三步以内

下单页是养老 APP 最容易做砸的地方。普通 APP 可以五六个步骤,老人端必须压到「选服务 → 选时间 → 确认」三步。地址默认取老人档案里的,不让手填。

// OrderCreateActivity 提交工单 private void submitOrder() { if (selectedItem == null) { toast("请选择服务项目"); return; } if (serviceTime == null) { toast("请选择上门时间"); return; } OrderReq req = new OrderReq(); req.setItemId(selectedItem.getId()); req.setElderId(currentElderId); // 从档案带出,不让用户选 req.setServiceTime(format(serviceTime)); req.setRemark(remarkInput.getText().toString()); api.createOrder(req).enqueue(new Callback<OrderResp>() { @Override public void onResponse(Call<OrderResp> call, Response<OrderResp> resp) { if (resp.isSuccessful()) { toast("下单成功,等待接单"); finish(); } else { // 服务端返回的失败原因要透传,比如"该时段已约满" toast(parseError(resp)); } } @Override public void onFailure(Call<OrderResp> call, Throwable t) { toast("提交失败,请检查网络"); } }); }

参数说明:elderId从当前登录账号绑定的老人档案里取,不让用户在下单时选,是为了避免家属给非绑定老人下单导致责任不清。serviceTime建议用时间选择器限制在「明天起 7 天内」,太远的预约助老员排班没法排。parseError要把服务端的业务错误码翻译成人话,比如「该时段已约满」比「错误码 4001」有用得多。

3.4 助老员接单列表

助老员端核心是一个列表加状态流转。列表按worker_id + status查,接单就是改状态。

// TaskListActivity 拉取待接单和进行中 api.getTasks(workerId, "0,1,2").enqueue(new Callback<List<Order>>() { @Override public void onResponse(Call<List<Order>> call, Response<List<Order>> resp) { if (resp.isSuccessful()) { adapter.submitList(resp.body()); } } @Override public void onFailure(Call<List<Order>> call, Throwable t) { /* 提示重试 */ } });

"0,1,2"这个参数是状态集合,服务端用IN查询。为什么把待接单和进行中放一个列表?因为助老员通常就一两个人管一个片区,分开两个 tab 反而增加操作。接单动作调acceptOrder(orderId),服务端要做乐观锁,防止两个助老员同时接同一单。

4. 后台与数据打通:工单流转、补贴核销、对账导出

4.1 状态机流转要放在服务端

客户端只负责触发动作,状态能不能流转必须服务端说了算。比如「服务中 → 待确认」只能由助老员触发,「待确认 → 已完成」只能由家属或老人确认。常见做法是写一个状态机校验方法:

// 服务端状态流转校验 private static final Map<Integer, List<Integer>> ALLOWED = new HashMap<>(); static { ALLOWED.put(0, Arrays.asList(1)); // 待接单 -> 已接单 ALLOWED.put(1, Arrays.asList(2)); // 已接单 -> 服务中 ALLOWED.put(2, Arrays.asList(3)); // 服务中 -> 待确认 ALLOWED.put(3, Arrays.asList(4)); // 待确认 -> 已完成 ALLOWED.put(4, Arrays.asList(5)); // 已完成 -> 已核销 } public void transit(Long orderId, int from, int to, Long operatorId) { List<Integer> allowed = ALLOWED.get(from); if (allowed == null || !allowed.contains(to)) { throw new BizException("非法的状态流转"); } // 再校验操作人角色是否匹配该流转 orderMapper.updateStatus(orderId, to, operatorId); }

这段是防「越权改状态」的关键。参数from和to都要传,不能只传to,否则并发下可能基于旧状态误判。operatorId用来记录操作人,后面出纠纷时能查是谁点的。

4.2 补贴核销与对账导出

补贴核销是养老项目和普通项目最大的分水岭。常见做法是工单完成时,按服务项和老人能力等级算出补贴金额,写入subsidy_amount,核销时生成一条核销记录。对账导出按gov_code聚合,导出 CSV 给社区。

字段含义注意
order_no工单号对账主键
gov_code服务分类编码必须和社区口径一致
elder_name老人姓名导出时脱敏
subsidy_amount补贴金额单独列,不和自付混
service_time服务时间核销依据

导出时老人身份证、手机号要脱敏,这是硬要求。我一般只导姓名加工单号,需要明细时再按权限查。

4.3 接口鉴权与数据隔离

后台接口必须做数据隔离:社区管理员只能看本社区的工单,助老员只能看自己的。常见做法是在 token 里带community_id和role,查询时强制拼条件,而不是靠前端传。

// 查询工单时强制拼数据权限 public List<Order> listOrders(OrderQuery q, LoginUser user) { if ("WORKER".equals(user.getRole())) { q.setWorkerId(user.getUserId()); // 助老员只能看自己的 } else if ("COMMUNITY".equals(user.getRole())) { q.setCommunityId(user.getCommunityId()); // 社区只能看本社区 } return orderMapper.selectByQuery(q); }

参数user从 token 解析,不信任前端传的communityId。这一步不做,后面数据串了就是大事故。

5. 避坑与排查:养老 APP 上线前必须过的五道坎

5.1 老人端字体被系统设置放大后布局错乱

现象:老人在系统里把字体调到最大,APP 里按钮文字溢出、列表错位。原因:布局用了固定高度和sp混用。解决:关键容器用wrap_content,字号用sp但给按钮设minHeight,并在Application里限制字体缩放上限。

// 限制字体缩放,避免系统超大字体撑爆布局 @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); Configuration cfg = new Configuration(newConfig); cfg.fontScale = Math.min(newConfig.fontScale, 1.3f); getResources().updateConfiguration(cfg, getResources().getDisplayMetrics()); }

5.2 助老员在弱网下重复接单

现象:助老员点接单没反应,连点几次,结果同一单被接了多次或报错。原因:按钮没做防抖,请求也没做幂等。解决:按钮点击后立即置灰,服务端接单接口用orderId + workerId做唯一约束或乐观锁。

5.3 定位权限在 Android 10 以上拿不到

现象:助老员打卡定位失败。原因:Android 10 起后台定位要单独申请ACCESS_BACKGROUND_LOCATION,且要引导用户选「始终允许」。解决:前台定位用ACCESS_FINE_LOCATION即可,打卡时确保 APP 在前台;确实需要后台定位的,按系统引导分步申请,别一次性全要。

5.4 工单时间用了本地时区导致对账错位

现象:导出对账时服务时间和实际差几小时。原因:客户端传本地时间字符串,服务端按 UTC 存。解决:统一传时间戳或 ISO8601 带时区,服务端存 UTC,展示时再转。

5.5 短信验证码被刷

现象:上线后被刷验证码,费用飙升。原因:发送接口没做频率限制。解决:同一手机号 60 秒一次、每天上限 10 次,加图形验证码兜底,服务端按 IP 也做一层限流。

6. 进阶:把「源码下载」变成能持续迭代的底座

拿到一套源码能跑起来只是起点,真正决定这个项目值不值得投入的,是它能不能接住后面的需求。我一般会先做一件事:把服务项字典和工单状态机抽成配置,而不是写死在代码里。因为社区的服务清单和补贴规则几乎每年都调,写死就意味着每次都要改代码发版。

具体做法是加一张service_config表,把「哪些服务项可补贴」「补贴比例」「是否需要资质」都做成配置,后台可改。这样运营改规则不用找开发。验证方法很简单:改一条配置,看下单时算出的subsidy_amount有没有跟着变,变了说明配置生效。

CREATE TABLE service_config ( id INT PRIMARY KEY AUTO_INCREMENT, item_id INT NOT NULL, community_id BIGINT NOT NULL, subsidy_ratio DECIMAL(5,2) DEFAULT 0.00 COMMENT '补贴比例', max_subsidy DECIMAL(10,2) COMMENT '单次补贴上限', effective_from DATE, effective_to DATE, UNIQUE KEY uk_item_community (item_id, community_id) );

参数上effective_from/to是必须的,补贴政策有生效期,没有这个字段,历史工单重算时会用错比例。max_subsidy防的是高单价服务补贴失控。

另一个进阶点是埋点。养老项目要向上汇报服务量、覆盖率,这些数据靠人工统计不现实。我习惯在工单状态每次流转时写一条order_log,记录时间、操作人、前后状态。后面做报表直接按 log 聚合,比在工单表上加一堆计数字段干净得多。

最后说个我自己的习惯:每接一个养老类项目,先不急着写界面,而是拿真实的服务清单和补贴规则,把工单状态机在纸上走一遍,走不通的地方就是后面要踩的坑。这套源码下载下来能不能用,不看界面多漂亮,看的是状态机和字典表设计得对不对。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询