适合谁看
正在调试鸿蒙防窥能力的人
觉得代码都接完了却还是很难验证的人
想知道为什么这类能力不能只靠模拟和日志的人
问题背景
很多鸿蒙系统能力只要权限正确、插件注册正常,就能较快在开发环境中观察结果。防窥保护不是这样,它更像一个"环境感知型能力"。
普通插件 vs 防窥保护的调试差异:
维度 | 普通插件(如 TTS) | 防窥保护 |
|---|---|---|
触发条件 | 代码调用 | 真实环境变化 |
验证方式 | 看日志/听声音 | 看蒙层是否出现 |
模拟难度 | 低(可以 mock) | 高(需要真实设备环境) |
外部依赖 | 权限 | 设备支持 + 开关 + 人脸识别 + 环境 |
项目中的真实场景
食界探味当前的防窥实现涉及 3 层:
层 | 文件 | 调试关键 |
|---|---|---|
鸿蒙原生层 |
| 系统条件、订阅状态、事件触发 |
Flutter 通道层 |
| 事件接收、状态变化 |
页面层 |
| 激活/取消时机、UI 响应 |
核心实现
一、防窥保护依赖的设备条件
防窥保护不是"代码对了就能用",它需要多个设备条件同时满足:
条件 | 说明 | 如何检查 |
|---|---|---|
设备支持防窥能力 | 不是所有鸿蒙设备都支持 |
|
防窥系统开关已开 | 用户需要在系统设置中开启 | 系统设置 → 安全 → 防窥 |
应用防窥开关已开 | 需要 | 执行器逻辑 |
人脸识别可用 | 设备需要有前置摄像头 | 硬件要求 |
当前用户是机主 | 只有机主的防窥才生效 | 系统判断 |
鸿蒙侧的开关检查:
// AntiPeepProtectionPlugin.ets private async ensureSubscription(): Promise<void> { if (this.isSubscribed) return; // 检查系统开关是否已开 const isOpen = await dlpAntiPeep.isDlpAntiPeepSwitchOn(); if (!isOpen) { // 开关未开,需要弹设置对话框 if (this.hasRequestedOptionsForActivation) { console.info(TAG, 'Anti-peep option dialog already requested'); return; } this.hasRequestedOptionsForActivation = true; const requestResult = await dlpAntiPeep.requestAntiPeepOptions(this.getAbilityContext()); if ( requestResult !== dlpAntiPeep.AntiPeepOptionsResult.SUCCESS && requestResult !== dlpAntiPeep.AntiPeepOptionsResult.ALREADY_ON ) { console.info(TAG, `Anti-peep not enabled: ${requestResult}`); return; } } // 订阅状态事件 dlpAntiPeep.on('dlpAntiPeep', this.onStatusChange); this.isSubscribed = true; }调试检查:
检查项 | 预期 | 异常表现 |
|---|---|---|
| 返回 true | 返回 false,需要弹设置 |
| SUCCESS 或 ALREADY_ON | 其他结果,防窥未启用 |
订阅成功 |
| 订阅失败 |
二、防窥保护依赖的真实环境因素
即使代码和开关都对了,防窥保护还需要真实环境触发:
环境因素 | 说明 | 能否模拟 |
|---|---|---|
有人旁观 | 人脸识别检测到非机主 | ❌ 需要真实场景 |
距离合适 | 旁观者距离屏幕足够近 | ❌ 需要真实场景 |
光线合适 | 环境光线允许人脸识别 | ❌ 需要真实场景 |
屏幕角度 | 设备角度适合检测 | ❌ 需要真实场景 |
这就是为什么防窥保护必须在真机上调试——这些环境因素无法通过代码模拟。
三、防窥保护的状态型逻辑
AntiPeepProtectionPlugin不是单次方法调用,而是一个状态机:
activate() │ ├─ 检查系统开关 │ ├─ 已开 → 订阅事件 │ └─ 未开 → 弹设置对话框 │ ├─ 订阅状态事件 │ → dlpAntiPeep.on('dlpAntiPeep', onStatusChange) │ ├─ 获取初始状态 │ → getDlpAntiPeepInfo() │ └─ 根据状态决定行为 ├─ PASS → 重置蒙层标记 └─ HIDE → 设置蒙层 + 通知 Flutter deactivate() │ ├─ 取消订阅 │ → dlpAntiPeep.off('dlpAntiPeep', onStatusChange) │ └─ 重置状态变量调试检查:
检查项 | 预期 | 异常表现 |
|---|---|---|
订阅成功 |
| 订阅失败 |
状态回调触发 | onStatusChange 被调用 | 没有事件 |
PASS 状态 |
| 蒙层标记没重置 |
HIDE 状态 |
| 蒙层没设置 |
Flutter 收到事件 |
| 事件没到 Flutter |
四、真机调试的完整流程
步骤 1:检查设备条件 │ ├─ 设备是否支持防窥? → isDlpAntiPeepSwitchOn() ├─ 系统开关是否打开? → 系统设置中检查 ├─ 应用开关是否打开? → requestAntiPeepOptions() 结果 │ ▼ 步骤 2:检查原生订阅 │ ├─ isSubscribed 是否为 true? ├─ onStatusChange 是否被注册? ├─ getDlpAntiPeepInfo() 返回什么? │ ▼ 步骤 3:触发真实环境 │ ├─ 让另一个人看屏幕 ├─ 调整距离和角度 ├─ 观察是否触发 HIDE │ ▼ 步骤 4:检查事件回推 │ ├─ ArkTS 日志:Anti-peep status HIDE ├─ ArkTS 日志:Anti-peep mask layer shown ├─ Flutter 日志:Anti-peep event: HIDE │ ▼ 步骤 5:检查页面响应 │ ├─ visibilityState 是否变为 hidden? ├─ 页面内容是否被隐藏? ├─ 蒙层是否出现? │ ▼ 步骤 6:检查恢复 │ ├─ 旁观者离开 ├─ 触发 PASS ├─ visibilityState 恢复 visible ├─ 页面内容恢复显示五、防窥保护 vs 其他插件的调试对比
维度 | TTS 插件 | ASR 插件 | 防窥插件 |
|---|---|---|---|
触发方式 | 代码调用 | 用户说话 | 真实环境变化 |
验证方式 | 听声音 | 看文本 | 看蒙层 |
模拟难度 | 低 | 中 | 高 |
真机依赖 | 低(模拟器可测) | 中(需要麦克风) | 高(需要真实环境) |
调试周期 | 短 | 中 | 长 |
日志价值 | 高 | 高 | 中(需要配合真机观察) |
六、真机调试时的日志重点
鸿蒙侧日志:
// AntiPeepProtectionPlugin.ets // 开关检查 console.info(TAG, `Anti-peep switch: ${isOpen}`); // 订阅状态 console.info(TAG, 'Anti-peep status subscription registered'); // 状态变化 console.info(TAG, 'Anti-peep status PASS'); console.warning(TAG, 'Anti-peep status HIDE'); // 蒙层设置 console.info(TAG, 'Anti-peep mask layer shown'); // 设置请求 console.info(TAG, 'Anti-peep option dialog already requested'); console.info(TAG, `Anti-peep not enabled: ${requestResult}`);Flutter 侧日志:
// anti_peep_protection_channel.dart // 事件接收 if (event == 'HIDE') { AppLogger.warning( '$message - possible anti-peek trigger detected on collection screen', ); } else { AppLogger.info(message); }真机调试时的日志观察顺序:
1. 检查开关日志 → 确认系统条件满足 2. 检查订阅日志 → 确认订阅建立 3. 触发环境变化 → 观察 HIDE/PASS 日志 4. 检查 Flutter 日志 → 确认事件到达 5. 观察页面 → 确认 UI 响应关键代码位置
文件 | 调试关键 |
|---|---|
| 鸿蒙原生层 |
| Flutter 通道层 |
| 页面激活/取消 |
常见坑
在不满足设备条件的环境里反复怀疑代码— 先确认设备支持防窥
只看插件激活成功,不验证状态变化— 激活成功不代表防窥生效
以为没有触发 HIDE 就一定是代码错误— 可能是环境条件不满足
没把系统条件、原生状态和 Flutter 结果三层一起看— 需要完整链路验证
在模拟器上调试防窥— 模拟器不支持真实环境检测
没有检查
isDlpAntiPeepSwitchOn()— 系统开关未开时一切都不生效
可复用模板
防窥调试检查清单
真机调试前: □ 设备是否支持防窥能力? □ 系统防窥开关是否打开? □ 应用防窥开关是否打开? 真机调试中: □ isSubscribed 是否为 true? □ onStatusChange 是否被注册? □ 触发环境变化(有人旁观) □ 观察 HIDE/PASS 日志 □ 检查 Flutter 事件接收 □ 观察页面 UI 响应 真机调试后: □ 旁观者离开后是否恢复 PASS □ 页面内容是否恢复显示 □ deactivate() 是否正确取消订阅防窥调试公式
防窥调试 = 系统条件(设备+开关) + 原生状态(订阅+事件) + Flutter 页面响应(状态+UI)防窥 vs 其他插件调试对比
TTS 调试:代码调用 → 听声音 → 完成 ASR 调试:用户说话 → 看文本 → 完成 防窥调试:系统条件 → 真实环境 → 原生事件 → Flutter 状态 → 页面 UI本篇总结
鸿蒙防窥保护比普通插件更依赖真机和真实环境,这不是偶然,而是能力模型决定的:
设备条件— 不是所有设备都支持,需要
isDlpAntiPeepSwitchOn()检查环境因素— 需要真实的人脸识别触发,无法 mock
状态型逻辑— 不是单次调用,而是持续订阅 + 状态变化
完整链路— 系统条件 → 原生订阅 → 事件触发 → Flutter 接收 → 页面响应
调试时必须同时看设备条件、原生状态和页面响应。只在代码层兜圈子,通常很难真正定位这类问题。