实现防截屏功能

概述

手机防截屏录屏的意义,远不止于一个简单的“禁止”功能。它的核心在于在数字世界中重新建立“控制”与“边界”,其意义可以从实用保护层和深层影响层两个维度来理解。

防护层面 防护目标 具体意义与场景
个人隐私与数据安全 防止敏感信息无意泄露 保护手机屏幕上的身份证照片、家庭住址、私密对话、健康记录等。例如,在公共场所展示手机内容时,防截屏功能可以降低被旁人恶意拍照记录的风险。
高价值数字资产 保护虚拟财产与数字凭证 防止数字货币钱包地址、密钥、证券交易账户、高价值游戏道具等被截取后用于诈骗或盗取。例如,许多投资APP的交易界面会禁用截屏。
商业机密与知识产权 遏制企业内部信息外泄 在企业办公场景中,保护未公开的财务报表、战略规划、设计图纸、源代码、客户名单。确保员工无法通过截屏将机密带离公司可控环境。
数字内容版权 保障付费内容商业模式 对于付费课程、独家视频、电子书、订阅制新闻等内容,防止用户通过录屏进行无损盗版和二次传播,保护创作者和平台的收入来源。
平台秩序与信任 维护特定场景的严肃性与安全性 在线考试、金融身份验证、政府/司法远程办理业务等场景,防录屏是防止作弊、伪造和确保流程可信的关键技术环节。

在鸿蒙(HarmonyOS)系统中,防止截屏和录屏的核心原理与Android类似,都是在应用窗口层面设置安全属性,由系统图形服务在底层阻止内容捕获。鸿蒙6.0在此基础上,引入了更智能、主动的安全特性。

设置窗口隐私模式,禁止截屏或录屏。此接口适用于禁止截屏/录屏的场景。

页面级防截屏

进入页面开启隐私模式,离开页面取消,具体可参考以下步骤:

首先,在 module.json5 文件中声明需要使用 ohos.permission.PRIVACY_WINDOW 权限。

"requestPermissions": [
  {
    "name": "ohos.permission.PRIVACY_WINDOW"
  }
],

通过导航栏显示状态切换触发 onNavBarStateChange回调。进入页面时,isVisible 为 true,调用setWindowPrivacyMode 设置窗口为隐私模式。离开页面时,isVisible 为 false,设置窗口为非隐私模式。参考示例代码如下:

import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import { window } from '@kit.ArkUI';

class WindowUtils {
  static setWindowPrivacyModeInPage(context: common.UIAbilityContext, isFlag: boolean) {
    window.getLastWindow(context).then((lastWindow) => {
      lastWindow.setWindowPrivacyMode(isFlag, (err: BusinessError) => {
        const errCode: number = err.code;
        if (errCode) {
          console.error('Failed to set the window to privacy mode. Cause:' + JSON.stringify(err));
          return;
        }
        console.info('Succeeded in setting the window to privacy mode.');
      });
    })
  }
}

@Entry
@Component
struct Index {
  @State message: string = 'hello world';
  @Provide('NavPathStack') pageStack: NavPathStack = new NavPathStack();
  context = this.getUIContext();

  @Builder
  PagesMap(name: string) {
    if (name === 'Index') {
      Index();
    } else if (name === 'PageOne') {
      PageOne();
    }
  }

  build() {
    Navigation(this.pageStack) {
      Column() {
        Button('pushPath', { stateEffect: true, type: ButtonType.Capsule })
          .width('80%')
          .height(40)
          .margin(20)
          .onClick(() => {
            this.pageStack.pushPath({ name: 'PageOne' }) // Push the page info of specified NavDestination into the stack
          })
      }
    }
    .navDestination(this.PagesMap)
    .onNavBarStateChange((isVisible: boolean) => {
      // Callback triggered when navigation bar display state changes
      console.info('------>isVisible:' + isVisible)
      WindowUtils.setWindowPrivacyModeInPage(this.context.getHostContext() as common.UIAbilityContext, isVisible);
    })
  }
}

@Component
struct PageOne {
  @Consume('NavPathStack') pageStack: NavPathStack;

  build() {
    NavDestination() {
      Column() {
        Text('PageOne')
      }
    }
    .title('pageOne')
    .onBackPressed(() => {
      const popDestinationInfo = this.pageStack.pop(); // Pop the top element from the navigation stack
      return true;
    })
  }
}

代码逻辑走读:

  1. 导入模块
    • 导入了BusinessError用于错误处理。
    • 导入了commonwindow用于UI能力上下文和窗口操作。
  2. WindowUtils类
    • 定义了一个静态方法setWindowPrivacyModeInPage,用于在给定的UIAbilityContext中设置窗口的隐私模式。
    • 使用window.getLastWindow(context)获取最后一个窗口。
    • 调用lastWindow.setWindowPrivacyMode(isFlag, callback)设置窗口的隐私模式,并处理回调中的错误。
  3. Index组件
    • 使用@Entry@Component装饰器定义了一个入口组件。
    • 定义了状态变量message和提供者pageStack
    • 定义了一个PagesMap方法用于根据名称映射页面组件。
    • build方法中构建UI,包含一个按钮用于导航到PageOne
    • 使用onNavBarStateChange监听导航栏的显示状态变化,并在状态变化时调用WindowUtils.setWindowPrivacyModeInPage
  4. PageOne组件
    • 定义了一个简单的页面组件,显示“PageOne”文本。
    • 使用onBackPressed监听返回键事件,从导航栈中弹出页面。

窗口级防截屏

设置主窗口为隐私模式,参考以下步骤:

在module.json5文件中声明需要使用的ohos.permission.PRIVACY_WINDOW权限。

"requestPermissions": [
  {
    "name": "ohos.permission.PRIVACY_WINDOW"
  }
],

在EntryAbility.ets文件的onWindowStageCreate回调中设置主窗口为隐私模式,具体可参考示例代码:

import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  }

  onDestroy(): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // Main window is created, set main page for this ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
    // Get the main window
    windowStage.getMainWindow((err: BusinessError, data) => {
      let errCode: number = err.code;
      if (errCode) {
        console.error('Failed to obtain the main window. Cause: ' + JSON.stringify(err));
        return;
      }
      let windowClass: window.Window = data;
      console.info('Succeeded in obtaining the main window. Data: ' + JSON.stringify(data));
      // Set the window privacy mode
      let isPrivacyMode: boolean = true;
      try {
        windowClass.setWindowPrivacyMode(isPrivacyMode, (err: BusinessError) => {
          const errCode: number = err.code;
          if (errCode) {
            console.error('Failed to set the window to privacy mode. Cause:' + JSON.stringify(err));
            return;
          }
          console.info('Succeeded in setting the window to privacy mode.');
        });
      } catch (exception) {
        console.error('Failed to set the window to privacy mode. Cause:' + JSON.stringify(exception));
      }
    })
    windowStage.loadContent('pages/Index', (err, data) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
    });
  }

  onWindowStageDestroy(): void {
    // Main window is destroyed, release UI related resources
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground(): void {
    // Ability has brought to foreground
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground(): void {
    // Ability has back to background
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
  }
}

代码逻辑走读:

  1. 类定义与导入模块
    • 代码首先导入了多个模块,包括AbilityConstantConfigurationConstantUIAbilityhilogwindowBusinessError。这些模块提供了应用程序生命周期管理、配置常量、UI能力、日志记录和业务错误处理的功能。
  2. 类继承与方法重写
    • EntryAbility类继承自UIAbility,并重写了多个方法,如onCreateonDestroyonWindowStageCreateonWindowStageDestroyonForegroundonBackground。这些方法分别在应用程序的生命周期不同阶段被调用,用于管理应用的状态和窗口。
  3. 生命周期管理
    • onCreate方法中,设置应用程序的颜色模式,并记录日志信息。
    • onDestroy方法用于在应用程序销毁时记录日志信息。
    • onWindowStageCreate方法在窗口阶段创建时被调用,获取主窗口并尝试设置其隐私模式,同时加载应用程序内容。
    • onWindowStageDestroy方法在窗口阶段销毁时被调用,用于释放UI相关资源。
    • onForegroundonBackground方法分别在应用程序进入前台和后台时被调用,用于记录日志信息。
  4. 错误处理
    • 在获取主窗口和设置窗口隐私模式的过程中,通过检查错误码来处理可能出现的错误,确保应用程序的稳定运行。
  5. 日志记录
    • 使用hilog模块记录应用程序的生命周期事件和错误信息,便于开发者进行调试和性能分析。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐