做养老类APP这件事,我从接到需求到最终跑通完整链路,前后折腾了差不多十个月。最开始我以为这就是个“大字号版的健康管理App”,把字号调到18sp、按钮放大到48dp就算交差。真正跟几十位老人坐在一起看他们用手机之后,才发现这个判断错得有多离谱——有位七十多岁的阿姨,手指悬在屏幕上不敢点,因为“怕按坏了”;有位大爷每次都要把手机横过来看,因为竖屏的字他认不全;还有人把“子女绑定”当成了“收费绑定”,反复问要不要交钱。这些场景不是产品文档里能写出来的,是坐在他们旁边一个一个看出来的。
智能养老APP设计与实现,说白了就是把紧急求助、健康记录、用药提醒、亲情互动这几件老人最需要的事,做成一套老人敢用、子女放心、社区能管的产品。它真正解决的不是“功能有没有”,而是“老人能不能在不求助别人的情况下独立完成一次操作”。这篇东西我打算把自己踩过的坑、做过的取舍、以及那些看起来很土但实测有效的方案,完整摊开讲一遍,适合正在做适老化改造的开发者、准备切入养老服务领域的产品同学,以及家里有老人、想自己动手做个小工具的爱好者参考。
1. 需求拆解:先搞清楚到底在给谁做产品
1.1 三类用户,三套完全不同的心智模型
这个项目最容易犯的错误,是把“使用者”和“付费者”混为一谈。老人是使用者,子女是付费者和监督者,社区或养老机构是管理者。三方的诉求不但不一致,很多时候是互相冲突的。
老人要的是简单、不被打扰、不显得自己没用。我印象特别深的一次可用性测试,我们做了个“健康评分”的环形图,红黄绿三色。结果一位老人看到自己的分数是黄的,当场就不高兴了,说“我又没病,你们凭什么给我打黄牌”。从那天起我们把所有评分类的展示全部改成中性描述,比如“本周血压记录完整度3/7天”,不给判断,只给事实。
子女端要的是异常能被及时告知,但不要一天到晚响。早期版本我们做得太“贴心”,老人每测一次血压就推一条给子女,结果一个子女跟我抱怨说“我妈一天测八次,我手机响八次,后来我就把通知关了”。通知一旦被关,紧急告警也就跟着失效了,这是致命问题。后来改成“正常数据静默汇总到日报,只有越界数据实时推”,关闭率立刻降下来了。
管理端要的是能批量看、能导出、出事有记录。这个诉求非常朴素,但很容易被忽略。我们第一版没做导出,社区的工作人员直接拿Excel手工抄,抄了两周就开始抵触系统。后来加了一键导出CSV,配合时间段筛选,才真正被用起来。
提示:做需求评审的时候,一定要把这三类人分开列场景,不要用一张用户旅程图糊弄过去。同一件事在三方眼里的价值完全不同,混在一起讨论必然扯皮。
1.2 功能清单的砍与留
刚立项的时候,需求清单拉出来有四十多项,从在线问诊到老年大学课程,什么都想放进去。我当时的判断标准很简单:如果一个功能,老人在无人指导的情况下连续三次操作失败,那它就不该出现在首屏。
下面是我们最终定下来的核心功能优先级,这个表后来被反复引用,因为它把“做什么”和“先做什么”说清楚了:
| 功能模块 | 使用频次 | 失败后果 | 优先级 | 决策理由 |
|---|---|---|---|---|
| 紧急呼叫 | 极低 | 极高 | P0 | 一次成功就值回全部投入 |
| 用药提醒 | 每日多次 | 高 | P0 | 高频、易忘、后果直接 |
| 亲情通话 | 每日 | 中 | P0 | 最高频的“打开理由” |
| 健康数据记录 | 每日 | 中 | P1 | 需要长期数据才有价值 |
| 位置守护 | 持续 | 高 | P1 | 走失是家属最焦虑的点 |
| 社区活动报名 | 每周 | 低 | P2 | 拉活跃用,非刚需 |
| 在线问诊 | 每月 | 中 | P2 | 依赖外部服务,先接后做 |
| 老年课程 | 每周 | 极低 | P3 | 锦上添花,最后再做 |
砍掉的典型是“健康知识推送”。产品同学很坚持,说能提升日活。我们做了个小范围灰度,两周数据出来,打开率不到3%,而且打开的人平均停留11秒。老人不缺知识,缺的是有人提醒他今天该吃药了。这个功能最后被砍成一个每周一条的图文卡片,塞在“我的”页面最底下。
还有一个被砍掉的是“语音助手”。技术上不是做不了,是老人不知道怎么跟它说话。测试的时候有位大爷对着手机喊“喂!喂!听得见吗”,喊了五遍,最后把手机放下了。这类功能对年轻用户是效率工具,对老人是认知负担。
1.3 技术选型的三条硬约束
技术选型上我没有太多纠结,因为约束条件非常死:
第一条,必须双端都有,且能长期维护。安卓覆盖了绝大多数老年机和老旧机型,iOS主要是子女端。我们最后选了跨平台方案做业务层,把相机、定位、蓝牙、拨号这类强原生能力单独写插件。这样业务代码只写一遍,原生差异隔离在插件层。代价是包体积大了十几兆,但换来的是两个端的功能永远同步,这个交换我认为很值。
第二条,弱网和离线必须是一等公民。老人家里的网络环境你想象不到有多差:老路由器放在电视柜里、隔两堵墙、2.4G频段被邻居挤满。我们早期做过一个统计,健康数据上传的首次成功率只有七成出头。这个数字直接决定了架构:所有写操作先落本地库,再异步同步,界面永远不等待网络。
第三条,服务端要能私有化部署。社区和机构客户对数据放在哪里非常敏感,这条是商务层面的硬要求,不是技术偏好。所以我们把对象存储、消息队列、数据库全部做成可替换的适配层,一套代码能跑在公有云,也能塞进一台机架服务器里。
注意:跨平台方案在低端安卓机上的列表滚动性能是硬伤。我们的做法是把长列表(比如用药记录、健康历史)全部改成分页加载,每页20条,滚动容器关掉惯性回弹。这个改动之后,千元机上的掉帧问题基本消失。
2. 适老化设计:那些尺寸规范之外的隐性门槛
2.1 视觉与触控的量化指标
网上讲适老化的文章,大多停在“字要大、色要深”。这话没错,但远远不够。下面这组参数是我们经过三轮测试后定下来的,可以直接抄:
| 项目 | 常规App | 本项目取值 | 说明 |
|---|---|---|---|
| 正文字号 | 14sp | 20sp | 低于18sp老人阅读明显吃力 |
| 辅助文字 | 12sp | 16sp | 不低于16sp |
| 最小触控区 | 44×44dp | 64×64dp | 手指粗、有老花、手抖 |
| 主要按钮高度 | 48dp | 72dp | 高度比宽度更影响点击成功率 |
| 行间距 | 1.2倍 | 1.6倍 | 老花眼需要更大行距 |
| 正文对比度 | 4.5:1 | 7:1以上 | 纯黑配纯白,别用灰 |
| 单屏信息组块 | 5-7个 | 不超过4个 | 认知负荷 |
字号这块有个细节值得说:我们没有做“字号设置”这个选项。听起来反直觉,但测试发现老人根本不会去设置里调字号,他们只会觉得“这个App字小”,然后卸载。所以我们直接把默认字号拉到20sp,然后在“我的-无障碍”里放了一个“超大字号”开关,点了之后全局放大到26sp。默认就给够,是适老化设计的第一原则。
颜色的坑更多。我们最早用了一套很漂亮的低饱和配色,灰蓝、浅绿,设计稿看着很高级。投到真机上,一位老人看了半天问我“这个按钮是不是不能点”。因为低饱和在他眼里就是“灰掉了、失效了”。后来全部换成高饱和的主色,按钮用实心填充,禁用态才用浅灰。这个改动很“土”,但点击率直接翻了一倍多。
2.2 流程压缩:把五步操作砍成一步
适老化设计里性价比最高的手段,不是把界面做大,而是减少步骤。每多一个页面,就多一次流失。
拿“给子女打电话”这件事举例。常规App的路径是:打开App → 找到通讯录 → 找到联系人 → 点进详情 → 点呼叫。五步。我们的做法是:打开App,首屏正中间放三个巨大的头像方块,分别对应“儿子”“女儿”“社区热线”,点头像直接拨号,中间不经过任何页面。
实现上有个小技巧,拨号不一定非要走应用内的VoIP,直接调系统拨号盘是最稳的:
// Android 端:一键直呼,跳过应用内页面 fun directCall(context: Context, phone: String) { val intent = Intent(Intent.ACTION_CALL).apply { data = Uri.parse("tel:$phone") // 部分机型需要此 flag 才能从后台拉起 addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } if (ContextCompat.checkSelfPermission( context, Manifest.permission.CALL_PHONE ) == PackageManager.PERMISSION_GRANTED ) { context.startActivity(intent) } else { // 权限被拒时降级到拨号盘,不让流程断掉 context.startActivity(Intent(Intent.ACTION_DIAL, Uri.parse("tel:$phone"))) } }注意那个降级分支。ACTION_CALL需要运行时权限,老人极大概率在首次弹窗时点了“拒绝”,而且不知道怎么改回来。这时候必须降级到ACTION_DIAL,让系统拨号盘带着号码打开,老人只需要按一下绿色按钮。整个链路不能因为权限问题断掉。
2.3 容错设计:让老人“点错了也没事”
老人用智能手机最大的心理障碍是“怕弄坏”。这个恐惧会让他们在关键时刻不敢操作。我们的应对策略是三层:
第一层,所有破坏性操作都要二次确认,且确认按钮的文字必须是具体动作。不要写“确定/取消”,要写“删除这条记录 / 保留”。测试中我们发现,老人对“确定”这个词是懵的,他不知道确定什么。
第二层,误触要有明确反馈。每次点击都要有视觉或震动反馈,哪怕是无效点击。我们在全局加了点击涟漪和一个短震动,效果立竿见影——老人一旦感觉到“手机有反应了”,就敢继续点下一步。
第三层,不做需要“长按”“滑动”“双击”的操作。这三个手势对老人来说几乎不可学习。我们早期做过一个左滑删除,结果一位老人把整条用药记录滑没了,找不回来,气得两天没用。后来所有列表项的操作入口全部改成右侧一个显式的“更多”按钮。
实操心得:测试适老化一定要在老人自己家里做,不能在公司会议室。会议室光线好、安静、有人陪着,老人表现会好很多。回到家里,光线暗、有电视声、没人帮忙,问题才暴露得出来。
3. 核心功能模块的落地实现
3.1 紧急呼叫链路:从按下按钮到有人响应
这是整个项目的生命线,我在这块花了最多时间。核心设计原则是:任何单一环节失效,都必须有其他环节兜底。
完整的链路是这样的:
[老人按下SOS按钮] ├─ 立即本地反馈:全屏红屏 + 语音播报"正在呼叫,请稍候" ├─ 通道A(不依赖网络):直接调系统拨号,呼出第一联系人 ├─ 通道B(依赖网络):MQTT上行 → 服务端 → 推送子女端/社区端 ├─ 30秒无接听 → 自动拨第二联系人 ├─ 60秒无接听 → 群发短信给全部紧急联系人 + 呼叫社区值班 └─ 全程写入本地日志,联网后补传,用于事后追溯通道A之所以放在最前面,是因为它最可靠。手机没网、App崩了、服务端挂了,只要还有信号,电话就能打通。我们甚至做了一个更极端的降级:连续快速按电源键5次触发系统级SOS,这个能力在部分安卓机型上是系统自带的,我们在引导页里教老人开启。
服务端这一侧,关键是幂等和去重。老人手抖可能连按七八次,如果不做去重,子女端会收到七八条告警,人会疯掉。我们的做法是客户端生成一个eventId(时间戳+设备ID+随机数),服务端按eventId去重,30秒内同一设备的重复请求只处理第一条,其余直接返回成功但不重复派发。
状态机也要设计清楚,不能只有“发起”和“结束”两个状态:
| 状态 | 触发条件 | 系统动作 |
|---|---|---|
| CREATED | 按钮按下 | 开始播报,启动通道A/B |
| ACKED | 任一联系人点击“我知道了” | 停止后续升级,记录响应人 |
| ESCALATED | 30秒无ACK | 呼叫下一联系人 |
| RESOLVED | 联系人标记“已处理” | 关闭事件,生成记录 |
| TIMEOUT | 5分钟无响应 | 转人工值守,标记高风险 |
这套状态机上线后,我们复盘了三个月的数据,平均首次响应时间是47秒,其中大部分是子女看到推送立刻回拨。有两次真实的意外跌倒事件,都是靠通道B在20秒内触达子女,这个链路没白做。
3.2 用药提醒与健康档案的数据结构与交互
用药提醒看起来简单,做起来坑很深。老人常见的用药模式是:一天三次、饭前饭后、有些药隔天吃、有些药吃两周停一周。这不是一个简单的cron表达式能表达的。
数据结构上,我用了一张计划表加一张执行表:
-- 用药计划(模板层) CREATE TABLE med_plan ( id BIGINT PRIMARY KEY AUTO_INCREMENT, elder_id BIGINT NOT NULL, drug_name VARCHAR(64) NOT NULL COMMENT '药名,老人看得懂的商品名优先', dosage VARCHAR(32) NOT NULL COMMENT '如:1片 / 半片 / 5ml', timing TINYINT NOT NULL COMMENT '1饭前 2饭后 3睡前 4空腹', freq_type TINYINT NOT NULL COMMENT '1每日 2隔日 3每周固定 4周期服药', freq_value VARCHAR(64) NOT NULL COMMENT '如 daily:3 / weekly:1,3,5 / cycle:14on7off', start_date DATE NOT NULL, end_date DATE NULL COMMENT 'NULL表示长期', created_by BIGINT NOT NULL COMMENT '创建人,区分子女建的和自己建的', status TINYINT DEFAULT 1 ); -- 用药执行记录(事实层) CREATE TABLE med_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, plan_id BIGINT NOT NULL, elder_id BIGINT NOT NULL, plan_time DATETIME NOT NULL COMMENT '应服时间', actual_time DATETIME NULL COMMENT '实服时间,NULL为未确认', action TINYINT NOT NULL COMMENT '1已服 2跳过 3稍后提醒 4超时未确认', remark VARCHAR(128) NULL, UNIQUE KEY uk_plan_time (plan_id, plan_time) );UNIQUE KEY那条很重要。因为客户端和服务端都可能生成记录,如果没有唯一约束,同一次用药会插出两条,统计就全乱了。
交互上的核心是:提醒必须霸道,但不能烦人。到点后弹一个全屏页面,字号巨大,显示“现在该吃 苯磺酸氨氯地平 1片”,下面两个按钮:“已服用”和“稍后提醒(10分钟)”。同时用TTS播报一遍。必须点“已服用”才消失,否则每5分钟响一次,最多三次,之后转成通知栏常驻提醒。
这里有个伦理上的取舍我纠结了很久:要不要给子女推“未服药”告警?最后的方案是,只在连续两次未确认时才推给子女,而且措辞要中性,写“今天的晚间用药暂未收到确认,可以打个电话问问”,不要写“老人忘记吃药了”。前者是提醒,后者是告状,老人知道后会抵触整个系统。
3.3 双端协同:子女端能看什么、不该看什么
子女端的设计比老人端难。因为它天然有“监控”的味道,处理不好会让老人产生被监视的反感,直接导致设备被拔掉电源。
我们定了一条边界:子女端默认只看“状态”,不看“原始数据”。具体来说,子女能看到“今天用药完成3/3”“今天血压已记录”“最近一次活动在2小时前”。想看具体数值,需要老人端授权一次。这个授权是一个显式的开关,在老人端“我的-家人共享”里,默认开启的是“健康摘要”,血压血糖的具体数值需要单独打开。
这个设计当时内部吵得很凶。产品同学认为多此一举,老人不会在意。但实际测试里,有超过一半的老人主动关掉了具体数值的共享。他们的理由很一致:“知道了又要说我。”这不是技术问题,是家庭关系问题,产品必须给这个空间。
技术上,双端数据同步我们用了一个“事件总线+快照”的混合方案。日常状态变化走轻量的消息通道,只推事件类型和时间戳;子女端下拉刷新时才拉完整快照。这样既保证了实时性,又把流量和电量压下来了。实测子女端后台一天的流量消耗在200KB以内。
还有一个细节:子女端必须有“远程协助”能力,但不能有“远程操控”能力。我们做了个屏幕共享+语音指导的功能,子女能看到老人当前的界面,可以圈画指示,但点不了。老人那边始终掌握操作权。这个分寸感很重要,越过这条线,老人就会觉得“这手机不是我的了”。
3.4 电子围栏与位置守护
走失是家属最焦虑的场景,这个功能的实际价值很高。但定位这块的技术坑,比想象中的多得多。
第一个坑是持续定位的耗电。老人手机往往电池老化,一天两充是常态。如果开GPS持续定位,半天就没电,手机会被直接关机,功能反而失效。我们的策略是分场景:
- 在家(连上常用Wi-Fi)时,完全停止定位,只做Wi-Fi指纹匹配,功耗接近零
- 外出时,每5分钟上报一次基站+Wi-Fi混合定位,功耗极低
- 触发围栏越界后,才升级到GPS高频定位,用于追踪轨迹
第二个坑是围栏误报。早期我们用“单点越界即告警”,结果一天推了十几条假告警,原因是基站定位精度差,老人在家附近超市买菜,定位漂到一公里外。后来改成三个条件同时满足才告警:
1. 连续3次上报都在围栏外(过滤单点漂移) 2. 距离围栏边界超过100米(缓冲带) 3. 持续超过10分钟(过滤快速路过)这个组合把误报率从每天十几条降到了每周一到两条,家属的接受度立刻不同了。
第三个坑是隐私。位置数据是最敏感的数据之一,我们的处理原则是:老人端可以随时关闭位置共享,关闭后子女端只能看到“位置共享已关闭”这个状态,看不到任何坐标。轨迹数据服务端只保留7天,超期自动清理。这些不是技术难题,是态度问题,但必须在产品说明里写清楚,老人才会放心打开。
4. 工程实现里的硬骨头
4.1 架构分层与技术栈
整体架构分四层,从下往上依次是:数据层、服务层、通道层、客户端。
数据层用关系型数据库存业务数据,缓存用KV存储扛读压力,位置和轨迹这类时序数据单独放一张宽表,按天分区。服务层是无状态的水平扩展,所有会话状态放在缓存里,这样加机器就能扛量。通道层负责消息推送,我们同时接了三套通道:长连接、系统推送、短信。三者按优先级依次降级,前一个不通就走下一个。
长连接这块用的是标准消息协议,心跳间隔60秒,断线重连用指数退避加随机抖动:
// 重连退避:避免大量设备同时重连打垮服务端 function nextDelay(attempt) { const base = Math.min(30 * Math.pow(2, attempt), 300); // 封顶5分钟 const jitter = Math.random() * base * 0.3; // 30%随机抖动 return (base + jitter) * 1000; }那个随机抖动非常关键。早期没加抖动,某次服务端重启,几万台设备在同一秒重连,直接把连接数打满,雪崩了半小时。加了抖动之后,重连被摊平到几分钟里,服务端压力曲线非常平滑。
4.2 弱网与离线:老人家里的网络比你想象的差
这块我要重点说,因为它决定了整个产品的体验下限。
我们做过一次实地网络质量抽测,在二十户人家里测上下行和丢包。结果很差:有三户的Wi-Fi信号在客厅只有-80dBm,有两户还在用百兆老路由器,上行带宽不足2Mbps。在这种环境下,任何一个“点击后转圈等接口返回”的设计都是灾难。
我们的方案是本地优先(Local First)。所有写操作,先写本地数据库,界面立刻更新并给出成功反馈,然后后台排队同步。同步队列用一张表存,字段包括操作类型、负载、重试次数、下次重试时间。App每次启动、网络状态变化、退到后台前,都会尝试冲刷队列。
冲突处理也得想清楚。比如老人端和子女端同时修改了用药计划,谁赢?我们的规则是“后写入者赢,但保留版本号”,冲突时以服务端最后收到的时间戳为准。为了避免混乱,用药计划的编辑权限做了限制:只有创建者能改,另一方只能查看和提建议。这条规则砍掉了一堆麻烦。
图片上传是另一个大头。老人的健康报告拍照上传,一张原图动辄四五兆,弱网下根本传不上去。我们在客户端做了压缩,长边压到1280px,质量70%,一张图降到200KB左右,肉眼看报告文字依然清晰。上传支持断点续传,分片大小256KB,失败自动重试三次。
4.3 后台保活与耗电的平衡
这是安卓端最头疼的问题,没有之一。老人不会用最近任务列表清理App,但系统会自己杀。一旦被杀,用药提醒不响、SOS收不到推送,整个产品就废了。
我们做了四件事,按有效性排序:
第一,引导用户加白名单。这是最有效的手段。我们做了一个引导页,根据机型自动匹配不同的设置路径截图,一步步教老人(或者子女)把App加入后台运行白名单、关闭电池优化、允许自启动。这个页面的完成率大概六成,但完成的那六成用户,消息到达率从七成提升到了98%以上。
第二,前台服务+常驻通知。用一个低优先级的常驻通知维持进程,通知内容写“用药提醒服务运行中”,点击进入App。注意通知不能写得太技术化,老人看到“服务”两个字会慌,我们改成了“今天还有1次用药提醒”。
第三,所有提醒走系统级闹钟。用药提醒不依赖App进程存活,而是用系统闹钟服务注册精确闹钟,到点由系统拉起一个广播接收器。这样即使App被杀,闹钟依然会响。
// 用精确闹钟保证到点必响,不依赖进程存活 val alarmManager = context.getSystemService(AlarmManager::class.java) val intent = Intent(context, MedReminderReceiver::class.java).apply { putExtra("planId", planId) putExtra("planTime", planTime) } val pending = PendingIntent.getBroadcast( context, planId.toInt(), intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, triggerAtMillis, pending )setExactAndAllowWhileIdle这个方法是关键,它能在低电耗模式下依然精确触发。代价是系统对这类闹钟有频率限制,所以只用在用药提醒和SOS这两个场景,不能滥用。
第四,厂商推送通道兜底。各主流安卓厂商都有自己的系统级推送通道,App被杀也能收到。我们把这些通道全部接进来,按机型自动路由。这块的接入成本不低,各家SDK的接口、回调、错误码都不一样,但为了消息到达率,这个钱必须花。
踩过的坑:早期我们只接了长连接,测试机是开发机,一直挂着充电、白名单全开,测出来到达率95%。上线后真实用户到达率只有68%。这个教训是——保活测试必须在真实老人的手机上做,用他们的日常使用习惯,不要用开发机自欺欺人。
5. 上线后遇到的问题与排查实录
5.1 推送不达的三类原因
上线第一个月,我们收到了大量“没收到提醒”的反馈。一条条查下来,原因可以归成三类,排查顺序也应该按这个来:
第一类,设备侧限制。后台被清、电池优化开着、自启动被禁。这类占了大头,大概七成。排查方法是在App里加一个“消息自检”页面,让用户点一下,系统会依次检查通知权限、电池优化状态、自启动状态、厂商通道注册状态,四个绿灯才算健康。这个页面做完之后,客服工单量降了一半。
第二类,通道侧问题。长连接掉线未重连、厂商通道限流、推送证书过期。有次证书过期,整整两天所有iOS推送全挂,而我们没有监控告警,是用户投诉才知道的。后来加了推送成功率的小时级监控,低于90%就自动告警到值班群。
第三类,服务端逻辑。比如用户自己关了某类通知,但设置页的开关没同步到服务端,导致还在推。这类问题最隐蔽,需要用设备ID做全链路日志串联,从“生成消息”到“下发”到“通道回执”三步都能查到。
5.2 定位漂移与围栏误报
前面提过围栏的三条件过滤,这里补充几个具体的排查手段。
老人反馈“我在家,怎么说我出去了”,第一件事是看上报点的精度值。如果accuracy大于500米,基本可以判定是基站定位,不可信。我们的规则是:精度差于300米的点,不参与围栏判断,只做轨迹补点。
第二个手段是看连续点。我们在服务端保留了每次上报的原始点,排查时把最近两小时的点和地图路网对照,如果点在地图上跳来跳去、跨河跨高架,那肯定是漂移。这时候调大缓冲带就能解决。
第三个手段是看Wi-Fi指纹。老人的位置其实很有规律,家、菜市场、社区活动中心,三个点占95%的时间。我们给常用地点做了指纹库,一旦匹配上Wi-Fi指纹,直接判定“在原地点”,忽略GPS。这一招把室内误报几乎清零了。
5.3 常见问题速查表
把这两年遇到的高频问题整理成一张表,遇到问题可以按图索骥:
| 现象 | 可能原因 | 快速验证 | 处理方式 |
|---|---|---|---|
| 用药提醒不响 | App被杀且闹钟未注册 | 看系统闹钟列表有无对应项 | 重装后重新授权,检查白名单 |
| 提醒响两次 | 闹钟注册了两次未去重 | 查广播接收器日志 | 按planId去重,注册前先cancel |
| 收不到推送 | 通道注册失败/证书过期 | 看推送自检页四个灯 | 重新注册通道/更新证书 |
| 围栏频繁误报 | 定位精度差、缓冲带太小 | 查上报点accuracy值 | 提高精度门槛,缓冲带扩到150米 |
| 求助电话打不出 | 拨号权限被拒 | 检查权限状态 | 降级到拨号盘,引导重新授权 |
| 数据不同步 | 本地队列积压 | 查sync_queue表重试次数 | 手动冲刷,检查网络状态 |
| 图片上传卡死 | 弱网下超时未重试 | 查上传分片日志 | 压缩图片,缩小分片到128KB |
| 老人说字太小 | 系统字体设置被改 | 检查系统fontScale | 引导关闭系统字体缩放,用App内设置 |
| 电量掉得快 | 定位频率过高 | 看耗电排行 | 降级为Wi-Fi指纹定位 |
| 子女端看不到数据 | 共享开关被老人关闭 | 查授权状态 | 提示子女通过通话沟通,不要强推 |
实操心得:排查这一类问题,最有效的工具是“设备诊断页”。把权限状态、网络状态、通道状态、队列积压、最近十条错误日志全部集中展示在一个页面,让用户截图发过来,比电话里问半天强十倍。我们做这个页面花了三天,省下的沟通成本无法估量。
6. 迭代半年后的一些个人体会
6.1 数据反馈推翻的两个假设
做了这么久,最让我意外的不是技术难题,而是两个被数据推翻的假设。
假设一:老人不愿意学新东西。我们原本以为功能要越少越好。但数据显示,使用超过三个月的老人,有41%会主动去点“健康报告”看趋势,还有人在“我的”页面里反复看自己的用药完成率。他们不是不愿意学,是学得慢。只要给足够的时间、明确的反馈、不怕出错的环境,他们的学习曲线是可以爬上去的。这个认知改变之后,我们把新功能的引导做成了“可重播”的,老人可以反复看。
假设二:子女最关心健康数据。实际上子女最关心的是“有没有异常”和“能不能联系上”。血压具体数值是多少,很多子女根本看不懂,也不太看。真正被高频使用的是“一键拨打”和“异常提醒”。这直接影响了我们的首页设计,把健康数据往后挪,把通话和状态放到了最前面。
6.2 后续还能往哪长
这个项目做到现在,核心链路已经稳了,但我认为还有几块可以继续挖。
一是社区侧的批量管理。目前的机构端还是比较粗糙,只能看名单和导出数据,缺少健康趋势的批量筛查。如果能把“连续三天未记录血压”的老人自动标红,社区工作人员的工作效率会有质变。
二是语音交互的真正落地。现在的语音只做了TTS播报,没做识别。如果老人能说一句“帮我打电话给女儿”,比点三次屏幕要自然得多。难点不在技术,在于老人的口音和表达方式差异太大,需要针对特定地域做语料优化。
三是和硬件设备的联动。手环、血压计、跌倒检测器这些设备,数据打通之后价值会大很多。目前我们只接了蓝牙血压计,体验还比较割裂,测量完还要手动确认上传。理想状态是设备一测完,数据自动进档案,异常自动告警,老人全程零操作。
最后再分享一个我认为最值钱的体会:做这类产品,最重要的能力不是写代码,是坐下来陪老人用手机。我在项目初期做了三十多场入户观察,每一场至少一小时,什么都不做,就看他们怎么用、在哪卡住、说了什么。那些“怕按坏了”“要不要交钱”“我儿子会不会看到”的话,是任何用户调研问卷都问不出来的。你把这些话记住了,产品就不会走偏。