HarmonyOS应用《左右相册》 隐私防窥实战:用 dlpAntiPeep 让相册“感知“正在被偷瞄

适用环境:HarmonyOS Next(API 20+)|ArkTS / ArkUI|仅 Phone 真机支持

一、功能描述

打开相册翻看照片,最尴尬的场景莫过于:你正低头看手机,旁边的人悄悄凑过来瞄了一眼------工资单、聊天记录、私密照片就这样暴露了。

传统做法是手动锁屏、切后台,体验很割裂。HarmonyOS 提供了系统级的防窥保护能力,本文介绍如何把这一能力接入到一个自研相册应用里,实现:

  • 自动感知 :系统通过前置摄像头 + 人脸识别判断"当前注视屏幕的是不是机主",当检测到他人正在窥屏时,实时通知应用;
  • 即时遮蔽 :应用收到 HIDE 状态后,全屏蒙层盖住所有照片/视频内容,只露出一句"检测到他人窥屏,内容已隐藏";
  • 自动恢复 :他人离开、机主回到屏幕前,收到 PASS 状态后自动撤掉蒙层,内容恢复可见;
  • 优雅降级:设备不支持、系统开关没开时,功能静默关闭,绝不影响正常浏览;
  • 多页面复用:照片页与视频页各自独立订阅,互不干扰。

整个能力零配置成本 (用户无需在 App 内设置),且完全由系统开关控制------用户可以在"设置 > 隐私与安全 > 防窥保护"里一键开关,App 只负责"响应",不负责"开启",边界非常清晰。

二、原理介绍

2.1 整体架构:系统感知 → 应用响应

防窥保护属于 Device Security Kit(设备安全服务) ,核心是 @kit.DeviceSecurityKit 下的 dlpAntiPeep 模块。它的分工是:

复制代码
┌─────────────────────────────────────────────────┐
│  系统侧(Device Security Kit)                     │
│  前置摄像头 + 人脸 + 姿态融合                        │
│  → 判断当前是否"他人注视屏幕"                         │
│  → 产出 DlpAntiPeepStatus(HIDE / PASS)           │
└──────────────────────────┬──────────────────────┘
                           │ 事件回调 on('dlpAntiPeep')
                           ▼
┌─────────────────────────────────────────────────┐
│  应用侧(ArkUI)                                   │
│  订阅状态 → 更新 @State isPeeking                 │
│  → 条件渲染全屏遮罩层                                │
└─────────────────────────────────────────────────┘

关键点:感知逻辑完全在系统侧,App 拿不到任何原始图像/生物特征数据,只收到一个"该不该隐藏"的布尔语义状态。这既保证了隐私(数据不出设备),也保证了安全(App 无法伪造或绕过)。

2.2 核心 API 一览

API 作用 说明
dlpAntiPeep.isDlpAntiPeepSwitchOn() 查询系统防窥开关是否打开 返回 Promise<boolean>,未开启则不订阅
dlpAntiPeep.on('dlpAntiPeep', cb) 订阅防窥状态变化 cb: (status: DlpAntiPeepStatus) => void
dlpAntiPeep.off('dlpAntiPeep', cb) 取消订阅 必须在页面销毁时调用,防止野回调
DlpAntiPeepStatus.HIDE 需要隐藏内容 他人正在窥屏
DlpAntiPeepStatus.PASS 可以显示内容 当前只有机主
canIUse('SystemCapability.Security.DlpAntiPeep') 设备能力探测 调用前务必先检查

2.3 两个状态值的语义

防窥只给两个状态,语义非常直白:

  • HIDE(隐藏):系统判断当前屏幕被他人注视,应用应当隐藏敏感内容;
  • PASS(通过):当前是机主本人在看,应用可以正常显示内容。

⚠️ 注意:它不是"检测到人脸"就触发,而是"检测到非机主 的人在窥屏"才触发 HIDE。机主自己看着是 PASS,不会误触发。

2.4 前置条件

  1. 系统开关:用户必须在系统"设置 > 隐私与安全 > 防窥保护"中开启该功能,且设备已录入人脸;
  2. 权限声明module.json5 中声明 ohos.permission.DLP_GET_HIDE_STATUS
  3. 设备能力canIUse('SystemCapability.Security.DlpAntiPeep') 为真;
  4. API 版本:API 20+,仅 Phone 形态支持。

下面进入实战。

三、实战实现

3.1 声明权限

entry/src/main/module.json5requestPermissions 中添加:

json5 复制代码
{
  "name": "ohos.permission.DLP_GET_HIDE_STATUS",
  "reason": "$string:dlp_anti_peep_reason",
  "usedScene": {
    "abilities": ["EntryAbility"],
    "when": "inuse"
  }
}

该权限为系统授予(system_grant)+ 用户授权语义,reason 字段用于上架说明用途,when: "inuse" 表示仅在使用期间需要。

3.2 状态定义与回调

在页面组件中定义一个 @State 布尔量,并用箭头函数属性 保存回调引用(on/off 必须用同一个引用,匿名函数会导致无法取消订阅):

typescript 复制代码
import { dlpAntiPeep } from '@kit.DeviceSecurityKit';

@State private isPeeking: boolean = false;

// 回调必须存成成员属性,on/off 使用同一引用
private antiPeepCallback = (status: dlpAntiPeep.DlpAntiPeepStatus): void => {
  if (status === dlpAntiPeep.DlpAntiPeepStatus.HIDE) {
    this.isPeeking = true;   // 他人窥屏 → 显示遮罩
  } else {
    this.isPeeking = false;  // 机主在 → 撤掉遮罩
  }
};

⚠️ 为什么必须存成员属性?dlpAntiPeep.off('dlpAntiPeep', cb) 要求传入与 on完全相同的回调引用 。如果写成 on('dlpAntiPeep', () => {...}),你手里根本没有这个引用,off 时就取消不掉,页面销毁后回调仍会被调用(此时修改已销毁组件的 @State,轻则无效,重则报错)。

3.3 启动监听:先查开关,再订阅

订阅之前先确认系统开关是打开的,避免无谓监听:

typescript 复制代码
private startAntiPeep() {
  try {
    dlpAntiPeep.isDlpAntiPeepSwitchOn().then((isOpen: boolean) => {
      if (isOpen) {
        dlpAntiPeep.on('dlpAntiPeep', this.antiPeepCallback);
      }
    }).catch((err: Error) => {
      // 开关未开 / 设备不支持,静默降级
      Logger.error('[PhotoBrowserPage] 检查防窥开关失败:', err.message);
    });
  } catch (e) {
    Logger.error('[PhotoBrowserPage] 启动防窥监听失败:', e);
  }
}

调用时机放在 aboutToAppear 末尾(此时页面状态已就绪):

typescript 复制代码
aboutToAppear() {
  // ... 加载数据、初始化避让区等 ...
  this.startAntiPeep();
}

3.4 解除监听:与启动对称

页面销毁时必须取消订阅 ,这是防窥(以及一切 on 类监听)最容易漏的一步:

typescript 复制代码
aboutToDisappear() {
  try {
    dlpAntiPeep.off('dlpAntiPeep', this.antiPeepCallback);
  } catch (e) {
    Logger.error('[PhotoBrowserPage] 取消防窥监听失败:', e);
  }
  // ... 其他清理(窗口监听、分享监听等) ...
}

3.5 条件渲染遮罩层

isPeeking@State,状态一变 ArkUI 自动重算 build()。在页面 Stack 的最上层放一个条件分支:

typescript 复制代码
// 防窥保护蒙层(放在 Stack 最后 = 最高层级)
if (this.isPeeking) {
  Column() {
    Image($r('app.media.ic_setting_privacy'))
      .width(48)
      .height(48)
      .fillColor('#88FFFFFF')
    Text('检测到他人窥屏,内容已隐藏')
      .fontSize(14)
      .fontColor('#CCFFFFFF')
      .margin({ top: 12 })
  }
  .width('100%')
  .height('100%')
  .backgroundColor('#E6000000')   // 90% 不透明度黑底,彻底遮住下层
  .justifyContent(FlexAlign.Center)
}

几个细节:

  1. 层级 :放在 Stack 子节点的最后,天然压在所有照片/视频之上;
  2. 不透明度#E6000000(约 90% 黑)而不是纯黑,隐约透出"内容还在",避免用户误以为 App 卡死;
  3. 提示文案:明确告知"内容已隐藏"而非"出错了",减少误解;
  4. 不可点击穿透 :遮罩本身没有 onClick,也不会拦截下层手势------窥屏期间用户点什么都没反应,等 PASS 后自动恢复。

3.6 设置页:状态展示 + 引导开启

除了被动响应,还在设置页做了两个增强:

展示当前系统开关状态

typescript 复制代码
private async checkAntiPeepStatus(): Promise<void> {
  try {
    this.antiPeepSwitchOn = await dlpAntiPeep.isDlpAntiPeepSwitchOn();
  } catch (e) {
    this.antiPeepSwitchOn = false;
  }
}
typescript 复制代码
// 开关开启时绿色对勾,未开启时黄色警示
if (this.antiPeepSwitchOn) {
  Row({ space: 4 }) {
    SymbolGlyph($r('sys.symbol.checkmark_circle')).fontSize(16).fontColor(['#34C759'])
    Text('防窥保护已开启').fontSize(14).fontColor('#34C759')
  }
} else {
  Row({ space: 4 }) {
    SymbolGlyph($r('sys.symbol.exclamationmark_triangle')).fontSize(16).fontColor(['#FF9500'])
    Text('防窥保护未开启').fontSize(14).fontColor(['#FF9500'])
  }
}

一键跳转到系统隐私设置(用户点了才跳,不主动打扰):

typescript 复制代码
private async openPrivacySettings(): Promise<void> {
  try {
    const ctx = getContext(this) as common.UIAbilityContext;
    await ctx.startAbility({
      bundleName: 'com.huawei.hmos.settings',
      abilityName: 'com.huawei.hmos.settings.MainAbility',
      uri: 'privacy_settings'
    }).catch((err: Error) => {
      Logger.error('[SettingsPage] 打开隐私设置失败:', err.message);
      promptAction.showToast({ message: '无法打开系统设置' });
    });
  } catch (e) {
    promptAction.showToast({ message: '无法打开系统设置' });
  }
}

说明:API 23 起官方提供了 dlpAntiPeep.requestAntiPeepOptions(context) 可拉起系统防窥设置弹窗,体验更顺;这里用 startAbility 跳转"隐私与安全"是更通用的兜底方案,低版本也能用。

四、工程化细节

4.1 多页面复用,各自管理

本应用照片页(PhotoBrowserPage)和视频页(VideoListView)都需要防窥,采用各页面独立订阅而非全局单例:

页面 订阅时机 解除时机 遮罩内容
PhotoBrowserPage aboutToAppear aboutToDisappear 照片遮罩
VideoListView aboutToAppear aboutToDisappear 视频遮罩(同时暂停所有视频)

为什么不用全局单例?防窥遮罩是页面级 UI(蒙层盖在哪个页面上),放在各自页面里,渲染与生命周期天然对齐;全局单例反而要额外管理"当前哪个页面可见"。

4.2 降级策略清单

场景 行为
canIUse 为 false(设备不支持) 不订阅,功能静默关闭
isDlpAntiPeepSwitchOn() 返回 false 不订阅,设置页提示"未开启"
on() 抛异常 catch 住,记日志,不影响浏览
未录入人脸 系统开关本身开不了,同上降级

4.3 常见坑

原因 解法
取消订阅无效,销毁后仍回调 回调用了匿名函数,off 拿不到同一引用 回调定义为成员属性
模拟器上收不到任何事件 防窥依赖前置摄像头 + 人脸识别,模拟器无此能力 真机调试,开发期可忽略
遮罩一闪而过 把"检测到人脸"当 HIDE 了 只有 HIDE 状态才遮,PASS
设置页状态与实际不符 用户从系统设置改完回来没刷新 页面 onForeground 时重新 checkAntiPeepStatus()

五、总结

防窥保护的接入模式可以概括为四步,任何敏感页面都能照搬

复制代码
aboutToAppear
  ↓
isDlpAntiPeepSwitchOn 查系统开关
  ↓ (开)
on('dlpAntiPeep', 成员回调) 开始监听
  ↓
HIDE → isPeeking=true → 条件渲染遮罩
PASS → isPeeking=false → 撤掉遮罩
  ↓
aboutToDisappear
  ↓
off('dlpAntiPeep', 同一回调) 停止监听
技术点 实现方式 关键 API
能力探测 系统能力检查 canIUse('SystemCapability.Security.DlpAntiPeep')
开关查询 异步 Promise dlpAntiPeep.isDlpAntiPeepSwitchOn()
状态监听 成员回调 + 事件订阅 dlpAntiPeep.on/off('dlpAntiPeep')
内容遮蔽 @State 驱动条件渲染 if (this.isPeeking) 全屏蒙层
引导开启 跳转系统设置 startAbility / requestAntiPeepOptions

核心收获

系统感知,应用响应 ------生物特征数据不出设备,App 只消费一个布尔语义状态;

引用一致性 ------on/off 必须同一回调引用,成员属性是 ArkTS 下的标准解法;

静默降级 ------不支持/未开启时零影响,功能"能开则开";

页面级生命周期 ------遮罩与订阅都绑定页面,aboutToDisappear 必须 off


技术栈 :HarmonyOS Next | ArkTS | ArkUI | DeviceSecurityKit | dlpAntiPeep

关键词:HarmonyOS | 防窥保护 | 隐私安全 | dlpAntiPeep | 人脸识别 | 状态订阅

如果这篇文章对你有帮助,欢迎点赞收藏。有问题可以在评论区交流~

相关推荐
贾伟康4 小时前
【HarmonyOS 7新能力|008】互动卡片入门实战:从能力边界到最小可运行链路
harmonyos·arkts·arkui·harmonyos 7·互动卡片
●VON5 小时前
Flutter 鸿蒙插件适配实战:用 clipboard_watcher 0.3.0 监听剪贴板变化
flutter·华为·harmonyos
昇腾知识体系6 小时前
CANN 安装升级避坑:version.cfg 查版本、银河麒麟找不到驱动、nnrt --version 无输出排查
人工智能·华为·知识图谱
贾伟康7 小时前
【HarmonyOS 7新能力|005】沉浸光感入门实战:从能力边界到最小可运行链路
harmonyos·arkts·arkui·harmonyos 7·交互动效
心态还需努力呀13 小时前
把 Flameshot 编译进鸿蒙 PC:一次 Qt C++ 截图工具的源码级移植实战
c++·qt·harmonyos
心态还需努力呀13 小时前
把 Zettlr 搬上鸿蒙 PC:一次 Electron 应用移植实战
华为·electron·harmonyos
OH_TPC13 小时前
HarmonyOS APP开发---“心动卡“社交匹配App,需要用到这个库
华为·harmonyos·鸿蒙
●VON14 小时前
Flutter 鸿蒙插件适配实战:用 is_lock_screen 2.0.0 判断真实锁屏状态
flutter·华为·harmonyos
Sunny_G14 小时前
鸿蒙 Markdown 编辑器表格所见即所得:七个版本的渲染重构(CodeMirror Decoration 实战)
ai编程·harmonyos