HarmonyOS 鸿蒙Next中创建日历提醒首次失败重启后才成功

HarmonyOS 鸿蒙Next中创建日历提醒首次失败重启后才成功

// 导入必要的模块
import { calendarManager } from '@kit.CalendarKit';
import { abilityAccessCtrl, common, Permissions } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { UserPermissionHandle } from '../manager/UserPermissionHandle';

const TAG = 'CalendarDemoPage';

@Entry
@Component
struct CalendarDemoPage {
  @State hasTriedFirstRequest: boolean = false;
  @State hasCalendarPermission: boolean = false;
  @State operationResult: string = '请点击按钮创建日程';
  private calendarMgr: calendarManager.CalendarManager | null = null;
  private calendarPermissions: Permissions[] = ['ohos.permission.READ_CALENDAR', 'ohos.permission.WRITE_CALENDAR'];
  private atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();

  aboutToAppear() {
    this.getCalendarManager();
    this.checkCalendarPermission(); // 初始化时检查一次权限(仅用于初始状态显示)
  }

  // 统一的权限申请入口
  async requestCalendarPermission() {
    const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    if (!context) {
      console.error(TAG, '获取上下文失败,无法申请权限');
      this.operationResult = '获取上下文失败,无法申请权限';
      return false;
    }

    // 先检查是否已有权限
    await this.checkCalendarPermission();
    if (this.hasCalendarPermission) {
      this.operationResult = '已有日历权限,可以创建日程';
      return true;
    }

    let isGranted = false;

    if (!this.hasTriedFirstRequest) {
      // 首次授权:使用系统弹窗
      isGranted = await this.requestFirstPermission(context);
    } else {
      // 二次授权:使用半模态设置页
      isGranted = await this.requestSecondPermission(context);
    }

    // 关键修改:权限申请成功后重新初始化日历管理器
    if (isGranted) {
      this.getCalendarManager(); // 重新初始化日历管理器
      await this.checkCalendarPermission(); // 重新检查权限状态
    }

    this.hasCalendarPermission = isGranted;
    return isGranted;
  }

  // 创建日程提醒(以运动场景为例)
  async createExerciseReminder(): Promise<void> {
    // 1. 先检查并申请权限
    const hasPermission = await this.requestCalendarPermission();
    if (!hasPermission) {
      this.operationResult = '无日历权限,无法创建日程';
      return;
    }

    // 关键修改:确保日历管理器已正确初始化
    if (!this.calendarMgr) {
      this.getCalendarManager();
      if (!this.calendarMgr) {
        this.operationResult = '日历管理器初始化失败';
        return;
      }
    }

    // 2. 配置日历账户
    const calendarAccount: calendarManager.CalendarAccount = {
      name: 'ExercisePlan',
      type: calendarManager.CalendarType.LOCAL,
      displayName: '运动健康'
    };

    // 3. 创建日程参数
    // 设置开始时间为当前时间1小时后,结束时间为2.5小时后
    const startTime = Date.now() + 60 * 60 * 1000;
    const endTime = startTime + 1.5 * 60 * 60 * 1000;

    const event: calendarManager.Event = {
      title: '每日跑步训练',
      startTime: startTime,
      endTime: endTime,
      isAllDay: false,
      reminderTime: [30, 60],
      description: '目标:5公里慢跑,配速6:00/公里',
      location: {
        location: '公园跑步道',
        latitude: 0,
        longitude: 0
      },
      type: calendarManager.EventType.NORMAL,
      service: {
        type: calendarManager.ServiceType.SPORTS_EXERCISE, // 一键服务类型
        uri: 'demo://sports/start'       // 跳转DeepLink
      }
    };

    try {
      // 4. 创建日历账户并添加日程
      const calendar = await this.calendarMgr.createCalendar(calendarAccount);
      if (calendar) {
        const eventId = await calendar.addEvent(event); // 写入日程
        this.operationResult =
          `日程创建成功!ID: ${eventId}\n标题:${event.title}\n时间:${new Date(startTime).toLocaleString()}`;
        console.warn(TAG, `日程创建成功,ID: ${eventId}`);
      } else {
        this.operationResult = '创建日历账户失败';
        console.error(TAG, '创建日历账户失败');
      }
    } catch (error) {
      const err = error as BusinessError;
      this.operationResult = `创建失败: Code=${err.code}, Message=${err.message}`;
      console.error(TAG, `创建失败: Code=${err.code}, Message=${err.message}`);

      // 关键修改:如果是权限问题,重新初始化日历管理器
      if (err.code === 201 || err.message?.includes('permission') || err.message?.includes('CreateCalendar failed')) {
        console.warn(TAG, '检测到权限问题,重新初始化日历管理器');
        this.getCalendarManager();
      }
    }
  }

  build() {
    Column({ space: 20 }) {
      Text('日历日程提醒Demo')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)

      Text(this.operationResult)
        .fontSize(16)
        .textAlign(TextAlign.Center)
        .padding(10)
        .width('100%')

      Button('创建运动提醒')
        .width('80%')
        .height(50)
        .fontSize(18)
        .onClick(() => {
          this.createExerciseReminder(); // 点击仅触发创建检查,无权限则申请
        })

      Button('检查权限状态')
        .width('80%')
        .height(50)
        .fontSize(18)
        .onClick(async () => {
          await this.checkCalendarPermission(); // 主动检查时更新状态
          if (this.hasCalendarPermission) {
            this.operationResult = '已有日历权限,点击「创建运动提醒」即可创建';
          } else {
            this.operationResult = '无日历权限,请点击「创建运动提醒」申请';
          }
        })

      Button('申请日历权限')
        .width('80%')
        .height(50)
        .fontSize(18)
        .onClick(async () => {
          await this.requestCalendarPermission(); // 单独申请权限,不创建
        })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .justifyContent(FlexAlign.Center)
  }

  // 获取日历管理器
  private getCalendarManager() {
    const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
    if (context) {
      this.calendarMgr = calendarManager.getCalendarManager(context);
      console.warn(TAG, '日历管理器重新初始化完成'); // 添加日志确认重新初始化
    } else {
      console.error(TAG, '获取上下文失败');
    }
  }

  // 检查权限状态(仅用于主动检查,不覆盖申请结果)
  private async checkCalendarPermission() {
    const readGranted = await UserPermissionHandle.checkPermissionGrantStatus('ohos.permission.READ_CALENDAR');
    const writeGranted = await UserPermissionHandle.checkPermissionGrantStatus('ohos.permission.WRITE_CALENDAR');
    // 仅在主动点击「检查权限状态」时更新,避免覆盖权限申请的结果
    this.hasCalendarPermission = readGranted && writeGranted;
    console.warn(TAG, `日历权限状态: 读=${readGranted}, 写=${writeGranted}`);
  }

  // 首次权限申请(保持原逻辑)
  private async requestFirstPermission(context: common.UIAbilityContext): Promise<boolean> {
    try {
      const permissionResult = await UserPermissionHandle.requestPermissions(context, this.calendarPermissions);
      const isGrant = permissionResult[0];
      const isDialogShown = permissionResult[1];
      if (isGrant) {
        this.operationResult = '首次授权成功!请再次点击「创建运动提醒」创建日程';
        this.hasTriedFirstRequest = false;
      } else {
        this.operationResult = '用户首次拒绝权限,请点击按钮再次申请';
        this.hasTriedFirstRequest = true;
      }
      return isGrant;
    } catch (err) {
      const error = err as BusinessError;
      console.error(TAG, `首次权限申请失败: ${error.code}, ${error.message}`);
      this.operationResult = `首次权限申请失败: ${error.message}`;
      return false;
    }
  }

  // 二次权限申请(保持原逻辑)
  private async requestSecondPermission(context: common.UIAbilityContext): Promise<boolean> {
    try {
      const atManager = abilityAccessCtrl.createAtManager();
      const permissionResults = await atManager.requestPermissionOnSetting(context, this.calendarPermissions);
      const isAllGranted = permissionResults.every(
        status => status === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
      );
      if (isAllGranted) {
        this.operationResult = '二次授权成功!请再次点击「创建运动提醒」创建日程';
        this.hasTriedFirstRequest = false;
      } else {
        this.operationResult = '用户拒绝了权限申请,请稍后再次尝试';
      }
      return isAllGranted;
    } catch (err) {
      const error = err as BusinessError;
      console.error(TAG, `二次权限申请失败: ${error.code}, ${error.message}`);
      this.operationResult = `二次权限申请失败: ${error.message}`;
      return false;
    }
  }
}
import abilityAccessCtrl, { Permissions } from "@ohos.abilityAccessCtrl";
import bundleManager from "@ohos.bundle.bundleManager";
import { BusinessError } from "@kit.BasicServicesKit";
import { common, Context, Want } from "@kit.AbilityKit";


export class UserPermissionHandle {
  // 检查普通权限状态
  static async checkPermissionGrantStatus(permission: Permissions): Promise<boolean> {
    let tokenId: number = 0;
    try {
      const bundleInfo =
        await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
      tokenId = bundleInfo.appInfo.accessTokenId;
    } catch (error) {
      const err = error as BusinessError;
      console.error(`获取应用信息失败: ${err.code}, ${err.message}`);
    }
    try {
      const grantStatus = await abilityAccessCtrl.createAtManager().checkAccessToken(tokenId, permission);
      return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED;
    } catch (error) {
      const err = error as BusinessError;
      console.error(`检查权限失败: ${err.code}, ${err.message}`);
      return false;
    }
  }

  // 请求普通权限(首次弹窗)
  static requestPermissions(context: Context, permissions: Permissions[]): Promise<[boolean, boolean]> {
    return new Promise((resolve, reject) => {
      abilityAccessCtrl.createAtManager().requestPermissionsFromUser(context, permissions)
        .then((result) => {
          let isGrant = true;
          result.authResults.forEach(auth => {
            if (auth !== 0) {
              isGrant = false;
            }
          });
          const isDialogShown = result.dialogShownResults?.includes(true) ?? false;
          resolve([isGrant, isDialogShown]);
        })
        .catch((err: BusinessError) => {
          console.error(`请求权限失败: ${err.code}, ${err.message}`);
          reject(err);
        });
    });
  }

  // 跳转普通权限设置页
  static async jumpToPermission(context: common.UIAbilityContext): Promise<void> {
    try {
      // 组合标志位:应用信息 + 权限信息
      let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION |
      bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION;
      //获取信息
      const bundleInfo =
        await bundleManager.getBundleInfoForSelf(bundleFlags);
      console.warn("BundleName:", bundleInfo.name); // 直接通过 BundleInfo 获取
      console.warn("权限列表:", bundleInfo.appInfo.permissions); // 通过 ApplicationInfo 获取
      const want: Want = {
        bundleName: 'com.huawei.hmos.settings',
        abilityName: 'com.huawei.hmos.settings.MainAbility',
        // //----------------跳转到当前app的设置页面
        uri: 'application_info_entry',
        parameters: {
          pushParams: bundleInfo.name
        }
        //-------------------跳转到设置里面的应用与元服务页面
        // uri: 'application_info_entry',
        // parameters: {
        //   pushParams: {
        //     bundleName: bundleInfo.name
        //   }
        // }
        //-------------------跳转到通知和状态栏页面
        // uri: 'systemui_notification_setting',
        // parameters: {
        //   pushParams: bundleInfo.name
        // }
        //-------------------跳转到通知管理页面
        // uri: 'systemui_notification_setting',
        // parameters: {
        //   pushParams: {
        //     bundleName: bundleInfo.name
        //   }
        // }
      };
      await context.startAbility(want);
      console.warn("跳转普通权限设置成功");
    } catch (error) {
      const err = error as BusinessError;
      console.error(`跳转普通权限设置失败: ${err.code}, ${err.message}`);
    }
  }
}

更多关于HarmonyOS 鸿蒙Next中创建日历提醒首次失败重启后才成功的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

鸿蒙系统中,权限从 “拒绝” 变为 “允许” 后,系统服务(如日历服务)需要约 100-300ms 同步权限状态到应用进程。首次创建时,日历管理器基于旧的权限状态工作,导致失败;而重启后,管理器重新初始化时已拿到最新权限,因此成功。

通过延迟初始化 + 自动重试 + 强制权限检查,可避免手动重启,让首次创建即可成功。

更多关于HarmonyOS 鸿蒙Next中创建日历提醒首次失败重启后才成功的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中,首次创建日历提醒失败可能由于系统服务初始化未完成或权限验证延迟。重启设备后,系统组件重新加载,服务初始化完成,权限状态刷新,因此操作成功。该问题通常与系统底层服务启动时序有关,建议检查系统日志确认具体错误代码。

从代码分析,首次创建日历提醒失败但重启后成功的问题,主要与权限申请和日历管理器初始化时机有关。

requestCalendarPermission方法中,权限申请成功后调用了getCalendarManager()重新初始化日历管理器,这是正确的处理方式。但需要注意:

  1. 权限状态同步:权限申请成功后,系统可能需要时间同步权限状态到各个服务模块。重启应用可以确保日历服务重新加载最新权限状态。

  2. 日历管理器缓存calendarManager.getCalendarManager(context)可能返回的是缓存实例,权限变更后需要重新创建才能生效。

  3. 异步时序问题:权限申请和日历操作都是异步的,可能存在时序竞争。建议在权限申请成功后添加短暂延迟:

if (isGranted) {
  // 添加短暂延迟确保权限完全生效
  await new Promise(resolve => setTimeout(resolve, 100));
  this.getCalendarManager();
}
  1. 错误处理优化:在createExerciseReminder的catch块中检测到权限相关错误时重新初始化日历管理器是合理的兜底策略。

这个问题属于HarmonyOS权限系统的正常行为,重启应用是最直接的解决方案。在生产环境中,可以通过上述延迟重试机制来避免用户重启应用。

回到顶部