1. 项目整体设计与思路拆解
智能养老 APP 这个方向,我在过去两年里反复做过三轮原型,也参与过一次完整的产品化落地。55 岁以上的用户群体和 20 多岁的用户群体,在操作习惯上的差异远比想象中大:他们不是“学不会”,而是“不敢点”。任何一次误触带来的扣费、弹窗、跳转,都会让一个老人彻底放弃这个 APP。所以做智能养老 APP,第一件事不是把功能堆上去,而是想清楚哪些功能必须砍掉、哪些路径必须压缩到三步以内、哪些风险必须在设计阶段就规避。这篇文章我会把适老化设计标准、健康数据采集与异常预警、一键呼救链路、家属端联动、后端数据模型和一堆踩过的坑,按实际项目推进的顺序完整讲一遍,适合正在做类似项目的开发者、产品设计者,以及想把家里老人照护流程数字化的人参考。
1.1 先搞清楚:老人究竟卡在哪一步
很多团队一开始的热情都放在“功能丰富”上,结果做出来的东西自己父母用一遍就放弃了。我做过一次很朴素的用户访谈,让 12 位 62 到 81 岁的老人各自独立完成三个任务:给子女发一条语音、确认一次吃药、模拟一次求救。结果只有 4 人能在不看提示的情况下完成第一个任务,剩下的人卡在三个地方——找不到麦克风按钮、不敢长按、按了之后不知道该说什么。
这三个卡点对应三个设计原则。第一,入口必须可见且唯一,同一个动作不要在三个页面各放一个入口,老人的记忆方式是“位置记忆”而不是“逻辑记忆”,位置一变就找不到。第二,不要用长按、双击、滑动这些隐藏手势,长按在老人的认知里等同于“按住不放会不会扣钱”,他们的第一反应是松手。第三,每一步都要有明确的听觉和视觉反馈,按下之后必须有声音、有文字、有震动,否则老人会认为是手机坏了,然后反复点。
从这三个原则反推,功能优先级就出来了:能救命的功能排第一,能减少子女焦虑的排第二,能带来商业价值的排最后。我在项目里做的一张优先级表,实测下来非常好用:
| 优先级 | 功能 | 判断依据 | 交互成本上限 |
|---|---|---|---|
| P0 | 一键呼救、跌倒检测 | 直接关联生命安全 | 0 步(常驻) |
| P0 | 用药提醒与确认 | 高频、直接影响健康 | 1 步 |
| P1 | 语音/视频联系子女 | 情感刚需、降低孤独感 | 1 步 |
| P1 | 健康数据记录与异常提醒 | 慢病管理的核心抓手 | 2 步 |
| P2 | 家属端数据看板 | 付费决策者关心 | 家属端可放宽 |
| P3 | 社区活动、内容资讯 | 提升留存,但不紧急 | 3 步以内 |
注意:这张表里最容易被做错的是“跌倒检测”。它技术难度高、误报率高,如果一开始就把它做成核心卖点,很可能上线三个月就被大量误报投诉淹没。我的建议是第一期只做“手动呼救 + 长时间无操作提醒”,把跌倒检测放到第二期用更谨慎的策略上。
1.2 双端边界:老人端做减法,家属端做加法
智能养老 APP 最容易犯的结构性错误,是把老人和子女放在同一个界面里,用角色权限去隐藏功能。这样做的问题在于,老人端一旦继承了整套信息架构,就必须承担所有的认知负担。我后来的做法是两个 App 内核、两套信息架构、共用一套后端,可以从一个安装包分发,但首次启动时必须二选一,且切换需要家属端确认。
老人端的设计哲学是“一个屏幕解决一件事”,首页只放四到六个巨大的方块,每个方块就是一个动作,不设底部导航栏。家属端则相反,需要密集信息、多维图表、历史趋势,因为家属是决策者,愿意花时间研究。两者的差异可以对照下面这张表:
| 维度 | 老人端 | 家属端 |
|---|---|---|
| 首屏信息量 | 4~6 个大卡片 | 6~8 个数据模块滚动 |
| 导航方式 | 单层页面栈,不设 Tab | 4 个 Tab + 二级页 |
| 字号基准 | 20sp 起 | 15sp 起 |
| 输入方式 | 语音优先、大键盘兜底 | 常规输入 |
| 数据展示 | 只说“正常/异常” | 给趋势图和具体数值 |
| 出错处理 | 自动重试 + 人工兜底 | 明确报错 + 自助排查 |
这套拆分的直接收益是老人端的代码量比家属端少 40% 左右,页面数量只有家属端的三分之一,后续做适老化迭代时改动面也更小。代价是两端的数据口径必须严格对齐,尤其是“异常”的定义——老人端说“今天血压有点高”,家属端的数据必须能解释清楚是哪一次、高了多少、是否触发了提醒,否则家属会失去信任。
1.3 技术选型:为什么最后没走纯跨平台
技术选型上我经历过一次反复。最初为了省人力选了跨平台方案,一套代码两端跑,开发效率确实高。但真正上线前做机型覆盖测试时,问题集中爆发:某些千元安卓机在跨平台渲染层上的字体渲染明显发虚,20sp 的字看起来像 16sp;后台保活能力弱,前台服务容易被系统回收,呼救推送延迟超过 40 秒;还有部分机型在调用系统 TTS 时出现首字吞音。
最终方案是混合策略:
- 老人端的关键链路(呼救、提醒、语音)用原生实现,保证最底层的稳定性;
- 老人端的次要页面(资讯、设置、帮助)用跨平台方案,节省人力;
- 家属端全部用跨平台方案,因为家属端对性能敏感度低,迭代速度更重要;
- 后端统一用一套 HTTP + WebSocket 服务,推送走系统级通道加短信兜底。
这个取舍的逻辑是:把稳定性预算花在“出事的时候”。日常浏览卡 200 毫秒没人注意,呼救延迟 5 秒可能就是另一回事。下表是我当时做选型对比时的记录,供参考:
| 方案 | 开发成本 | 呼救链路可靠性 | 适老化渲染一致性 | 结论 |
|---|---|---|---|---|
| 纯跨平台 | 低 | 中低(保活弱) | 中(字体渲染差异) | 仅用于家属端 |
| 纯原生双端 | 高 | 高 | 高 | 关键链路采用 |
| 混合(推荐) | 中 | 高 | 高 | 最终选择 |
2. 适老化设计的量化标准
适老化这个词被说烂了,但大部分团队落地时靠的是“感觉大一点”。我在项目里把它拆成了可以写进设计规范、可以让测试同学逐条验证的硬指标。这一章讲的全是能量化、能验收的东西,你完全可以抄去做自己项目的适老化 checklist。
2.1 字号、行高、对比度、热区的具体数值
先说字号。Android 的 sp 和 iOS 的 pt 在不同设备上的实际物理大小不同,我一般以“在 6.5 英寸屏幕上,老人不戴老花镜、距离 35 厘米能一次读清”为验收标准,反推出来的基准值是:正文不小于 20sp,标题 26 到 32sp,按钮文字 22sp 起,辅助说明文字最低 16sp 且不能再小。行高统一给到字号的 1.5 倍,段间距不小于 16dp,因为老人阅读时视线容易串行。
对比度这块,WCAG 的 AA 标准是 4.5:1,但实测下来这个标准对老人偏宽松。我在项目里把正文对比度提到了 7:1,做法是深灰字 + 纯白底(#1A1A1A on #FFFFFF),而不是纯黑,纯黑在 OLED 屏上边缘会发虚。特别要避开的两个坑:浅灰占位文字(#999999)在老人眼里几乎等于不存在;彩色背景上的白字,饱和度稍高就会糊。
触控热区是最容易被忽略的。设计稿上画一个 32dp 的图标,实际可点区域必须扩到 56dp 以上,因为老人的手指定位精度比年轻人低不少,而且很多人有轻微手抖。我给团队的硬性规定是:任何可点元素的响应区域不小于 56×56dp,相邻可点元素的间距不小于 16dp,否则一定会出现“想点 A 结果点到 B”的投诉。
还有一个细节是文本缩放。系统字体放大功能必须尊重,但不能无限放大导致布局崩坏。Flutter 里的处理方式是这样:
MaterialApp( builder: (context, child) { final media = MediaQuery.of(context); // 允许放大,但锁在 1.0 ~ 1.3 之间,避免布局击穿 final scale = media.textScaler.scale(1.0).clamp(1.0, 1.3); return MediaQuery( data: media.copyWith(textScaler: TextScaler.linear(scale)), child: child!, ); }, );注意:不要用
MediaQuery.of(context).textScaleFactor这种老 API,新版本已经标记弃用,而且部分厂商 ROM 会在这里返回异常值,clamp 一下能挡掉不少莫名其妙的布局错乱。
2.2 三步到达:把核心操作路径压到极限
“三步之内完成核心操作”这句话听起来像口号,但落到具体页面就有明确的算法:从解锁屏幕开始数,点击次数不超过 3 次。我拿这个标准重新审了所有页面,砍掉了两件事——首次启动的多页引导,和每次进入都要弹的隐私授权二次确认。
首次启动引导改成了单页大字 + 语音播报,只有一页,讲清楚三件事:这个 App 能干什么、有事按哪个键、出问题找谁。原来设计的 5 页滑动引导被彻底删掉,因为实测中 9 位老人里有 7 位在第一页就找不到“下一步”。
呼救功能做到了0 步。做法是在老人端用系统悬浮窗常驻一个半透明的圆形按钮,直径 88dp,位置固定右下,可拖动但会自动吸附。老人不需要打开 App,在任何界面都能按。这个方案需要申请悬浮窗权限,成本是要多做一套权限引导,但收益极大——实测呼救的平均触发时间从 12 秒降到了 3 秒以内。
吃药确认做到了1 步。提醒到点后,锁屏上直接出现一个大按钮“我已吃了”,点击即完成记录,不需要解锁进 App。如果 15 分钟内没有点击,系统会升级为响铃 + 语音播报,30 分钟未响应则推送给家属。这条链路的关键是不要要求老人进 App 才能确认,因为进 App 本身就是一道门槛。
2.3 语音与无障碍:让不会打字的人也能用
语音是这个项目里投入产出比最高的一块。我把语音拆成两个方向:听得懂和说得出。
听得懂指的是 TTS 播报。这里有个很多人不知道的细节:语速。系统默认语速对老人来说偏快,我在项目里把 TTS 语速降到默认值的 0.8 倍,并在设置里给了“慢/标准/快”三档,默认慢。另外,播报内容不能用书面语,比如“血压数据采集已完成”要改成“血压记好了,是 135 和 85”。数字的读法也要单独处理,135 要读成“一百三十五”而不是“一三五”,否则老人会听成三个数字。
说得出指的是语音输入。老人端所有文本输入框旁边都挂一个麦克风按钮,按下即录、松手即转文字,转完直接把文字填进输入框让老人确认。这里踩过一个坑:语音转文字在方言重的老人身上识别率极低。我的兜底方案是常用语快捷短语——把“我没事”“我不舒服”“帮我买点菜”“今天不吃饭了”这类高频短句做成大按钮,点一下就发出去,比语音识别可靠得多。
无障碍还有两个必做项。一是TalkBack / VoiceOver 的标签必须写全,不能出现“按钮”“图片”这种无意义朗读,所有图标按钮都要有明确的语义标签。二是震动反馈,老人对震动的感知比对视觉反馈更敏感,每次关键操作都给一次短震,能显著降低“我到底点上没点上”的焦虑。
3. 核心功能模块的实现细节
这一章讲具体怎么实现。我会给出数据判定逻辑、呼救链路的伪代码,以及用药提醒的完整设计。这些都不是理论推导,是项目里跑过真实数据之后调出来的参数,你可以直接拿去改。
3.1 健康数据采集与异常预警:先解决误报
健康数据这块最大的坑不是采不到,而是采到了之后怎么判断“异常”。我第一版用的是单点阈值:心率超过 110 就报警。上线两周,家属端被大量误报淹没——老人刚爬完楼、刚喝完热水、刚跟人吵完架,心率都会短暂上到 110 以上,但这不是异常。
第二版改成了滑动窗口 + 持续性判定:只有连续 3 个采样点(每分钟一个点)都超出阈值,才认为是值得提醒的异常。这一改,误报率降了大概 80%。判定逻辑大致如下:
def judge_hr(samples, low=50, high=110, hold=3): """ samples: [(ts, hr), ...] 按时间升序,约 1 分钟一个采样点 hold: 连续超限多少次才算异常 return: 异常事件列表 """ events, streak, peak = [], 0, None for ts, hr in samples: if hr < low or hr > high: streak += 1 peak = hr if peak is None else max(peak, hr, key=lambda x: abs(x - 80)) else: streak, peak = 0, None if streak >= hold: events.append({"ts": ts, "peak": peak, "type": "hr_persist"}) streak, peak = 0, None # 触发后清零,避免重复上报 return events阈值本身也要分层。静息心率和活动心率不能用同一个标准,所以我在采集端加了一个很粗糙但有效的“活动状态”判断:读加速度传感器的方差,方差大就标记为活动状态,此时阈值上浮 20。血压则更保守,收缩压超过 160 或低于 90、并且连续两次测量都异常,才推送给家属,同时在文案里明确写“这是记录值,不作为诊断依据”。
这里必须强调一点:这类健康数据只能做提醒,不能做诊断。我在老人端和家属端的每一次异常提醒下面,都固定加了一句“本提醒仅用于日常记录,身体不适请及时就医”。这句话不是免责套话,它直接影响用户对产品的预期——如果老人把它当成血压仪,出了问题就是信任崩塌。
3.2 一键呼救:不依赖网络的那三秒
呼救是整个 APP 里唯一一条不允许失败的链路。我按“网络正常、网络弱、完全断网”三种情况分别做了设计,核心原则是本地优先、逐级兜底。
第一步永远是本地响应。按下按钮后,不管有没有网络,先做三件事:扬声器播报“正在为您联系家人”、震动 3 秒、屏幕切到红色高对比界面。这三步的意义在于给老人即时反馈,让他知道“按对了,有人在处理”,否则他会反复按,反而让事件队列混乱。
第二步才是联网。我把整个链路写成了下面这段伪代码,逻辑比较直观:
async function triggerSOS(payload) { // 1. 本地先响,不依赖网络 playLocalSiren(); vibrate(3000); showEmergencyScreen(); // 2. 先落本地队列,断网也不丢事件 await localQueue.push({ type: 'sos', ...payload, ts: Date.now() }); // 3. 主通道:HTTP,3 秒超时 const ok = await withTimeout(postJSON('/sos', payload), 3000).catch(() => false); // 4. 主通道失败,走短信兜底(不依赖数据网络) if (!ok) { await sendFallbackSms(payload); // 号码在本地预置,不查接口 } // 5. 15 秒内家属未确认,升级到第二联系人 scheduleEscalation(payload.incidentId, 15000, ['contact_2', 'community']); }这套设计里有三个细节值得单独说。第一,联系人号码必须本地预置。不能等呼救时再去服务端拉联系人列表,因为断网时拉不到。预置的代价是换号码后要同步,所以我在每次联网启动时都做一次静默刷新。第二,升级机制的时间要短。最初设的是 60 秒,实测太长了,改成 15 秒。老人摔倒后能不能站起来是未知的,15 秒无人响应就该找下一个人。第三,事件必须落本地队列,很多老人家里信号差,按下呼救时刚好没网,等走到阳台有网了,事件已经丢了。
注意:短信兜底要单独申请通道,并且要处理“发送失败”的情况。我遇到过一次,老人手机欠费,短信也发不出去,最后是本地响铃被邻居听到才解决的。所以后来我加了一条:呼救触发后如果 60 秒内所有通道都失败,本地响铃持续 3 分钟不停止。
3.3 用药提醒与依从性统计
用药提醒看着简单,实际上是老人端使用频率最高的功能,也是最容易做砸的。踩过的坑主要在三个方面:时间表达、多次服药、以及“吃了没吃”的确认。
时间表达上,绝对不要用“每日三次”这种说法,老人不知道是哪三次。我在配置界面强制要求家属输入具体时间点,比如“08:00、12:30、19:00”,到点推送时文案写成“现在是早上 8 点,该吃降压药了,1 片”。把时间、药名、剂量说全,老人才能对上号。
数据结构上,我用一张计划表加一张记录表,计划表存规则,记录表存每一次的实际动作:
CREATE TABLE med_plan ( plan_id BIGINT PRIMARY KEY, elder_id BIGINT NOT NULL, drug_name VARCHAR(64) NOT NULL, dosage VARCHAR(32) NOT NULL, -- "1片" / "半片" / "5ml" times VARCHAR(128) NOT NULL, -- "08:00,12:30,19:00" start_date DATE NOT NULL, end_date DATE, remind_ahead_min SMALLINT DEFAULT 0, -- 提前几分钟提醒 status TINYINT DEFAULT 1 -- 1启用 0停用 ); CREATE TABLE med_record ( record_id BIGINT PRIMARY KEY, plan_id BIGINT NOT NULL, plan_time DATETIME NOT NULL, action TINYINT NOT NULL, -- 1已服用 2跳过 3超时未确认 acted_at DATETIME, source TINYINT, -- 1锁屏确认 2App内确认 3家属代填 UNIQUE KEY uk_plan_time (plan_id, plan_time) );UNIQUE KEY这一行很关键。因为提醒可能同时从锁屏通知、App 内弹窗、语音播报三个入口触发,如果没有唯一约束,一次服药会写出三条记录,依从性统计直接失真。
依从性统计我按周算,公式是“按时确认次数 / 应服次数”,但展示给家属的时候不直接给百分比,而是给一个颜色加一句话。低于 60% 显示红色“本周漏服较多,建议关注”,60% 到 85% 显示黄色“基本按时”,85% 以上显示绿色“按时服药做得好”。原因是家属看到 73.5% 这种数字没有直观感受,看到红色才会有行动。
3.4 家属端:把数据翻译成可行动的结论
家属端最容易做成“数据仪表盘”,堆一堆折线图,看起来很专业,但家属打开两次就不看了。我在第二版里做了一次大改,核心思路是先给结论,再给数据。
具体做法是首屏只放三块内容:一块是“今日状态”,用一句话概括,比如“今天血压偏高 1 次,已提醒,目前正常”;一块是“待办”,比如“今晚 19:00 的药还没确认”;一块是“最近异常”,按时间倒序列出最近 7 天的异常事件。折线图全部下沉到二级页,点进去才看。
还有一个功能值得单拎出来讲:远程协助。老人遇到问题时,家属端可以发起“远程看屏”,在老人授权后看到老人的实时界面,并用一个手指光标指引“点这里”。这个功能的技术实现是基于屏幕采集加坐标下发,成本不低,但对降低“电话里说不清”的沟通成本效果极好。我在项目里做过一次对比,有远程协助的组,家属平均通话时长从 6 分钟降到 2 分钟,老人问题解决率从 55% 提到 88%。
4. 后端与数据链路的设计取舍
后端这块,我想讲的不是怎么搭微服务,而是三个直接影响用户体验的具体问题:数据模型怎么设计才够用、推送怎么保证到达、隐私边界划到哪里。
4.1 数据模型:先分家,再关联
智能养老类应用的数据模型有一个特点:老人和家属是多对多关系。一个老人可能有多个子女,一个子女可能要照顾多位老人(父母、岳父母)。所以从第一天起就要按“家庭”来建模,而不是简单地给老人挂一个family_contact字段。
我的做法是三张核心表:老人档案、家属账号、关系表。老人档案里的敏感字段(姓名、身份证号、详细住址)全部加密存储,业务侧只暴露elder_id和脱敏后的昵称。关系表里带一个role字段区分“主联系人”“次联系人”“只读成员”,权限判定全部走这张表。
CREATE TABLE family_relation ( id BIGINT PRIMARY KEY, elder_id BIGINT NOT NULL, user_id BIGINT NOT NULL, role TINYINT NOT NULL, -- 1主联系人 2次联系人 3只读 can_call TINYINT DEFAULT 1, -- 是否可发起紧急呼叫 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_elder_user (elder_id, user_id) );这样设计的好处是,后面要加“社区护工”“签约医生”这类角色时,不需要改表结构,只加一个 role 值就行。坏处是权限判定变复杂了,每个接口都要查一次关系表。我的处理是在网关层做一次缓存,把user_id -> [elder_id, role]的映射缓存 5 分钟,命中率能到 98% 以上。
4.2 推送到达率:别指望一条通道
推送是这类应用的生命线,但也是最不可控的一环。国内安卓生态的碎片化决定了,你不可能靠一条通道覆盖所有机型。我在项目里做的是三通道冗余:系统级推送通道作为主通道,长连接作为次通道,短信作为最终兜底。
三通道的调度策略是这样的:事件产生后,先走系统级推送,同时通过 WebSocket 长连接下发一份;如果 30 秒内客户端没有回执,判定为未到达,触发短信兜底。这里的“回执”很关键,不能以“服务端发送成功”为准,必须以“客户端已确认收到”为准,因为推送服务返回成功但终端没收到的情况非常普遍。
实测数据供参考:单用系统级推送,冷启动场景的到达率大概在 70% 到 85% 之间波动,机型差异极大;加上长连接后,前台和后台存活场景能到 95% 以上;再加短信兜底,关键告警的到达率能稳定在 99% 以上。这套组合的成本主要是短信费用,所以我把短信兜底限定在“呼救”“严重异常”两类事件上,日常提醒不走短信。
| 通道 | 覆盖场景 | 到达率(实测区间) | 成本 | 使用范围 |
|---|---|---|---|---|
| 系统级推送 | 冷启动、离线 | 70%~85% | 低 | 全部提醒 |
| 长连接 | App 存活 | 95%+ | 中 | 全部提醒 |
| 短信息 | 全部 | 99%+ | 高 | 仅呼救与严重异常 |
4.3 隐私与权限:最小化采集,显式授权
健康类数据的敏感性不用多说。我在项目里定了几条硬规则。第一,能不采的就不采。比如精确住址,只在设置紧急联系人时采集一次,并且加密存储,日常业务不读取。第二,定位只在呼救时开启。日常不后台持续定位,避免耗电和隐私争议;呼救触发时才申请一次高精度定位,并在界面上明确告诉老人“正在获取您的位置”。第三,所有授权都要有明确的解释页,用大白话写清楚“为什么需要这个权限”“不给会怎样”,不能只弹系统对话框。
还有一个容易被忽略的点:家属看数据的边界。不是所有家属都能看所有数据。我在关系表里给role为“只读”的成员关掉了健康明细,只让他们看到“今日状态”的汇总;用药记录、就诊记录这类信息只有主联系人和次联系人可见。这个设计的出发点是,很多家庭里老人并不希望所有子女都知道自己的具体病情。
5. 常见问题与排查实录
这一章是项目里真实踩过的坑,按问题类型整理,附排查思路。如果你正在做类似的东西,这部分大概能帮你省掉两三个月的试错。
5.1 机型适配:那些在测试机上永远不会出现的问题
最典型的一类问题是后台被杀。某厂商的千元机在内存紧张时会 aggressively 清理后台进程,导致提醒不响、长连接断开。排查方法是抓系统日志看进程被杀的原因,通常能看到 lowmemorykiller 的记录。解决方案分三层:申请前台服务并常驻通知(用户可感知,但要设计好文案,不能让老人觉得是广告);引导用户在系统设置里把 App 加入电池优化白名单,这一步必须做成带截图的引导页,纯文字说明没人看得懂;最后才是接受现实,用推送兜底。
第二类问题是字体渲染异常。部分机型在系统字体调到最大时,会在跨平台渲染层出现文字截断。排查方法是写一个自动遍历所有页面并切换字体倍率的脚本,跑一遍截图对比。解决方式是给所有文本容器加maxLines和溢出策略,同时前面提到的 clamp 也能挡掉一部分。
第三类问题是TTS 首字吞音。这个几乎无解,我的处理是在播报文本前面加一个极短的静音占位(比如一个逗号或空格),让引擎先完成初始化。实测下来能解决八成左右。
5.2 推送延迟与提醒不准时
提醒不准时的原因通常有三种。一是系统省电策略把定时任务推迟了,尤其是夜间长时间待机后。排查方式是对比“设定时间”和“实际触发时间”的日志差值,如果普遍偏移几分钟,基本可以确认。解决方案是不要依赖单一的本地定时器,而是“本地定时器 + 服务端推送”双保险,本地定时器负责准时,服务端推送负责兜底。二是时区处理错误,这个在跨时区场景或者用户手动改时区时会暴露,处理方式是所有时间存 UTC,展示时再转本地。三是重复触发,前面提到的唯一约束就是为了解决这个。
5.3 定位漂移:别信单次定位结果
呼救场景下的定位漂移是最让人头疼的。老人在室内,GPS 信号弱,单次定位可能偏出去几百米。我的做法是多次采样 + 精度过滤:连续采 5 次,丢弃精度半径大于 100 米的点,剩下的取中位数。如果全部被丢弃,就退回基站定位,并在给家属的消息里明确写“位置精度较低,仅供参考”。这个“仅供参考”的标注很重要,它能让家属知道该不该直接按这个位置去找人。
| 问题现象 | 优先排查方向 | 常用手段 | 处理建议 |
|---|---|---|---|
| 提醒不响 | 后台保活 / 电池优化 | 系统日志、进程存活检查 | 前台服务 + 白名单引导 + 推送兜底 |
| 推送延迟数分钟 | 省电策略 / 通道选择 | 对比设定与实际触发时间 | 本地定时器 + 服务端推送双保险 |
| 定位偏移大 | 信号环境 / 单次采样 | 连续采样并看精度半径 | 多次采样取中位数,标注精度 |
| 字体截断 | 系统字体倍率 | 脚本遍历倍率截图对比 | clamp 倍率 + 溢出策略 |
| 语音识别率低 | 方言 / 环境噪音 | 分场景统计识别成功率 | 常用语快捷短语兜底 |
| 重复记录 | 多入口触发 | 查数据库唯一键冲突 | 加唯一约束 + 幂等写入 |
5.4 几条不上文档的经验
第一条,不要把“帮助”做成文档。我最初写了一篇三千字的图文说明,实测打开率不到 3%。后来改成“每个页面右上角一个问号,点开是一个 30 秒的演示动画”,使用率上去了,客服量也下来了。
第二条,老人不会主动反馈问题。他们遇到卡顿、闪退,第一反应是“手机老了”或者“我按错了”,不会来投诉。所以必须做主动埋点:页面停留超过 60 秒无操作、连续点击同一按钮 3 次以上、页面返回率异常高,这些都要上报。我在项目里靠这套埋点发现了三个严重的交互缺陷,都是老人从来没提过的。
第三条,客服通道要比正常产品更“重”。老人端的客服不是在线工单,而是直接一个电话按钮,点下去就拨号,接通后客服能看到老人的设备信息和最近的异常记录。这个设计让单次问题的平均处理时间从 20 分钟降到了 4 分钟。
6. 测试与灰度:上线前必须做的一件事
功能做完不等于能用。这个项目里我花在测试上的时间,大概占了总工期的三成,其中一半花在找真人测试上。
6.1 找真人测,别找同事
这是我最想强调的一条。同事再努力模拟,也无法还原真实的认知模式。我在项目里组织了四轮测试,每轮 8 到 12 位 60 岁以上的老人,做法很土但有效:把手机给他们,只给一个任务描述(比如“帮你儿子发条语音”),然后全程不说话、不提示,只记录他们在哪里停顿、在哪里皱眉、在哪里放弃。
四轮下来最反直觉的三个发现:一是老人更愿意点“大色块”而不是“图标”,哪怕图标画得很清楚;二是老人对红色极度敏感,红色按钮会被认为是“危险”“别点”,所以呼救按钮最后改成了橙色,红色只用在确认环节;三是老人会反复确认,同一个操作点两次,所以所有提交类操作都要做幂等,否则会重复下单、重复发送。
注意:测试过程中不要让家属在旁边指导。有家属在场时,老人会出于面子假装会用,测试结果会严重失真。我的做法是把家属请到另一个房间。
6.2 灰度节奏:先小范围,再逐步放开
灰度策略上,我分了三批。第一批是内部员工家属,20 户左右,跑两周,主要看崩溃率和误报率。第二批扩大到 300 户,重点看推送到达率和客服工单分布。第三批才放开到 2000 户以上。
每一批之间我设了明确的卡点指标:崩溃率低于 0.3%、关键告警到达率高于 99%、因误报产生的投诉占比低于 5%,才允许进入下一批。这套卡点看起来保守,但避免了上线首周就被大量投诉冲垮的情况。特别提醒一点,健康类应用的误报成本极高,一次半夜的误报可能直接导致家属卸载,所以误报率的门槛要卡得比崩溃率更严。
另外,灰度期间一定要保留“一键回退”的能力。我的做法是把所有核心功能的开关做成服务端可配置,出现问题时不需要发版,改个开关就能降级。这个能力在灰度第二周救过一次——某个机型的呼救链路出现异常,通过开关把该机型的呼救临时切到短信通道,撑了两天才发修复版本。
做这个项目最大的体会是,适老化不是把字调大、把颜色调深就完事了,它本质上是一次对“默认用户是谁”的重新回答。我们习惯了假设用户会探索、会容错、会看提示,但老人不会,他们只会放弃。所以每一个交互决策,我都逼自己问一句:如果我七十岁、手有点抖、眼睛有点花、还怕点错扣钱,我敢不敢点这个按钮?这个问题的答案,往往比任何设计规范都管用。