鸿蒙学习实战之路-STG系列(10/11)-应用访问限制-解除限制

朋友们,上一篇我们学习了如何设置应用访问限制。今天这篇我们来学习如何解除应用访问限制 - 这就像是给你的应用"解除封禁"或"重新开放" o(╯□╰)o

解除应用访问限制功能可以让你解除对某些应用的访问限制,让用户恢复正常访问。这就像给孩子"解除游戏限制",让他们可以正常使用了~

今天这篇,我会手把手带你实现应用访问限制的解除功能,全程不超过5分钟(不含你测试的时间)~


一、解除应用访问限制概述

解除应用访问限制功能可以让你解除对指定应用的访问限制,让用户恢复正常访问。

1. 业务流程

应用调用解除应用访问限制接口
    ↓
系统检查应用是否有权限、用户是否授权
    ↓
如果没有权限 → 抛出错误码
如果有权限 → 解析参数
    ↓
判断传入的应用列表是否被其他应用或健康使用设备管控
    ↓
如果有被其他应用管控 → 对应应用不解除限制
如果没有被其他应用管控 → 解除限制
    ↓
返回处理结果

2. 限制和解除的对称性

🥦 西兰花警告:
同一个管控应用的限制和解除限制必须对称使用,即解除限制必须和其限制的类型匹配上!

  • 如果之前用的是禁止清单设置限制,解除时也必须用禁止清单
  • 如果之前用的是允许清单设置限制,解除时也必须用允许清单
  • 类型不匹配或之前没有做过限制,都会报参数错误

二、核心接口

解除应用访问限制的核心接口:

接口名 说明
releaseAppsRestriction(appInfo, restrictionType) 根据应用 token 数组和限制类型,解除应用访问限制

接口参数说明

  • appInfo: 应用信息对象,包含 appTokens 数组
  • restrictionType: 限制类型,可以是 BLOCKLIST_TYPEALLOWLIST_TYPE

边界场景

🥦 西兰花警告:

  1. 空列表 + 禁止清单: 如果传入的应用列表为空,限制类型为禁止清单,则不对任何应用做解除限制

  2. 空列表 + 允许清单: 如果传入的应用列表为空,限制类型为允许清单,则对除了系统内置允许清单应用、管控发起应用本身、已授权的管控应用和健康使用设备之外的所有应用做解除限制

  3. 必须对称使用: 解除限制必须和限制的类型匹配,不匹配或之前没有做过限制都是参数错误

  4. 禁止清单解除要完整: 如果之前用禁止清单方式做限制,解除时传入的应用列表必须包含所有的禁用清单应用,才可全部解除

  5. 禁止清单不能多传: 如果按禁止清单方式解除限制时,传入的应用名单里包含了不在之前限制禁用清单中的应用(或 token 无效),则为参数错误

  6. 允许清单不能多传: 如果按允许清单方式解除限制时,传入的应用名单里包含了不在之前允许清单中的应用(或 token 无效),则为参数错误


三、开发步骤

步骤 1: 导入相关模块

import { guardService, appPicker } from '@kit.ScreenTimeGuardKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';

步骤 2: 实现应用访问限制解除功能

@Entry
@Component
struct ReleaseAppsRestrictionDemo {
  @State selectedTokens: string[] = [];
  @State restrictionType: string = 'BLOCKLIST_TYPE';
  @State message: string = '';

  /**
   * 选择要解除限制的应用
   */
  async selectApps() {
    try {
      const tokens = await appPicker.startAppPicker(
        this.getUIContext().getHostContext() as common.UIAbilityContext,
        { appTokens: this.selectedTokens }
      );
      this.selectedTokens = tokens;
      this.message = `已选择 ${tokens.length} 个应用`;
      hilog.info(0x0000, 'ReleaseAppsRestrictionDemo', 
        `selected ${tokens.length} apps`);
    } catch (err) {
      const message = (err as BusinessError).message;
      const code = (err as BusinessError).code;
      this.message = `选择应用失败: ${message}`;
      hilog.error(0x0000, 'ReleaseAppsRestrictionDemo',
        `startAppPicker failed with error code: ${code}, message: ${message}`);
    }
  }

  /**
   * 解除应用访问限制
   */
  async releaseRestriction() {
    if (this.selectedTokens.length === 0) {
      this.message = '请先选择要解除限制的应用';
      return;
    }

    try {
      const appInfo: guardService.AppInfo = {
        appTokens: this.selectedTokens
      };

      const restrictionType: guardService.RestrictionType = 
        this.restrictionType === 'BLOCKLIST_TYPE' 
          ? guardService.RestrictionType.BLOCKLIST_TYPE 
          : guardService.RestrictionType.ALLOWLIST_TYPE;

      await guardService.releaseAppsRestriction(appInfo, restrictionType);
      this.message = `应用访问限制解除成功 (${this.getRestrictionTypeDesc()})`;
      hilog.info(0x0000, 'ReleaseAppsRestrictionDemo', 
        `releaseAppsRestriction succeeded with type: ${this.restrictionType}`);
    } catch (err) {
      const message = (err as BusinessError).message;
      const code = (err as BusinessError).code;
      this.message = `解除失败: ${message}`;
      hilog.error(0x0000, 'ReleaseAppsRestrictionDemo',
        `releaseAppsRestriction failed with error code: ${code}, message: ${message}`);
    }
  }

  /**
   * 获取限制类型描述
   */
  getRestrictionTypeDesc(): string {
    return this.restrictionType === 'BLOCKLIST_TYPE' ? '禁止清单' : '允许清单';
  }

  build() {
    Column() {
      Text('应用访问限制解除演示')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 40 })

      Text(this.message)
        .fontSize(14)
        .margin({ top: 20 })
        .fontColor('#666666')

      // 限制类型选择
      Text('限制类型 (必须与设置时一致)')
        .fontSize(16)
        .margin({ top: 30 })

      Row() {
        Button('禁止清单')
          .onClick(() => { this.restrictionType = 'BLOCKLIST_TYPE'; })
          .backgroundColor(this.restrictionType === 'BLOCKLIST_TYPE' ? '#007DFF' : '#F5F5F5')
          .fontColor(this.restrictionType === 'BLOCKLIST_TYPE' ? '#FFFFFF' : '#000000')
          .margin({ right: 10 })

        Button('允许清单')
          .onClick(() => { this.restrictionType = 'ALLOWLIST_TYPE'; })
          .backgroundColor(this.restrictionType === 'ALLOWLIST_TYPE' ? '#007DFF' : '#F5F5F5')
          .fontColor(this.restrictionType === 'ALLOWLIST_TYPE' ? '#FFFFFF' : '#000000')
          .margin({ left: 10 })
      }
      .margin({ top: 10 })
      .width('80%')

      // 限制类型说明
      Text(this.getRestrictionTypeDesc() + ': ' + 
        (this.restrictionType === 'BLOCKLIST_TYPE' 
          ? '解除禁止清单中应用的限制' 
          : '解除允许清单方式设置的限制'))
        .fontSize(14)
        .margin({ top: 10 })
        .fontColor('#666666')

      // 应用选择
      Text('选择要解除限制的应用')
        .fontSize(16)
        .margin({ top: 30 })

      Button('选择应用')
        .onClick(() => this.selectApps())
        .margin({ top: 10 })
        .width('80%')

      Text(`已选择 ${this.selectedTokens.length} 个应用`)
        .fontSize(14)
        .margin({ top: 5 })
        .fontColor('#666666')

      // 解除按钮
      Button('解除限制')
        .onClick(() => this.releaseRestriction())
        .margin({ top: 30 })
        .width('80%')
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

四、完整示例代码

下面是一个更完整的示例,集成了设置和解除应用访问限制的完整功能:

import { guardService, appPicker } from '@kit.ScreenTimeGuardKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
import { preferences } from '@kit.ArkData';

@Entry
@Component
struct AppsRestrictionCompleteManagement {
  // 状态变量
  @State authStatus: string = '未知';
  @State selectedTokens: string[] = [];
  @State restrictionType: string = 'BLOCKLIST_TYPE';
  @State isRestricted: boolean = false;
  @State message: string = '';
  @State currentTab: number = 0;

  // 用户首选项
  private pref: preferences.Preferences | null = null;

  async aboutToAppear() {
    // 初始化用户首选项
    let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    this.pref = await preferences.getPreferences(context, 'apps_restriction_complete_data');

    // 加载保存的状态
    await this.loadStatus();

    // 检查授权状态
    await this.checkAuthStatus();
  }

  /**
   * 加载保存的状态
   */
  async loadStatus() {
    try {
      if (this.pref) {
        const tokens = await this.pref.get('selected_tokens', '') as string;
        if (tokens) {
          this.selectedTokens = JSON.parse(tokens);
        }

        const type = await this.pref.get('restriction_type', 'BLOCKLIST_TYPE') as string;
        this.restrictionType = type;

        const restricted = await this.pref.get('is_restricted', false) as boolean;
        this.isRestricted = restricted;
      }
    } catch (err) {
      hilog.error(0x0000, 'AppsRestrictionCompleteManagement', 'loadStatus failed');
    }
  }

  /**
   * 保存状态
   */
  async saveStatus() {
    try {
      if (this.pref) {
        await this.pref.put('selected_tokens', JSON.stringify(this.selectedTokens));
        await this.pref.put('restriction_type', this.restrictionType);
        await this.pref.put('is_restricted', this.isRestricted);
        await this.pref.flush();
      }
    } catch (err) {
      hilog.error(0x0000, 'AppsRestrictionCompleteManagement', 'saveStatus failed');
    }
  }

  /**
   * 检查授权状态
   */
  async checkAuthStatus() {
    try {
      const status = await guardService.getUserAuthStatus();
      this.authStatus = status === guardService.AuthStatus.AUTHORIZED ? '已授权' : '未授权';
    } catch (err) {
      const message = (err as BusinessError).message;
      const code = (err as BusinessError).code;
      hilog.error(0x0000, 'AppsRestrictionCompleteManagement',
        `checkAuthStatus failed with error code: ${code}, message: ${message}`);
    }
  }

  /**
   * 请求用户授权
   */
  async requestAuth() {
    try {
      await guardService.requestUserAuth(
        this.getUIContext().getHostContext() as common.UIAbilityContext
      );
      await this.checkAuthStatus();
      this.message = '授权成功';
    } catch (err) {
      const message = (err as BusinessError).message;
      const code = (err as BusinessError).code;
      this.message = `授权失败: ${message}`;
      hilog.error(0x0000, 'AppsRestrictionCompleteManagement',
        `requestUserAuth failed with error code: ${code}, message: ${message}`);
    }
  }

  /**
   * 选择应用
   */
  async selectApps() {
    try {
      const tokens = await appPicker.startAppPicker(
        this.getUIContext().getHostContext() as common.UIAbilityContext,
        { appTokens: this.selectedTokens }
      );
      this.selectedTokens = tokens;
      this.message = `已选择 ${tokens.length} 个应用`;
      hilog.info(0x0000, 'AppsRestrictionCompleteManagement', 
        `selected ${tokens.length} apps`);
    } catch (err) {
      const message = (err as BusinessError).message;
      const code = (err as BusinessError).code;
      this.message = `选择应用失败: ${message}`;
      hilog.error(0x0000, 'AppsRestrictionCompleteManagement',
        `startAppPicker failed with error code: ${code}, message: ${message}`);
    }
  }

  /**
   * 设置应用访问限制
   */
  async setRestriction() {
    if (this.selectedTokens.length === 0) {
      this.message = '请先选择要限制的应用';
      return;
    }

    try {
      const appInfo: guardService.AppInfo = {
        appTokens: this.selectedTokens
      };

      const restrictionType: guardService.RestrictionType = 
        this.restrictionType === 'BLOCKLIST_TYPE' 
          ? guardService.RestrictionType.BLOCKLIST_TYPE 
          : guardService.RestrictionType.ALLOWLIST_TYPE;

      await guardService.setAppsRestriction(appInfo, restrictionType);
      this.isRestricted = true;
      await this.saveStatus();
      this.message = `应用访问限制设置成功 (${this.getRestrictionTypeDesc()})`;
      hilog.info(0x0000, 'AppsRestrictionCompleteManagement', 
        `setAppsRestriction succeeded with type: ${this.restrictionType}`);
    } catch (err) {
      const message = (err as BusinessError).message;
      const code = (err as BusinessError).code;
      this.message = `设置失败: ${message}`;
      hilog.error(0x0000, 'AppsRestrictionCompleteManagement',
        `setAppsRestriction failed with error code: ${code}, message: ${message}`);
    }
  }

  /**
   * 解除应用访问限制
   */
  async releaseRestriction() {
    if (this.selectedTokens.length === 0) {
      this.message = '请先选择要解除限制的应用';
      return;
    }

    try {
      const appInfo: guardService.AppInfo = {
        appTokens: this.selectedTokens
      };

      const restrictionType: guardService.RestrictionType = 
        this.restrictionType === 'BLOCKLIST_TYPE' 
          ? guardService.RestrictionType.BLOCKLIST_TYPE 
          : guardService.RestrictionType.ALLOWLIST_TYPE;

      await guardService.releaseAppsRestriction(appInfo, restrictionType);
      this.isRestricted = false;
      this.selectedTokens = [];
      await this.saveStatus();
      this.message = `应用访问限制解除成功 (${this.getRestrictionTypeDesc()})`;
      hilog.info(0x0000, 'AppsRestrictionCompleteManagement', 
        `releaseAppsRestriction succeeded with type: ${this.restrictionType}`);
    } catch (err) {
      const message = (err as BusinessError).message;
      const code = (err as BusinessError).code;
      this.message = `解除失败: ${message}`;
      hilog.error(0x0000, 'AppsRestrictionCompleteManagement',
        `releaseAppsRestriction failed with error code: ${code}, message: ${message}`);
    }
  }

  /**
   * 获取限制类型描述
   */
  getRestrictionTypeDesc(): string {
    return this.restrictionType === 'BLOCKLIST_TYPE' ? '禁止清单' : '允许清单';
  }

  /**
   * 获取限制类型说明
   */
  getRestrictionTypeDescDetail(): string {
    if (this.restrictionType === 'BLOCKLIST_TYPE') {
      return '禁止访问选中的应用';
    } else {
      return '只允许访问选中的应用';
    }
  }

  build() {
    Column() {
      Text('应用访问限制完整管理')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ top: 40 })

      // 消息显示
      Text(this.message)
        .fontSize(14)
        .margin({ top: 10 })
        .fontColor('#666666')

      // 授权状态
      Row() {
        Text('授权状态: ' + this.authStatus)
          .fontSize(16)
          .margin({ top: 20 })
        
        if (this.authStatus === '已授权') {
          Text('✓')
            .fontSize(16)
            .fontColor('#00FF00')
            .margin({ left: 10 })
        } else {
          Button('请求授权')
            .onClick(() => this.requestAuth())
            .margin({ left: 10 })
        }
      }
      .margin({ top: 10 })
      .width('100%')
      .padding({ left: 20, right: 20 })
      .backgroundColor('#F5F5F5')
      .borderRadius(8)

      // 限制状态
      Row() {
        Text('限制状态: ' + (this.isRestricted ? '已开启' : '未开启'))
          .fontSize(16)
          .margin({ top: 20 })
        
        if (this.isRestricted) {
          Text('✓')
            .fontSize(16)
            .fontColor('#00FF00')
            .margin({ left: 10 })
        }
      }
      .margin({ top: 10 })
      .width('100%')
      .padding({ left: 20, right: 20 })
      .backgroundColor('#F5F5F5')
      .borderRadius(8)

      // Tab 切换
      Row() {
        Button('设置限制')
          .onClick(() => { this.currentTab = 0; })
          .backgroundColor(this.currentTab === 0 ? '#007DFF' : '#F5F5F5')
          .fontColor(this.currentTab === 0 ? '#FFFFFF' : '#000000')
          .margin({ right: 10 })

        Button('解除限制')
          .onClick(() => { this.currentTab = 1; })
          .backgroundColor(this.currentTab === 1 ? '#007DFF' : '#F5F5F5')
          .fontColor(this.currentTab === 1 ? '#FFFFFF' : '#000000')
          .margin({ left: 10 })
      }
      .margin({ top: 20 })
      .width('80%')

      // Tab 内容
      if (this.currentTab === 0) {
        // 设置限制页面
        Column() {
          // 限制类型选择
          Text('限制类型')
            .fontSize(16)
            .margin({ top: 30 })

          Row() {
            Button('禁止清单')
              .onClick(() => { this.restrictionType = 'BLOCKLIST_TYPE'; })
              .backgroundColor(this.restrictionType === 'BLOCKLIST_TYPE' ? '#007DFF' : '#F5F5F5')
              .fontColor(this.restrictionType === 'BLOCKLIST_TYPE' ? '#FFFFFF' : '#000000')
              .margin({ right: 10 })

            Button('允许清单')
              .onClick(() => { this.restrictionType = 'ALLOWLIST_TYPE'; })
              .backgroundColor(this.restrictionType === 'ALLOWLIST_TYPE' ? '#007DFF' : '#F5F5F5')
              .fontColor(this.restrictionType === 'ALLOWLIST_TYPE' ? '#FFFFFF' : '#000000')
              .margin({ left: 10 })
          }
          .margin({ top: 10 })
          .width('80%')

          // 限制类型说明
          Text(this.getRestrictionTypeDesc() + ': ' + this.getRestrictionTypeDescDetail())
            .fontSize(14)
            .margin({ top: 10 })
            .fontColor('#666666')

          // 应用选择
          Text('选择要限制的应用')
            .fontSize(16)
            .margin({ top: 30 })

          Button('选择应用')
            .onClick(() => this.selectApps())
            .margin({ top: 10 })
            .width('80%')

          Text(`已选择 ${this.selectedTokens.length} 个应用`)
            .fontSize(14)
            .margin({ top: 5 })
            .fontColor('#666666')

          // 设置按钮
          Button('设置限制')
            .onClick(() => this.setRestriction())
            .margin({ top: 30 })
            .width('80%')
        }
        .width('100%')
        .height('100%')
      } else {
        // 解除限制页面
        Column() {
          // 限制类型选择
          Text('限制类型 (必须与设置时一致)')
            .fontSize(16)
            .margin({ top: 30 })

          Row() {
            Button('禁止清单')
              .onClick(() => { this.restrictionType = 'BLOCKLIST_TYPE'; })
              .backgroundColor(this.restrictionType === 'BLOCKLIST_TYPE' ? '#007DFF' : '#F5F5F5')
              .fontColor(this.restrictionType === 'BLOCKLIST_TYPE' ? '#FFFFFF' : '#000000')
              .margin({ right: 10 })

            Button('允许清单')
              .onClick(() => { this.restrictionType = 'ALLOWLIST_TYPE'; })
              .backgroundColor(this.restrictionType === 'ALLOWLIST_TYPE' ? '#007DFF' : '#F5F5F5')
              .fontColor(this.restrictionType === 'ALLOWLIST_TYPE' ? '#FFFFFF' : '#000000')
              .margin({ left: 10 })
          }
          .margin({ top: 10 })
          .width('80%')

          // 限制类型说明
          Text(this.getRestrictionTypeDesc() + ': ' + 
            (this.restrictionType === 'BLOCKLIST_TYPE' 
              ? '解除禁止清单中应用的限制' 
              : '解除允许清单方式设置的限制'))
            .fontSize(14)
            .margin({ top: 10 })
            .fontColor('#666666')

          // 应用选择
          Text('选择要解除限制的应用')
            .fontSize(16)
            .margin({ top: 30 })

          Button('选择应用')
            .onClick(() => this.selectApps())
            .margin({ top: 10 })
            .width('80%')

          Text(`已选择 ${this.selectedTokens.length} 个应用`)
            .fontSize(14)
            .margin({ top: 5 })
            .fontColor('#666666')

          // 解除按钮
          Button('解除限制')
            .onClick(() => this.releaseRestriction())
            .margin({ top: 30 })
            .width('80%')
        }
        .width('100%')
        .height('100%')
      }
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

五、注意事项

🥦 西兰花警告:

  1. 必须先获得用户授权: 解除应用访问限制之前,必须先请求用户授权,否则会报错

  2. 限制和解除必须对称:

    • 如果之前用的是禁止清单设置限制,解除时也必须用禁止清单
    • 如果之前用的是允许清单设置限制,解除时也必须用允许清单
    • 类型不匹配会报参数错误
  3. 空列表的特殊含义:

    • 空列表 + 禁止清单 = 不解除任何应用限制
    • 空列表 + 允许清单 = 解除除系统应用、管控应用外的所有应用限制
  4. 禁止清单解除要完整:

    • 如果之前用禁止清单方式做限制,解除时传入的应用列表必须包含所有的禁用清单应用
    • 不能只解除部分应用,要么全部解除,要么都不解除
  5. 不能多传应用:

    • 禁止清单解除时,传入的应用名单不能包含不在之前限制禁用清单中的应用
    • 允许清单解除时,传入的应用名单不能包含不在之前允许清单中的应用
    • 多传或 token 无效都会报参数错误
  6. 其他应用管控不受影响: 如果某个应用被其他三方应用或健康使用设备设置的策略管控,则对应应用不会解除限制

  7. 应用 Token 要保存: 应用 Token 需要保存好,用于后续的解除限制操作


六、使用场景

解除应用访问限制功能可以用于以下场景:

1. 家长控制

  • 解除游戏限制: 允许孩子在特定时间访问游戏
  • 解除社交限制: 允许孩子访问社交应用

2. 专注模式

  • 工作时间结束后解除限制: 允许访问娱乐应用
  • 休息时间解除限制: 允许访问各种应用

3. 设备共享

  • 设备收回后解除限制: 恢复正常应用访问权限
  • 临时解除限制: 允许访问特定应用

七、文档资源

官方文档链接:


八、总结

解除应用访问限制功能可以让你轻松解除对应用的访问限制,让用户恢复正常访问。

核心要点:

  1. 限制和解除必须对称使用,类型必须匹配
  2. 禁止清单解除时,必须包含所有之前限制的应用
  3. 不能多传应用,多传或 token 无效都会报错
  4. 其他应用管控的应用不会被解除
  5. 应用 Token 需要保存好,用于后续解除限制
  6. 空列表有特殊含义,要根据限制类型正确理解

解除应用访问限制就像是给应用"解除封禁",让用户恢复正常访问权限。但要记住,限制和解除必须对称使用 _


下一步行动

建议你:

  1. 先请求用户授权
  2. 测试设置限制功能
  3. 测试解除限制功能
  4. 测试对称性要求(类型不匹配的情况)
  5. 测试禁止清单的完整解除要求
  6. 测试多传应用的错误情况
  7. 结合下一篇的完整实战案例,实现一个完整的家长控制应用

记住,不教理论,只给你能跑的代码和避坑指南! _


我是盐焗西兰花,
不教理论,只给你能跑的代码和避坑指南。
下期见!🥦

Logo

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

更多推荐