鸿蒙6.0应用开发——实现防截屏功能

实现防截屏功能

概述

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

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

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

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

页面级防截屏

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

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

json 复制代码
"requestPermissions": [
  {
    "name": "ohos.permission.PRIVACY_WINDOW"
  }
],

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

typescript 复制代码
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权限。

json 复制代码
"requestPermissions": [
  {
    "name": "ohos.permission.PRIVACY_WINDOW"
  }
],

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

typescript 复制代码
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模块记录应用程序的生命周期事件和错误信息,便于开发者进行调试和性能分析。
相关推荐
高心星4 天前
鸿蒙6.0应用开发——基础动画实践案例
华为·动画·鸿蒙6.0·harmonyos6.0·水波动画·微动画·手势动画
高心星10 天前
鸿蒙6.0应用开发——Tabs滑动动画
华为·动画·tabs·鸿蒙6.0·harmonyos6.0
高心星13 天前
鸿蒙6.0应用开发——切换主题
主题·theme·皮肤·鸿蒙6.0·harmonyos6.0
高心星14 天前
鸿蒙6.0应用开发——图片合成视频
视频·图片处理·图片合成视频·鸿蒙6.0·harmonyos6.0
高心星14 天前
鸿蒙6.0应用开发——图文混排
arkui·图文混排·鸿蒙6.0·harmonyos6.0
高心星18 天前
鸿蒙6.0应用开发——定位功能的实现
华为·定位功能·鸿蒙6.0·harmonyos6.0·鸿蒙定位
高心星18 天前
鸿蒙6.0应用开发——自定义键盘的实现
键盘·自定义键盘·鸿蒙6.0·harmonyos6.0·键盘布局
高心星18 天前
鸿蒙6.0应用开发——模块化设计选型:HAP、HAR、HSP?
鸿蒙系统·har·hsp·技术选型·hap·harmonyos6.0·鸿蒙模块化设计
高心星1 个月前
鸿蒙6.0应用开发——AppStorageV2和PersistenceV2的使用
appstorage·鸿蒙6.0·harmonyos6.0·persistence
高心星2 个月前
HarmonyOS 6.0应用开发——V2装饰器@param的使用
装饰器·param·鸿蒙6.0·harmonyos6.0·v2装饰器