鸿蒙学习实战之路-STG系列(11/11)-Screen Time Guard Kit完整实战案例

朋友们,这是 STG 系列的最后一篇啦! 前面 10 篇我们学习了 Screen Time Guard Kit 的所有核心功能。今天这篇我们来做一个完整的实战案例 - 一个功能完善的家长控制应用 o(╯□╰)o

这个实战案例会整合我们学过的所有功能,包括用户授权、应用选择、守护策略管理、应用访问限制等,让你可以一次性学会如何使用 Screen Time Guard Kit 构建一个完整的屏幕时间管控应用~

今天这篇,我会手把手带你实现一个完整的家长控制应用,全程不超过5分钟(不含你测试的时间)~


一、实战案例概述

我们将实现一个功能完善的家长控制应用,包含以下核心功能:

1. 核心功能模块

功能模块 说明
用户授权管理 请求和取消用户授权,查看授权状态
守护策略管理 添加、修改、查询、删除、启动、停止守护策略
应用访问限制 设置和解除应用访问限制
策略状态监控 实时显示策略运行状态
数据持久化 保存所有配置和状态

2. 应用特点

  • 🎯 完整的屏幕时间管控功能
  • 🔐 用户授权和权限管理
  • 📱 应用选择和管控
  • ⏰ 多种时间策略类型支持
  • 🚫 黑白名单限制模式
  • 💾 数据持久化保存
  • 📊 状态实时显示

二、完整实现代码

下面是一个完整的家长控制应用实现:

1. TimeGuardExtensionAbility 实现

// ets/entryability/TimeGuardExtension.ets
import { TimeGuardExtensionAbility } from '@kit.ScreenTimeGuardKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

export default class TimeGuardExtension extends TimeGuardExtensionAbility {
  async onStart(strategyName: string): Promise<void> {
    hilog.info(0x0000, 'TimeGuardExtension', `策略启动: ${strategyName}`);
    // 在这里执行策略启动时的逻辑
    // 比如发送通知、记录日志等
  }

  async onStop(strategyName: string): Promise<void> {
    hilog.info(0x0000, 'TimeGuardExtension', `策略停止: ${strategyName}`);
    // 在这里执行策略停止时的逻辑
  }

  async onUserAuthSwitchOn(): Promise<void> {
    hilog.info(0x0000, 'TimeGuardExtension', '用户授权开启');
    // 在这里执行用户授权开启时的逻辑
  }

  async onUserAuthSwitchOff(): Promise<void> {
    hilog.info(0x0000, 'TimeGuardExtension', '用户授权关闭');
    // 在这里执行用户授权关闭时的逻辑
  }
}

2. 主页面实现

import { guardService, appPicker, TimeGuardExtensionAbility } 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 ParentalControlApp {
  // 授权状态
  @State authStatus: string = '未知';
  
  // 守护策略相关
  @State strategies: guardService.GuardStrategy[] = [];
  @State selectedStrategy: guardService.GuardStrategy | null = null;
  @State runningStrategies: Set<string> = new Set();
  
  // 应用访问限制相关
  @State restrictionTokens: string[] = [];
  @State restrictionType: string = 'BLOCKLIST_TYPE';
  @State isRestricted: boolean = false;
  
  // Tab 导航
  @State currentTab: number = 0;
  
  // 消息提示
  @State message: string = '';
  
  // 策略表单数据
  @State strategyName: string = '';
  @State strategyType: string = 'START_END_TIME_TYPE';
  @State startTime: string = '09:00';
  @State endTime: string = '18:00';
  @State duration: number = 7200;
  @State repeatDays: number[] = [1, 2, 3, 4, 5];
  @State strategyRestrictionType: string = 'BLOCKLIST_TYPE';
  
  // 用户首选项
  private pref: preferences.Preferences | null = null;

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

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

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

    // 查询所有策略
    await this.queryStrategies();
  }

  /**
   * 加载保存的状态
   */
  async loadStatus() {
    try {
      if (this.pref) {
        const running = await this.pref.get('running_strategies', '') as string;
        if (running) {
          this.runningStrategies = new Set(JSON.parse(running));
        }

        const restricted = await this.pref.get('is_restricted', false) as boolean;
        this.isRestricted = restricted;

        const tokens = await this.pref.get('restriction_tokens', '') as string;
        if (tokens) {
          this.restrictionTokens = JSON.parse(tokens);
        }

        const type = await this.pref.get('restriction_type', 'BLOCKLIST_TYPE') as string;
        this.restrictionType = type;
      }
    } catch (err) {
      hilog.error(0x0000, 'ParentalControlApp', 'loadStatus failed');
    }
  }

  /**
   * 保存状态
   */
  async saveStatus() {
    try {
      if (this.pref) {
        await this.pref.put('running_strategies', JSON.stringify([...this.runningStrategies]));
        await this.pref.put('is_restricted', this.isRestricted);
        await this.pref.put('restriction_tokens', JSON.stringify(this.restrictionTokens));
        await this.pref.put('restriction_type', this.restrictionType);
        await this.pref.flush();
      }
    } catch (err) {
      hilog.error(0x0000, 'ParentalControlApp', '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, 'ParentalControlApp',
        `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, 'ParentalControlApp',
        `requestUserAuth failed with error code: ${code}, message: ${message}`);
    }
  }

  /**
   * 取消用户授权
   */
  async revokeAuth() {
    try {
      await guardService.revokeUserAuth();
      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, 'ParentalControlApp',
        `revokeUserAuth failed with error code: ${code}, message: ${message}`);
    }
  }

  /**
   * 查询所有策略
   */
  async queryStrategies() {
    try {
      const strategies = await guardService.queryGuardStrategies();
      this.strategies = strategies;
      hilog.info(0x0000, 'ParentalControlApp', 
        `found ${strategies.length} strategies`);
    } catch (err) {
      const message = (err as BusinessError).message;
      const code = (err as BusinessError).code;
      this.message = `查询失败: ${message}`;
      hilog.error(0x0000, 'ParentalControlApp',
        `queryGuardStrategies failed with error code: ${code}, message: ${message}`);
    }
  }

  /**
   * 添加策略
   */
  async addStrategy() {
    if (!this.strategyName.trim()) {
      this.message = '请输入策略名称';
      return;
    }

    if (this.restrictionTokens.length === 0) {
      this.message = '请先选择要管控的应用';
      return;
    }

    try {
      const time: guardService.TimeStrategy = {
        type: this.strategyType as guardService.TimeStrategyType,
        startTime: this.startTime,
        endTime: this.endTime,
        duration: this.duration,
        repeat: this.repeatDays
      };

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

      const strategy: guardService.GuardStrategy = {
        name: this.strategyName,
        timeStrategy: time,
        appInfo: appInfo,
        appRestrictionType: this.strategyRestrictionType as guardService.RestrictionType
      };

      await guardService.addGuardStrategy(strategy);
      this.message = '策略添加成功';
      hilog.info(0x0000, 'ParentalControlApp', 
        `addGuardStrategy succeeded: ${this.strategyName}`);
      
      // 刷新策略列表
      await this.queryStrategies();
    } catch (err) {
      const message = (err as BusinessError).message;
      const code = (err as BusinessError).code;
      this.message = `添加失败: ${message}`;
      hilog.error(0x0000, 'ParentalControlApp',
        `addGuardStrategy failed with error code: ${code}, message: ${message}`);
    }
  }

  /**
   * 删除策略
   */
  async removeStrategy() {
    if (!this.selectedStrategy) {
      this.message = '请先选择要删除的策略';
      return;
    }

    try {
      await guardService.removeGuardStrategy(this.selectedStrategy.name);
      
      // 从运行列表中移除
      if (this.runningStrategies.has(this.selectedStrategy.name)) {
        this.runningStrategies.delete(this.selectedStrategy.name);
        await this.saveStatus();
      }
      
      this.message = '策略删除成功';
      hilog.info(0x0000, 'ParentalControlApp', 
        `removeGuardStrategy succeeded: ${this.selectedStrategy.name}`);
      
      // 刷新策略列表
      await this.queryStrategies();
      this.selectedStrategy = null;
    } catch (err) {
      const message = (err as BusinessError).message;
      const code = (err as BusinessError).code;
      this.message = `删除失败: ${message}`;
      hilog.error(0x0000, 'ParentalControlApp',
        `removeGuardStrategy failed with error code: ${code}, message: ${message}`);
    }
  }

  /**
   * 启动策略
   */
  async startStrategy() {
    if (!this.selectedStrategy) {
      this.message = '请先选择策略';
      return;
    }

    if (this.runningStrategies.has(this.selectedStrategy.name)) {
      this.message = '策略已在运行中';
      return;
    }

    try {
      await guardService.startGuardStrategy(this.selectedStrategy.name);
      this.runningStrategies.add(this.selectedStrategy.name);
      await this.saveStatus();
      this.message = '策略启动成功';
      hilog.info(0x0000, 'ParentalControlApp', 
        `startGuardStrategy succeeded: ${this.selectedStrategy.name}`);
    } catch (err) {
      const message = (err as BusinessError).message;
      const code = (err as BusinessError).code;
      this.message = `启动失败: ${message}`;
      hilog.error(0x0000, 'ParentalControlApp',
        `startGuardStrategy failed with error code: ${code}, message: ${message}`);
    }
  }

  /**
   * 停止策略
   */
  async stopStrategy() {
    if (!this.selectedStrategy) {
      this.message = '请先选择策略';
      return;
    }

    if (!this.runningStrategies.has(this.selectedStrategy.name)) {
      this.message = '策略未在运行中';
      return;
    }

    try {
      await guardService.stopGuardStrategy(this.selectedStrategy.name);
      this.runningStrategies.delete(this.selectedStrategy.name);
      await this.saveStatus();
      this.message = '策略停止成功';
      hilog.info(0x0000, 'ParentalControlApp', 
        `stopGuardStrategy succeeded: ${this.selectedStrategy.name}`);
    } catch (err) {
      const message = (err as BusinessError).message;
      const code = (err as BusinessError).code;
      this.message = `停止失败: ${message}`;
      hilog.error(0x0000, 'ParentalControlApp',
        `stopGuardStrategy failed with error code: ${code}, message: ${message}`);
    }
  }

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

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

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

      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 = `应用访问限制设置成功`;
      hilog.info(0x0000, 'ParentalControlApp', 
        `setAppsRestriction succeeded`);
    } catch (err) {
      const message = (err as BusinessError).message;
      const code = (err as BusinessError).code;
      this.message = `设置失败: ${message}`;
      hilog.error(0x0000, 'ParentalControlApp',
        `setAppsRestriction failed with error code: ${code}, message: ${message}`);
    }
  }

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

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

      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.restrictionTokens = [];
      await this.saveStatus();
      this.message = `应用访问限制解除成功`;
      hilog.info(0x0000, 'ParentalControlApp', 
        `releaseAppsRestriction succeeded`);
    } catch (err) {
      const message = (err as BusinessError).message;
      const code = (err as BusinessError).code;
      this.message = `解除失败: ${message}`;
      hilog.error(0x0000, 'ParentalControlApp',
        `releaseAppsRestriction failed with error code: ${code}, message: ${message}`);
    }
  }

  /**
   * 获取策略描述
   */
  getStrategyDesc(strategy: guardService.GuardStrategy): string {
    const time = strategy.timeStrategy;
    let desc = '';

    if (time.type === guardService.TimeStrategyType.START_END_TIME_TYPE) {
      desc = `${time.startTime}-${time.endTime}`;
    } else if (time.type === guardService.TimeStrategyType.TOTAL_DURATION_TYPE) {
      desc = `总时长: ${Math.floor((time.duration || 0) / 60)} 分钟`;
    } else if (time.type === guardService.TimeStrategyType.SHARED_DURATION_TYPE) {
      desc = `共享时长: ${Math.floor((time.duration || 0) / 60)} 分钟`;
    }

    return desc;
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Text('家长控制应用')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .height(60)
      .backgroundColor('#007DFF')
      .padding({ left: 20 })

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

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

      // Tab 导航
      Tabs({ barPosition: BarPosition.Start }) {
        TabContent() {
          this.StrategyManagementTab()
        }
        .tabBar('策略管理')

        TabContent() {
          this.AppRestrictionTab()
        }
        .tabBar('应用限制')
      }
      .barHeight(50)
      .margin({ top: 20 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }

  @Builder
  StrategyManagementTab() {
    Column() {
      // 控制按钮
      Row() {
        Button('刷新')
          .onClick(() => this.queryStrategies())
          .layoutWeight(1)
          .margin({ right: 5 })

        Button('启动')
          .onClick(() => this.startStrategy())
          .layoutWeight(1)
          .margin({ left: 5, right: 5 })

        Button('停止')
          .onClick(() => this.stopStrategy())
          .layoutWeight(1)
          .margin({ left: 5 })
      }
      .margin({ top: 20 })
      .width('90%')

      // 策略列表
      if (this.strategies.length > 0) {
        List() {
          ForEach(this.strategies, (strategy: guardService.GuardStrategy) => {
            ListItem() {
              Column() {
                Row() {
                  Text(strategy.name)
                    .fontSize(16)
                    .fontWeight(FontWeight.Bold)
                    .layoutWeight(1)

                  if (this.runningStrategies.has(strategy.name)) {
                    Text('运行中')
                      .fontSize(12)
                      .fontColor('#00FF00')
                      .backgroundColor('#E8F5E9')
                      .padding({ left: 8, right: 8 })
                      .borderRadius(4)
                  }
                }
                .width('100%')

                Text(this.getStrategyDesc(strategy))
                  .fontSize(14)
                  .fontColor('#666666')
                  .margin({ top: 5 })

                Text(`应用数量: ${strategy.appInfo.appTokens.length}`)
                  .fontSize(12)
                  .fontColor('#999999')
                  .margin({ top: 5 })
              }
              .width('100%')
              .padding(15)
              .backgroundColor(
                this.selectedStrategy?.name === strategy.name ? '#E8F5E9' : '#F5F5F5'
              )
              .borderRadius(8)
              .margin({ top: 5 })
              .onClick(() => {
                this.selectedStrategy = strategy;
              })
            }
          })
        }
        .width('100%')
        .height('50%')
        .margin({ top: 20 })
      } else {
        Text('暂无策略,点击"添加策略"创建')
          .fontSize(16)
          .margin({ top: 30 })
          .fontColor('#999999')
      }

      // 底部按钮
      Row() {
        Button('添加策略')
          .onClick(() => { this.currentTab = 1; })
          .layoutWeight(1)
          .margin({ right: 10 })

        Button('删除策略')
          .onClick(() => this.removeStrategy())
          .layoutWeight(1)
          .margin({ left: 10 })
      }
      .margin({ top: 20 })
      .width('90%')
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }

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

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

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

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

        // 控制按钮
        Row() {
          Button('设置限制')
            .onClick(() => this.setRestriction())
            .layoutWeight(1)
            .margin({ right: 10 })

          Button('解除限制')
            .onClick(() => this.releaseRestriction())
            .layoutWeight(1)
            .margin({ left: 10 })
        }
        .margin({ top: 30 })
        .width('90%')
      }
      .width('100%')
      .padding(20)
    }
  }
}

三、功能特点

这个完整的家长控制应用具有以下特点:

1. 完整的功能模块

  • ✅ 用户授权管理
  • ✅ 守护策略管理(增删改查启停)
  • ✅ 应用访问限制(设置和解除)
  • ✅ 策略状态监控
  • ✅ 数据持久化

2. 用户体验优化

  • 📱 简洁直观的界面
  • 🎯 Tab 导航切换
  • 💬 消息提示反馈
  • 📊 状态实时显示

3. 技术亮点

  • 💾 数据持久化保存
  • 🔧 完整的错误处理
  • 📝 详细的日志记录
  • 🎨 良好的代码结构

四、使用流程

使用这个家长控制应用的完整流程:

1. 首次使用
    ↓
2. 请求用户授权
    ↓
3. 选择要管控的应用
    ↓
4. 添加守护策略(选择策略类型、时间规则)
    ↓
5. 启动策略
    ↓
6. 策略开始生效
    ↓
7. 可随时查看、修改、删除、停止策略
    ↓
8. 也可以设置应用访问限制

五、注意事项

🥦 西兰花警告:

  1. 必须先获得用户授权: 使用任何功能之前,必须先请求用户授权
  2. 策略名称必须唯一: 不能有同名的策略
  3. 限制和解除必须对称: 解除限制必须和限制的类型匹配
  4. 应用 Token 要保存: 应用 Token 需要保存好,用于后续操作
  5. 启动后系统时间锁定: 策略启动后,系统时间会被锁定
  6. 管控应用不可卸载: 策略启动后,管控应用会被设置为不可卸载
  7. 数据持久化: 所有配置和状态都会保存,重启应用后不会丢失

六、扩展建议

这个家长控制应用还可以进一步扩展:

1. 功能扩展

  • 📊 使用统计和报表
  • 🔔 通知和提醒功能
  • 👤 多用户支持
  • 📅 日历视图
  • 🎮 游戏专用模式

2. UI/UX 优化

  • 🎨 更美观的界面设计
  • 📱 适配不同屏幕尺寸
  • 🌓 支持深色模式
  • 🎬 添加动画效果
  • 📝 更详细的帮助文档

3. 性能优化

  • ⚡ 数据加载优化
  • 🔄 状态同步优化
  • 💾 存储优化
  • 📊 内存优化

七、文档资源

官方文档链接:


八、总结

恭喜你! 到这里,你已经学会了 Screen Time Guard Kit 的所有核心功能,并且能够独立实现一个完整的家长控制应用了~

核心要点:

  1. 完整的用户授权管理
  2. 守护策略的增删改查启停
  3. 应用访问限制的设置和解除
  4. 数据持久化保存
  5. 良好的用户体验设计
  6. 完整的错误处理

这个实战案例整合了我们前面学过的所有功能,是一个功能完善的家长控制应用。你可以直接使用这个代码,或者在此基础上进行扩展和优化~


下一步行动

建议你:

  1. 运行这个完整的家长控制应用
  2. 测试所有功能模块
  3. 根据实际需求进行调整和优化
  4. 添加更多功能(如使用统计、通知等)
  5. 优化 UI/UX 设计
  6. 发布到应用商店

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


STG 系列总结

恭喜你完成了整个 STG 系列的学习! 让我们来回顾一下这 11 篇文章的内容:

  1. 屏幕时间守护服务全攻略 - STG 概述和核心功能介绍
  2. 配置签名与权限申请 - 开发前的准备工作
  3. 用户授权管理详解 - 请求和取消用户授权
  4. 应用选择页功能详解 - 拉起应用选择页
  5. 守护策略管理-添加与修改策略 - 策略的创建和修改
  6. 守护策略管理-查询与删除策略 - 策略的查询和删除
  7. 守护策略管理-启动与停止策略 - 策略的启动和停止
  8. 守护策略管理完整实战 - 策略管理的完整示例
  9. 应用访问限制-设置限制 - 设置应用访问限制
  10. 应用访问限制-解除限制 - 解除应用访问限制
  11. Screen Time Guard Kit完整实战案例 - 完整的家长控制应用

通过这 11 篇文章,你已经掌握了 Screen Time Guard Kit 的所有核心功能,可以独立开发各种屏幕时间管控应用了~


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

Logo

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

更多推荐