
eChain 这个名字是我给自己做的一个手机应用起的“花名”直译过来就是“电子钥匙扣”。起因特别朴素我裤兜里的实体卡实在太多了。小区门禁卡、图书馆借书卡、健身房会员卡、楼下打印店的储值卡、超市会员卡再加上偶尔发的临时访客码和会议室预约码这些卡片摞在一起比手机还厚。更尴尬的是每次要用的时候永远翻不到对的那一张好不容易翻到了扫码枪还经常对着磨损的条码唉声叹气。eChain 要解决的就是这个问题——把能数字化的卡片全部变成手机里的一张张“数字卡片”像钥匙串上的挂件一样排在一起点开就能亮出二维码或条形码也能快速查看卡号、有效期和备注。它本身不是一个重型的卡包平台而是走“轻量、趣味、本地优先”的路线。适合天天带一堆卡的通勤族、学生党也适合那些想练手做一个完整小工具的独立开发者参考。这篇文章就把整个项目的设计思路、核心实现、实际踩坑和常见问题一口气盘清楚。1. 项目解读eChain到底在解决什么问题1.1 实体钥匙串的物理痛点先数一数实体钥匙串的毛病你就知道为什么会有这个项目。第一是厚。每张卡都有塑料壳、磁条、芯片叠起来不仅占地方放在裤袋里坐下还会硌腿。第二是乱。门禁卡和超市会员卡长得差不多夜间掏出来根本分不清借书卡上一次用是三个月前真要还书的时候却死活想不起塞哪儿了。第三是损耗。条码这东西特别娇气刮花一点、沾上油污扫码枪就“嘀”一声后报错。第四是临时性。访客码、会议邀请码、停车券用一次就失效却还得一直留着小纸条或截图简直烦人。这些问题的共同点是卡片的信息价值其实很低真正值钱的是“卡号”“条码内容”“有效期”这几个字段但物理载体把它们绑死了。eChain 的思路就是把这几个字段从实体卡里解放出来让手机成为唯一入口。你不需要在一堆塑料卡里翻找你只需要打开应用搜索“健身”那张卡就自己弹出来。1.2 “数字钥匙链”的产品概念“数字钥匙链”这个叫法比“卡包”更贴合 eChain 的气质。卡包应用通常很严肃一上来就让你填银行、填身份证感觉像在办理政务。钥匙链就不一样你可以往上面挂各种小挂件有的是毛绒玩具有的是铃铛有的是迷你扳手。eChain 在这个比喻下每张数字卡片就是挂件可以换颜色、加贴纸也可以顺手调整顺序。翻卡动画像在拨弄真实的钥匙串小组件放在桌面也像一串小挂饰。这个定位决定了产品的两个决策第一不做复杂的账户系统不做云端同步打开就能用第二尽量把“打开卡片到扫出条码”的路径缩短到两秒以内不给用户增加认知负担。很多卡包应用失败就是因为功能太全用户根本不知道把会员卡放哪里eChain 只保留最简单的心智模型——钥匙链上挂卡片手机上放卡面。1.3 哪些卡片值得数字化不是所有卡都适合塞进 eChain。我实际整理下来值得数字化的卡有这几类卡片类型典型场景数字化方式离线可用性会员卡超市、药店、书店积分条形码/二维码展示完全离线借阅卡图书馆、共享书房条形码展示完全离线储值卡打印店、洗车店、健身房卡号条形码完全离线临时访客码物业、展会、访客预约二维码图片/文本需在有效期内门禁卡本人的小区、公司只记录卡号和备注不经NFC复制这里我必须说清楚一个边界eChain 做的是“记录本人合法持有的卡面信息”和“展示官方生成的二维码/条形码”不做任何形式的磁卡、芯片卡克隆也不收集别人的卡信息。有些实体门禁卡虽然有芯片但复制或模拟可能违反物业或公司的管理规定所以项目第一版就明确不碰 NFC 模拟。对于门禁卡我只建议记录卡号、楼栋和备注方便报修或补办时查询。反过来银行卡、身份证这类强敏感证件不建议放进来。虽然技术上可以加密存储但手机丢失后的风险远远大于便利。eChain 的定位是“日常小额工具”不是保险箱。2. 功能设计与方案选型2.1 第一版功能清单任何工具型应用第一步都是做减法。eChain 的 MVP最小可行产品只保留了六个功能自定义卡片手动添加卡片选择类型、颜色、贴纸填写名称和字段。卡面展示点开卡片后全屏展示二维码或条形码自动调整屏幕亮度。扫码录入用相机扫描已有的条形码/二维码自动把内容填入卡片。搜索与分组顶部搜索框按名称/标签筛选支持“常用”“卡片类型”分组。生物识别锁打开敏感卡片前需要 Face ID / 指纹验证。桌面小组件放置常用卡片点击直接跳到对应卡面。刻意砍掉的东西也列一下云端同步、NFC 模拟、卡面模板商城、社交分享、过期提醒全部不做。砍掉这些不是因为没价值而是它们会让项目陷入“大而全”的泥潭。比如云端同步一旦牵扯账号系统就必须处理注册、找回密码、多端冲突这一个月开发时间根本不够。没有这些功能eChain 的第一版才能在三十天内跑通完整流程。2.2 本地存储而不是云端同步为什么坚持本地优先我自己的理解是钥匙串本来就该长在你身上而不是存在某个服务器上。云端同步对日常卡片来说收益不高却很重。隐私是最直接的原因。会员卡号、借阅卡号虽然不是密码但很多人会顺手把手机号、住址写在备注里这些数据放在本地数据库只有用户自己能解锁。没有服务器就没有泄露面。其次是可用性。在车库、地铁、地下食堂这些信号差的地方打开 eChain 必须要立刻亮码不能等网络请求。本地数据库天然满足这一点。最后是成本。跑一次云端同步要做多端变更合并、冲突策略、服务端数据加密工程量翻一倍不止而 eChain 的目标用户其实只需要一台手机。“本地优先”不意味着不备份。eChain 的备份方案是导出一个加密文件用户手动发送到自己的网盘、邮件或另一台设备。恢复时输入备份口令应用解密后导入。这个方案做到了没有服务器也能安全迁移。2.3 视觉交互怎样做出“fun”标题里的“Fun”不是装饰它决定了 eChain 和普通卡包应用之间最直接的差异。我不想做一个列表页塞满灰白单元格的工具而是想让人打开时有一种“把玩小物件”的愉悦感。第一版做了四个趣味点。卡片列表用的是“钥匙串”纵向布局每张卡片像挂件一样有微弱摆动动画长按拖动可以重排。点开卡片后卡面有一个 180 度翻转过渡背面是字段信息正面是二维码。第三是“贴纸”内置大概二十种小图标比如星星、猫爪、闪电、西瓜可以贴在卡片角上纯装饰但很出效果。第四是摇一摇随机卡片适合有“选择困难”的用户——摇一下手机随机弹出一张常用卡。这些交互的实现成本都不高。摆动动画用 Flutter 的AnimatedContainer加轻微旋转就能做翻转用AnimatedSwitcher贴纸只是叠加一个小图标。真正贵的不是代码是设计上的克制。后来测试时发现动画太频繁会给人一种“不专业”的感觉所以我把列表摆动改为只在第一帧播放一次点开卡片的翻转时间控制在 250 毫秒——不快不慢刚好让人感觉到“翻面”但又不会等待。2.4 为何第一版不做NFC模拟很多人一听“数字钥匙串”第一反应是“能不能把门禁卡放进手机里”。这个需求我懂但 eChain 第一版明确不做 NFC 模拟原因有三。第一是平台限制。iOS 的 Core NFC 目前只支持读取不支持把手机模拟成任意卡片Android 虽然支持HostCardEmulation但很多小区的门禁读卡器频率特殊、加密方式私有扫了也不一定认。第二是厂商碎片化。小米、华为、三星各自有系统级模拟方案接口不公开而且适配成本极高。第三是合规风险。未经授权复制门禁卡在很多物业管理规约里不被允许我不想把一个工具应用引到灰色边缘。替代方案是把门禁卡作为“文本卡片”录入记录卡号、楼栋、物业电话并添加一张卡面照片。这样照样能快速查到信息也不触碰红线。如果以后系统级方案稳定且用户有清晰授权再考虑拓展 NFC 相关能力。3. 核心实现细节数据模型、扫码与安全存储3.1 卡片数据模型eChain 的数据模型围绕一个原则卡片是“壳”字段是“内容”。不同卡片的字段差异非常大会员卡只需要卡号和手机号借阅卡需要读者证号和有效期访客码需要二维码原图。如果为每种卡片单独建表后续加类型会改数据库太僵化。所以最终采用了一个主表cards加一个fieldsJSON 数组的结构{ id: uuid-v4, type: barcode, title: 楼下超市会员卡, color: #F5A623, sticker: star, barcodeData: 6923450654321, barcodeFormat: code128, qrData: , createdAt: 1722441600000, updatedAt: 1722441600000, fields: [ { label: 会员卡号, value: 6923450654321, sensitive: false }, { label: 手机号, value: 138****8000, sensitive: true }, { label: 备注, value: 每月8号会员日, sensitive: false } ] }这个结构有几个好处。fields数组让新增字段不需要改表结构兼容未来任何新卡片。sensitive标记让应用知道哪些字段需要加密和蒙版。color和sticker纯属视觉字段但对用户认知很有帮助——快餐店的卡用红色加鸡腿图标健身房的卡用深灰加哑铃图标扫的时候一眼就能认出来。类型字段type只保留三个值qr、barcode、text。qr展示二维码barcode展示一维码text直接以大字号文本展示。没有设计“图片卡”因为图片占用空间大、隐私控制难访客码这类需求实际上也可以把屏幕截图裁剪后导入等后续版本再优化。3.2 二维码与条形码的生成策略扫码展示是 eChain 的高频路径也是最容易翻车的地方。二维码生成用 Flutter 生态里成熟的qr_flutter传入qrData字符串即可。这里有一个细节很多二维码 App 会默认给内容加纠错级别但 eChain 强制用Q级约 25% 容错率这样即使手机贴膜上有细微划痕也能扫出来。一维码则用barcode_widget默认生成 Code128 格式。别小看格式选择——很多老式超市扫码枪只认 Code128 或 EAN-13你用 QR 码去怼反而会被嫌弃。另一个实测很关键的技巧是“边距”。生成二维码时默认的空白边距可能偏小印刷品没问题手机上因为屏幕边缘的曲率和贴膜容易扫码失败。我当时直接把边距拉大到了 32 像素识别率立刻提升。屏幕亮度也要在扫码界面自动拉到最高并在页面左上角放一个“手电筒”按钮因为用户很可能在昏暗的车库、货架角落亮卡。一维码还有一个特别坑的点部分扫码枪对“屏幕上细密的条纹”会有摩尔纹导致解码失败。我后来把条形码的高度固定为屏幕宽度的 22% 左右条宽按内容长度自适应缩放并且始终铺在纯白背景上不叠加任何纹理这个问题才基本解决。3.3 敏感信息的加密方案卡片数据里有些字段确实需要保护比如备注里的手机号或者“家庭住址”这种自由文本。eChain 的加密采用“数据库整体加密 字段级蒙版”双保险。数据库层本地 SQLite 使用 SQLCipher 加密。SQLCipher 是 SQLite 的加密版本每页数据都经过 AES-256 加密密钥由一个随机生成的 256 位主密钥派生而来。主密钥不直接存在磁盘上而是放入系统安全区iOS 存 KeychainAndroid 存 Keystore。这样可以保证数据库文件即使被拷贝出来没有密钥也读不出内容。字段级加密针对sensitive: true的字段在写入数据库前再用 AES-GCM 加密一遍。为什么双层加密因为 SQLCipher 解决的是“数据库文件被拖走”的风险而字段级加密解决的是“应用内数据被其他模块误用”的问题。比如列表页预览卡片时我只需要标题和颜色绝不触碰敏感字段只有点开卡面且通过生物识别后才在内存中临时解密。GCM 模式同时提供密文校验只要密文被人改过解密就会失败应用可以立刻提示“数据异常”。生物识别锁用local_auth插件实现打开敏感卡片前触发 Touch ID / Face ID / 指纹。这里有一个产品细节第一次验证失败后eChain 不会直接拒绝用户而是弹出一个“稍后再说”按钮允许用户临时查看非敏感字段。否则在光线差、手指湿的情况下用户会被锁在自己的数据外面体验非常糟糕。3.4 桌面小组件的实现思路小组件是 eChain 最受欢迎的功能之一。实现上Flutter 主应用本身不支持直接渲染到桌面需要原生平台配合。我采用的是home_widget插件主应用负责把“卡片缩略图”渲染成一张图片写入共享存储原生小组件读取图片并显示点击时通过 URL Scheme 跳回主应用并携带卡片 ID。这个方案避开了平台小组件框架的大量限制。iOS WidgetKit 的TimelineProvider每天刷新次数有限绝不能让它实时读数据库Android RemoteViews 只支持有限的 View 类型不支持自定义复杂布局。所以先离线渲染成图片是性能和复杂度都最优的做法。实际操作中有三个需要注意的坑。第一图片尺寸要做多个适配iPhone 的小组件尺寸和 Android 桌面小部件尺寸不一样我直接生成了 1x、2x、3x 各一套缩略图。第二点击事件要用Uri跳转在 Flutter 侧通过getInitialUri和onUri事件监听不能简单用sharedPreferences传值否则冷启动和热启动的时序处理会让你怀疑人生。第三小组件图片不要包含敏感字段只显示卡片标题、颜色和贴纸因为小组件本身在锁屏界面也可能可见。4. 实操复盘从MVP到天天用我踩过的坑4.1 MVP范围裁切带来的好处现在回头看eChain 能在一个月内落地最大功臣是“先砍功能”。第一版砍掉键盘自定义模板、砍掉多端同步、砍掉云备份只保留“添加卡片、亮码、搜索、锁屏小组件”这条核心链路。砍完之后开发任务一下子清晰了数据模型、加密存储、二维码渲染、扫描录入、小组件五个模块每个模块完成标准明确。做独立项目最忌讳的就是边做边加需求。我一开始也幻想做“卡面市场”让用户下载别人设计的卡面但很快意识到这是社交产品不是工具产品。工具产品的核心是“从打开到用上”的路径最短。eChain 把“加一张卡”的默认流程控制在三步很多用户就不觉得这是又多了一个 App而是真的把钥匙串数字化了。4.2 扫码识别率的教训第一个版本发布后我拿给同事试反馈最多的问题是“超市会员码扫不出来”。我拿自己的中端 Android 手机测没有任何问题但同事用的是贴了厚钢化膜的 iPhone加上超市扫码枪的年头比较久问题就暴露了。排查之后定位到三个原因一是钢化膜反光旧式红外扫码枪对屏幕玻璃膜产生的反光非常敏感二是屏幕亮度不够手机自动亮度在室内拉不高三是我一开始用 QR 码显示所有条码但老扫码枪优先识别的是 Code128。修复方案很直接手动把亮码页面亮度强制拉满默认优先使用 一维码渲染边距放大同时增加一个“放大两倍”模式。放大模式不是简单把图片 scale而是重新渲染一个更大尺寸的条码图保证条纹清晰度。如果你也在做类似功能建议准备一台“最挑剔”的扫码枪来测试超市款、仓储款、快递手持机这三类覆盖了绝大多数真实场景。千万别只拿自己手机摄像头测手机摄像头识别能力比专业扫码枪强太多了测不出真实问题。4.3 备份恢复的痛与版本兼容备份功能一开始做得很随意导出 JSON 文件让用户存到网盘。后来被朋友一提醒才意识到会员卡号、手机号明文存在一个 JSON 里分发出去比放实体卡还危险。我紧急改成“加密备份”导出前对整个 JSON 做 AES-GCM 加密口令由用户设置恢复时要求输入口令解密成功后先校验版本号再导入数据。版本号字段是后来补上的因为第一版导出格式里没有version结果我开发第二版时改了字段结构旧备份导入直接崩。加了version之后导入逻辑可以做到向下兼容未来 v2 的恢复程序遇到 v1 数据会先做字段映射再写入数据库。这个细节对所有做了导出功能的项目都适用永远是“先写版本号再写内容”。4.4 小组件的刷新限制和缓存策略小组件开发到后期遇到了一个典型的“平台限制”问题。iOS 的 WidgetKit 对刷新频率有预算控制同一个小组件一天内多次主动刷新会被系统降级如果用户在小组件上看到的数据是两小时前导出的体验会打折扣。Android 的 RemoteViews 虽然没有这么严格的预算但也会因为厂商省电策略延迟刷新。我的处理方式是分层缓存主应用每次启动或切换前台时把常用八张卡片的缩略图批量生成并更新到共享目录小组件只读取缓存图片不做任何数据库查询。在 Widget 的Timeline里把刷新策略设为“每次进入前台刷新”加“每天固定时间刷新一次”尽量平衡实时性和系统预算。实际感受下来用户对这种两秒级延迟并不敏感能接受。4.5 安卓厂商后台机制带来的崩溃eChain 在安卓机上遇到的最隐性问题是“返回后相机预览黑屏”。扫描录入用的相机插件在某些国产系统上从后台恢复活动时预览 Surface 没有重新绑定导致整个页面黑掉。排查后发现是 Activity 被系统回收了但 Flutter 层还认为页面活着。解决方式是双管齐下把扫描页做成单独的 Activity并且在 Manifest 里指定android:launchModesingleInstance避免被系统前后台切换干扰同时监听AppLifecycleState.resumed在恢复时强制重建相机控制器。另外部分厂商对后台应用有激进清理策略普通用户如果无法打开应用多半不是 eChain 代码问题而是系统把应用加入了“不活跃应用列表”。我在帮助页里专门写了一条“请将 eChain 加入电池优化白名单”这个提示虽然有点土但实测能解决七成后台被杀的问题。5. 常见问题与排查技巧实录问题现象可能原因排查/解决方案亮码时二维码扫不出来屏幕亮度不够、边距过小、贴膜反光打开“高亮模式”并手动拉开边距一维码优先用 Code128条形码在旧扫码枪上扫不出生成格式用了 QR 而不是 Code128卡片类型里显式选择“一维码”不要统一用二维码渲染恢复备份后卡片少了备份文件版本与当前应用不兼容导入前检查 JSON 里的version字段先导出再升级打开应用后被要求反复验证生物识别库初始化失败降级到系统 PIN 码确认 Keychain/Keystore 权限正常小组件点击无反应URL Scheme 没注册或主应用被系统冻结检查home_widget的 Uri 注册添加省电白名单相机扫码页面黑屏Activity 被回收预览 Surface 未绑定使用单独 Activity singleInstance恢复时重建相机搜索不到某张卡标题和标签都不含关键词增加“卡号后四位”作为隐式搜索字段方便按记忆碎片查找这里再补充几个经验类的小技巧。用户在卡片列表页快速滑动时如果卡面上有敏感字段蒙版即使屏幕被截图截图上显示的也是***但二维码区域本身是明文所以敏感卡片的二维码展示页要启用“防截屏”模式。Flutter 里可以用FlutterWindowManager插件设置 FLAG_SECUREAndroid 生效iOS 只能靠UIScreen层面拦截目前系统限制较多至少微信小程序和部分银行应用是这么处理的。还有一个容易被忽略的点二维码内容里不要带不可见字符。扫码录入时很多相机会把诸如“\r\n”尾随命令一并识别进去复制到 QR 内容里后打印出来的二维码虽然肉眼一样但专业设备可能解码失败。所以录入后我做了一步“清洗”去除换行符和首尾空格只截取有效载荷。这一步非常简单但能省掉大量用户反馈。6. 扩展想法与一点体会项目做到这里eChain 已经在我手机上服役了两个月。我自己的使用习惯是门禁卡和通勤卡做成常驻桌面小组件会员卡按颜色分类每次去超市只要亮出手机收银员扫一下就走已经完全忘记了那个鼓鼓囊囊的卡片袋。如果继续做我会优先补三个能力。一是卡片到期提醒比如借书卡借阅到期、临时访客码过期前 24 小时推送二是“家庭共享”模式通过加密频道让家人之间分享常用卡片的展示权限但不上传到服务器三是离线卡面缓存优化把常用卡片视图预加载到内存让亮码速度再快 200 毫秒。最后分享一个我自己做这类项目最深的感觉工具类应用最容易死在一个执念上——功能越多越好。实际上用户要的就很简单。eChain 能做到“打开即用、扫得出、不泄露”就已经完成了 80% 的使命。剩下的 20%与其说是在增加卡片类型不如说是在减少用户每一次操作的时间。你如果也想做个类似的数字钥匙链先把实体钥匙串放在桌上盯着它看十分钟你会发现真正需要被数字化的永远只有那两三张天天用的卡。