本文基于 Device Security Kit 防窥保护(dlpAntiPeep)的官方资料与华为开发者联盟最佳实践整理。文中代码是为说明问题自写的示例,不是官方示例的搬运;API 名称、权限与版本号等事实性信息均标注出处;涉及真机表现的部分已明确标注,未做任何实测数据编造。

引子:地铁上那一眼,挡不住也看不见
V哥认识一个做银行应用的朋友,安全评审年年过,密码键盘、防截屏、登录二次认证一样不少。有次V哥坐地铁,正好看见旁边一位乘客打开这类应用查账单,手机斜对着车门方向,车厢里人挤人------那一眼里能看到什么,只有天知道。
事后V哥问他:你们应用管得了这个吗?他苦笑:管不了。密码键盘防的是"机器偷",防不了"人看"。用户登进来了,手机摆在那儿,旁边谁瞟了一眼、瞟走了多少信息,应用层既感知不到,也没法响应。
这事在 HarmonyOS 6.1 上有解了:Device Security Kit 提供防窥保护能力(dlpAntiPeep),系统借助前置摄像头和人脸识别判断"屏幕前是不是只有机主一个人",检测到非机主在窥视时通知应用,应用可以选择拉起系统级蒙层把整个窗口盖住(参见开发者联盟防窥保护最佳实践)。这期V哥把这条链路拆开讲:定位、接口、代码、坑,最后给一份接入自检清单。
一、先定位准:它防的是"登录之后"的问题
很多人第一反应拿它当登录安全做,这就用错了。官方的定位很清楚------防窥保护解决的不是"谁能进入 App"(那是身份验证的事),而是**"进入之后,旁边有没有人在看"**(华为开发者联盟实战分享)。
两个能力是互补关系,不是替代关系:
| 能力 | 防什么 | 失效场景 |
|---|---|---|
| 身份验证(密码/人脸登录) | 谁能打开应用 | 登录之后,手机被旁人看到 |
| 防窥保护(dlpAntiPeep) | 打开之后,谁在窥视屏幕 | ------正是上面那个空档 |
系统怎么判断"有人在看"?官方的机制是:把长期通过人脸解锁的机主人脸作为基准,通过前置摄像头实时检测注视状态------只有机主一个人看屏幕,状态是 PASS;检测到机主和非机主同时注视屏幕,状态变为 HIDE。应用订阅这个状态变化,收到 HIDE 就做自己的保护动作。
V哥把整条"感知---状态---响应"链路画成一张图,系统管上面两层,应用只管最下面一层:

这里有个V哥认为很聪明的设计:系统只负责感知,不替应用决定怎么防。HIDE 到了,你可以模糊余额数字、暂停视频、遮盖聊天内容,也可以直接拉系统级蒙层------感知和响应分层,各应用按自己的业务形态接。
二、接入解剖:一个模块、两道前提、六个接口
先看物理形态。所有能力都收在 @kit.DeviceSecurityKit 的 dlpAntiPeep 模块里,接口不多,每个职责都清楚:
| 接口 | 职责 | 版本口径 |
|---|---|---|
isDlpAntiPeepSwitchOn() |
查系统级防窥开关是否已开 | API 20 起 |
requestAntiPeepOptions(context) |
拉起系统弹窗,引导用户为当前应用开启防窥保护 | API 23 起 |
on('dlpAntiPeep', callback) |
订阅窥视状态变化(PASS/HIDE) | API 20 起 |
getDlpAntiPeepInfo() |
同步获取当前状态快照 | API 20 起 |
setAntiPeepMaskLayer(windowId) |
在指定窗口拉起系统级蒙层 | API 20 起 |
off('dlpAntiPeep') |
释放订阅 | API 20 起 |
版本口径来自官方最佳实践页的标注(参考),能力随 HarmonyOS 6.0 Beta 起逐步开放,建议在 6.1 及以上机型上使用,以实际支持机型为准。另外当前仅支持 Phone 设备,模拟器上会直接报不支持------别在模拟器上白折腾。
两道前提比接口本身容易漏:
第一道:设备侧条件。 用户必须已录入人脸数据(系统拿它做机主基准)、设备支持人脸识别,且用户在「设置 > 隐私与安全 > 防窥保护」里打开了系统开关。前置校验可以用 userAuth.getAvailableStatus 判断:错误码 12500005 表示设备不支持人脸识别,12500010 表示人脸没录入------这两个码建议做成用户引导,而不是直接报错。
第二道:受限权限。 应用要声明 ohos.permission.DLP_GET_HIDE_STATUS,这是受限权限,不是声明了就能用------上架前要走 AGC 的 ACL 申请流程,并在隐私政策里说明用途。V哥把这条放坑里单独讲。
三、动手:给余额页加一层"有人看就蒙上"
V哥的示例是账单详情页,策略选了最省心的一种:感知交给系统,响应就是拉蒙层。完整思路是"进页面查快照 + 订阅增量,收 HIDE 拉蒙层,出页面释放":
typescript
// BalancePage.ets ------ 敏感页防窥接入:订阅状态 + 拉系统蒙层
// 前提:module.json5 已声明 ohos.permission.DLP_GET_HIDE_STATUS(受限权限)
import { dlpAntiPeep } from '@kit.DeviceSecurityKit';
import { window } from '@kit.ArkUI';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct BalancePage {
@State masked: boolean = false;
private watching: boolean = false;
aboutToAppear(): void {
// V哥的习惯:先查快照拿"现在",再订阅拿"变化"------回调只报增量,不报存量
this.applyStatus(dlpAntiPeep.getDlpAntiPeepInfo());
dlpAntiPeep.on('dlpAntiPeep', (status: dlpAntiPeep.DlpAntiPeepStatus) => {
this.applyStatus(status);
});
this.watching = true;
}
aboutToDisappear(): void {
// 离开敏感页必须释放订阅;蒙层由系统在状态恢复后自动撤
dlpAntiPeep.off('dlpAntiPeep');
this.watching = false;
}
private applyStatus(status: dlpAntiPeep.DlpAntiPeepStatus): void {
this.masked = (status === dlpAntiPeep.DlpAntiPeepStatus.HIDE);
if (this.masked) {
this.maskThisWindow(); // 被窥视:拉系统级蒙层
}
}
private async maskThisWindow(): Promise<void> {
try {
// 参数是窗口 id,从窗口模块取当前页所属窗口(取值细节以官方 SDK 为准)
const win = await window.getLastWindow(getContext(this) as common.UIAbilityContext);
await dlpAntiPeep.setAntiPeepMaskLayer(win.getWindowProperties().id);
} catch (e) {
// 1020600001~4 是窗口未就绪类错误:页面刚切入时可能触发,做有限重试
console.error(`setAntiPeepMaskLayer failed: ${(e as Error).message}`);
}
}
}
三个写法上的讲究:
① 快照和订阅要成对出现。 on 回调只在状态变化时触发,用户进页面时恰好已经处于 HIDE(比如窥视者早就在旁边),没有快照兜底就漏了第一次。getDlpAntiPeepInfo() 就是这个兜底。
② 释放点写死在 aboutToDisappear。 订阅泄漏的后果不是内存问题,是语义问题:用户已经退出余额页,蒙层逻辑还在响应 HIDE,把别的页面也蒙了。防窥是页面级能力,生命周期就得按页面级管。
③ 窗口未就绪的错误要重试。 官方错误码 1020600001~4 都指向窗口状态异常,典型时机是页面刚切入、窗口还没就绪。V哥建议做最多三次的退避重试,别一次失败就放弃。
四、三个坑,V哥替你踩过了
坑一:受限权限当成普通权限声明完事。 DLP_GET_HIDE_STATUS 属于 ACL 受限权限,本地调试用自动签名能跑通,一到上架审核就过不去。正确顺序是:module.json5 声明(带用途文案)→ AGC 提交 ACL 申请 → 隐私政策对齐。别把申请留到提审前夜。
坑二:系统开关没开,应用干瞪眼。 防窥开关默认可能没开,应用侧 isDlpAntiPeepSwitchOn() 查到 false 时,别只弹一句"请去设置里打开"------用 requestAntiPeepOptions(context) 直接拉起系统弹窗,用户在弹窗里就能为当前应用打开开关(API 23 起)。一步引导和一步跳设置,转化率差着一个量级。
坑三:把它当百分百可靠的安防系统。 人脸感知有物理边界:人脸要在有效距离内、无遮挡、光线充足(官方约束)。所以别把资金操作安全压在蒙层上------防窥是"降低肩窥暴露面"的体验增强,指纹密码那一层照旧要上。
五、接入前自检清单
| # | 检查项 | 挂了会怎样 |
|---|---|---|
| 1 | 设备支持人脸识别且用户已录入?(12500005/12500010 有引导) | 功能静默不可用 |
| 2 | DLP_GET_HIDE_STATUS 已声明且 ACL 已申请? |
本机能跑、上架被驳 |
| 3 | 快照 + 订阅成对出现? | 进页面时的存量 HIDE 漏判 |
| 4 | 订阅在 aboutToDisappear 释放? |
蒙层响应串页 |
| 5 | 窗口未就绪错误码有重试? | 页面刚切入时蒙层偶发失败 |
| 6 | 开关未开时用 requestAntiPeepOptions 引导? |
用户找不到入口,功能白接 |
| 7 | 模拟器不报错才上传? | 801 设备不支持,模拟器测不出 |
六、V哥的收尾判断:防护边界出了屏幕
回看这套能力,V哥认为它的意义不只是多一个 Kit:HarmonyOS 的安全边界第一次从系统内部延伸到了物理环境。沙箱管数据、权限管访问、身份验证管入口,防窥管的是"屏幕和围观者之间"那段真空地带------这是以往任何应用层方案都够不着的。
代价是它把一部分信任交给了感知系统:误判会蒙住不该蒙的界面,漏判会放过窥视。所以官方才把"感知 + 响应"分层------应用可以按页面敏感度选响应强度,登录页模糊数字、相册暂停轮播、账单页直接蒙层。别一刀切,敏感度分级的活儿得应用自己做。
第一期的智感握姿那篇V哥写过:系统能感知的,应用别自己造轮子。这篇补上隐私方向的同款结论------环境感知交给系统,响应策略留给自己。
参考与出处
本文涉及的机制、接口与约束来自以下官方及开发者联盟文档:
- 如何用防窥保护给应用敏感页面加一层系统蒙层(华为开发者联盟)
- HarmonyOS 防窥保护最佳实践:基于 dlpAntiPeep 的感知---响应隐私防护方案(华为开发者联盟)
- 应用内隐私信息被窥视?防窥保护自动感知一键防护(华为 HarmonyOS SDK 官方社区转载)
- 华为官宣 HDD 分享报道(中国日报)
最后一句:密码防的是"谁知道",防窥防的是"谁看见"------感知交给系统的前置摄像头,响应留给你的业务分级,地铁上那一眼,从此一无所获。