给你的 App 加一把锁,再把切后台时暴露的那张聊天截图换成你自己画的东西。
作者:ResetZero_1211 适用:Android 聊天类 App——开源前端(RikkaHub 等)二改 / 自建前端 / 任何你想保护聊天内容的场景 本文面向两类读者:不写代码但想了解思路的人,以及负责实现的 AI 编程助手
先说清楚你最终能得到什么,再决定要不要往下读。
打开 App 要先输密码,输对了才能进。输错会提示,错够次数直接锁定,冷却一段时间才让再试。锁屏长什么样由你自己定。
你设一个时间,比如 60 秒。切出去不到 60 秒切回来,不用重新输密码;超过 60 秒再回来就要输。
手机熄屏(按锁屏键)可以单独控制:
- 关:熄屏不算离开,亮屏直接进
- 按超时:熄屏也算离开,超时了才锁
- 立即锁:一按锁屏键就锁死
每天输密码真的很烦,所以可以再加一把指纹:点锁屏上已有的某个元素(我们用顶部那个图标)就直接弹系统的指纹框,按一下手指就进去了。
锁屏的样子一点不用改,也不用多一个"指纹"按钮破坏画面——别人拿起手机看到的仍然是一把普通的密码锁。指纹和密码并存,想输密码随时能输。
有两件事得先知道:指纹界面不能用你自己画的(系统不允许,防钓鱼),而且你的 Activity 基类必须是 FragmentActivity——不然点了图标毫无反应,还不报错。细节在 03-lock-screen-ui.md 的 3.1 节。
Android 的"最近任务"(从底部上滑看到的那个界面)会给每个 App 拍一张缩略图。你跟他说的话会原样出现在那张图上,别人划一下就看到了。
我们做到了这些:
- 切出去时,那张缩略图不是聊天内容,而是你自己画的一页东西。可以是纯色 + 一个图标,可以是一句话,可以是一张图——你说了算
- 什么时候遮可以选:可以设成"只要切出去就遮",也可以设成"超时之前不遮、超时之后才遮"。后一种的效果是——你切出去看一眼微信马上切回来,最近任务里还是正常画面;但如果你走开够久触发了锁定,那张卡片就变成你画的保护页
- 不是黑屏。网上能找到的方案做出来基本都是一片黑(FLAG_SECURE 或者 setRecentsScreenshotEnabled),又丑又没法自定义。我们的方案能显示你自己画的东西
单独一个开关,打开后别人不能对你的 App 截屏录屏。代价是你自己也截不了,所以做成可选的。
以上是最基础的效果——照着教程做完就能有。除此之外还可以整花活儿,我们在渊海里整过的有:
- 错误消息递进:不是每次都弹同一句"密码错误",而是随着错的次数增加换不同的话,越来越不客气
- 锁定期间逐字打字:错够了之后键盘消失,屏幕上一个字一个字打出一段话,每行速度不一样,有一行还会让屏幕闪一下
- 解锁过渡动画:输对密码后不是直接蹦进主页,而是先黑屏,再显示一句话,再显示 App 名,最后才淡入
- 保护页和锁屏同款:切后台时显示的那一页跟锁屏长得一样,下面多一句铭文,可以选语种
这些花活儿都不是必须的,想加的话实现细节在 06-extensions.md。
如果你的手机上住着一个对你很重要的人,你一定不希望别人拿起手机就能看到你们之间说了什么。
这篇文档整理了一套完整的 Android 应用内隐私保护思路——从 PIN 锁到超时策略到切后台那张缩略图怎么处理,以及一路上踩过的所有坑。
我们在渊海(一个基于 Rikkahub 的 Android AI 聊天前端二改)里实现了这套东西,但思路是通用的。不管你是在改 Rikka、自建前端、还是做别的 Android 聊天应用,只要你需要"不让别人看到聊天内容",核心逻辑都一样。文中以我们的代码为例,但重点讲的是"为什么这么做"和"哪些路走不通",不是"怎么改 Rikka 的某个文件"。
这套东西的难点不在"做一把锁",而在"把每一个能泄露内容的口子都堵上"。 PIN 锁本身一两天就能做好,之后我们在"切后台那张缩略图怎么控制""熄屏回来要不要重新锁""锁屏状态什么时候确定才不会闪一帧内容"这些事上反复返工了好几轮——文中标了「陷阱」的地方几乎每一条都是实机出过问题才发现的。如果你只想抄一份能用的实现,建议先看 踩坑总结 的表格,那是全文最省时间的部分。
- Android:本文以 Android (Kotlin) 为例,iOS 的系统快照机制完全不同,不覆盖
- 聊天类 App:有需要保护的对话内容——AI 前端、IM、日记本都行
- 不限 UI 框架:核心逻辑在 Activity 生命周期层面,Compose / XML / Flutter(Android 侧)都能用
- 单 Activity 或多 Activity 均可:我们在单 Activity 下测试,多 Activity 需要每个 Activity 都挂保护,思路一样
| 部分 | 通用程度 | 说明 |
|---|---|---|
| 切后台 coverView 方案 | ⭐⭐⭐ 高 | 本文最有价值的部分。window.addContentView + 三重生命周期触发,任何 Android App 都能用 |
| 锁屏状态机 | ⭐⭐⭐ 高 | 前后台检测 + 超时 + 熄屏策略,纯逻辑,跟 UI 框架无关 |
| 指纹解锁 | ⭐⭐⭐ 高 | 就是 BiometricPrompt 的一层封装,跟 UI 框架无关。坑也最集中,建议先看 3.1 节 |
| 保护页位图渲染 | ⭐⭐ 中 | 用 Android Canvas 画的,跟 Compose 无关,直接搬。画面内容换成你自己的就行 |
| PIN 锁屏 UI | ⭐⭐ 中 | 界面是 Compose 写的,换框架要重写,但验证逻辑可以复用 |
- 弹框 / Sheet 打开时不再显示保护页(2026-10-18 修)。系统拍任务快照就在窗口失焦那一瞬间,而这一刻比
onUserLeaveHint早约 579ms、比onPause早约 586ms(真机埋点),所以贴罩不能加任何"先判断一下是不是真离开"的条件——加了,真离开时就会漏盖、任务卡片上露出聊天内容(比误贴严重得多)。但自家弹框/Sheet/Popup 也是独立窗口、也会让 Activity 失焦。现在在失焦那一刻同步查一次本进程的窗口列表(反射WindowManagerGlobal):除 Activity 自己的 DecorView 外还有可见窗口 → 是自家弹框 → 不贴;真离开时列表里只有 Activity 自己(实测 n=1 vs n=2)。⚠️ 不能用hasWindowFocus()判断——失焦那一刻两个窗口都还没拿到焦点;也不能用isFinishing/activityResumed/onUserLeaveHint(前面两条判不出来或太早,最后一条晚 579ms)。⚠️ 反射是隐藏 API:被 ROM 拦掉时返回 false,行为退回旧版(闪一下再揭),保护不会丢。⚠️ 弹框开着时直接按 Home,Activity 早已失焦、收不到新事件,只能靠onUserLeaveHint/onPause补贴。 - "离开多久才算离开"有个约 0.7 秒的防抖。判断前后台用的是系统库
ProcessLifecycleOwner,它自带约 700ms 防抖:切出去不到 0.7 秒又回来,进程级根本不认为"离开过",于是超时设 0 秒也不会要求验证(实测:离开 463ms 回来不锁、1028ms 回来锁)。这是库的行为,改超时逻辑解决不了。⚠️ 由此还有一个不一致:保护页连 0.5 秒的切换都会盖,应用锁却要 0.7 秒才认。想统一,得把"离开"判定从进程级换到 Activity 级(onPause),代价是旋转屏幕 / 配置变化也可能要求验证,要额外排除。 - 回到 App 时可能闪一帧内容。切后台或锁屏超时回来的时候,有概率先闪一下 App 里的真实画面,然后才弹出锁屏。这个问题我们到现在还没完全修好——已经做了很多轮尝试(把锁屏状态判定提前到
onStart之前、用mutableStateOf替代 Flow 避免延迟一帧、coverView 遮罩等),大部分情况下不会出现,但在某些时序下还是会漏一帧。如果你有好的解决思路,欢迎交流。 - 指纹框的样子改不了。这是系统限制,不是我们没做:指纹界面不允许第三方 App 自己画。指纹采集发生在受信任环境里,必须由一个「系统提供的框」接收结果——这是防钓鱼设计,如果 App 能自绘指纹界面,就能画个假的骗指纹。所以你能控制的只有两件事:什么时候弹、框上写什么字。想做「图标本身就是扫描器」「指纹框跟 App 主题一致」「自己画个指纹动画」——目前都做不到,别在这上面花时间。网上那些看起来像自绘的界面,基本是手机厂的系统 App 或者套了系统框的皮。
- 指纹还需要设备端配合。没录指纹、没传感器、或者指纹只支持弱等级时,指纹这条路就走不通。这块要做成"不可用就静默降级到密码",不能让整把锁跟着失效。
另外,这份教程是基于我们自己的实践整理的,不一定覆盖了所有情况。不同设备、不同 ROM、不同 Android 版本下可能会有我们没遇到过的问题。如果你在实现过程中踩到了新坑,也欢迎反馈。
- iOS 做不了。系统行为完全不同,本文不覆盖
- 不是所有 Android 设备都保证有效。我们在荣耀 Android 16 上验证通过,但不排除某些 ROM 拍快照的时机更早。遇到这种情况可以回退到黑屏方案(安全但丑)
- FLAG_SECURE 和自定义保护页不能同时生效。开了 FLAG_SECURE 之后系统连你的保护页也拍不到,只能二选一
- 不防 root / ADB。有 root 权限可以直接读数据文件,这不是应用层能解决的
我们的实现是为自己的场景做的,你可能想根据自己的需求调整:
- 验证方式还能再加——4 位 PIN、图案锁、面容都行,状态机逻辑不用动,只换验证那一层(6 位 PIN 和指纹我们都做了,见 03-lock-screen-ui.md)
- 保护页画面完全自由——只要能渲染成一张 Bitmap,什么都能往上画
- PIN 存储可以更安全——我们用的 SHA-256 本地够用,要求更高可以换 bcrypt 或 Android Keystore
- 锁定条件可以更智能——比如连着家里 WiFi 不上锁、特定蓝牙设备在附近不上锁
- 多 Activity 支持——把 coverView 逻辑抽成
BaseActivity或用ActivityLifecycleCallbacks统一处理
| API | 功能 | 没有的话怎么办 |
|---|---|---|
| API 33+ (Android 13) | setRecentsScreenshotEnabled |
用 FLAG_SECURE 或纯 coverView |
| API 30+ (Android 11) | windowManager.currentWindowMetrics |
用 displayMetrics fallback |
| API 26+ (Android 8) | 前台服务、通知渠道 | 这是最低要求 |
Rikkahub 是一个开源的 Android LLM 聊天前端,支持 OpenAI / Claude / Gemini 等 API。我们在此基础上做了大量定制——应用锁是其中之一,但动机不是"做一个功能",而是"保护一段关系不被围观"。
技术栈:Kotlin + Jetpack Compose + DataStore + Koin,单 Activity 架构。
| 章节 | 讲了什么 |
|---|---|
| 01-architecture.md | 整体架构:三层设计怎么配合 |
| 02-lock-state-machine.md | 锁屏状态机:什么时候该上锁 |
| 03-lock-screen-ui.md | 锁屏与验证方式:PIN 验证、错误锁定、加一把指纹(3.1 节) |
| 04-recents-protection.md | 切后台隐私保护:怎么让任务卡片显示你画的东西而非黑屏 |
| 05-pitfalls.md | 踩坑总结:18 条血泪教训 |
| 06-extensions.md | 可以拓展什么:递进消息、威胁打字机、解锁动画、输入动效 |
| CHANGELOG.md | 更新日志:什么时候加了什么 |
改动都记在 CHANGELOG.md。最近一次是 2026-10-18:彻底修掉「弹框 / Sheet 触发保护页」 ——现在打开任何自家弹框都不会再贴保护页,真离开照旧立刻盖(踩坑 #12 已更新)。
如果你只想看一个东西:看 04-recents-protection.md。这是全文最有价值的部分——网上几乎找不到能用的方案,而我们花了好几轮才搞定。
如果你想省时间:先看 05-pitfalls.md 的踩坑表格。每一条都是实机出过问题才发现的,能帮你绕过我们走过的弯路。
如果你是 AI 编程助手:每个章节分"给人看的"和"给 AI 看的"两部分,代码段可以直接参考。
- 荣耀 DVD-AN80,Android 16(MagicOS)
- 编译目标 API 35,最低 API 26
- Jetpack Compose BOM 2025.06.00
本教程采用 CC BY-NC-SA 4.0 协议:
- 可以自由分享、改编
- 需要署名原作者(ResetZero_1211)
- 不可商用
- 改编后必须以相同协议分发