ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Android 插入日程到系统日历要注意的3点:TaoToken 统一 Key 配置与 CalendarContract CRUD 验证

Android 插入日程到系统日历要注意的3点:TaoToken 统一 Key 配置与 CalendarContract CRUD 验证 1. 为什么 Android 写系统日历总踩坑三个真实场景Android 通过 CalendarContract 往系统日历插入日程看起来只是 ContentResolver 的一次 insert但真正落地到不同厂商 ROM 上问题会集中爆发。我见过最多的三类反馈日程插进去了但日历里看不到、看得到却没有删除按钮、重复日程时间错乱或者提醒不生效。这些都不是代码写错而是对 CalendarContract 的账户权限模型、时间字段约束、多表关联更新理解不到位。这篇聚焦三个关键注意点第一插入前必须找到拥有最高权限的日历账户否则日程归属错误会导致无法删除第二DTEND 和 DURATION 不能同时设置重复与非重复事件要分开处理第三更新日程时提醒表要单独 update因为 Events 和 Reminders 是两张不同的表、两个不同的 Uri。同时为了让 AI 编码工具Cline、Claude Code、CC Switch 等在生成这些 CRUD 代码时保持一致的模型通道我会把 TaoToken 统一 Key 的配置骨架一起给出。这样你在本地调试 CalendarContract 的同时AI 侧调用也走同一条 API 通道减少环境切换带来的变量。适合正在做日程同步、待办提醒、企业日历集成的 Android 开发者。2. TaoToken 前置统一 Key 与 API 通道准备TaoToken 在这里的角色是给 AI 编码工具提供统一的模型调用入口。你不需要在每个工具里分别填不同厂商的 Key而是拿一个统一 Key配置到 Cline、Claude Code、CC Switch 的配置文件里即可。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 这个地址不加 UTM 参数。操作路径很直接登录后进入控制台创建 API Key然后按工具要求填入 base_url 和 api_key。模型对话调试可以用 https://taotoken.net/api-keys 管理密钥接入文档在 https://taotoken.net/doc 查看字段说明。如果你要长期跑编码 Agent建议直接看 Coding Plan 页面避免按次调用带来的额度波动。注意TaoToken 只作为模型 API 通道不替代 Android Studio 或编辑器本身。CalendarContract 的代码仍然在你的工程里编译运行。拿到 Key 之后先别急着写日历代码把 AI 工具的配置跑通后面生成 CRUD 片段时才能稳定复现。3. 可复制配置settings.json / config.toml 与工具片段下面给出三套配置骨架按你使用的工具选一套即可。核心字段都是 base_url 指向 https://taotoken.net/api api_key 填你创建的统一 Key。3.1 Claude Code settings.json 骨架{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的统一Key }, model: claude-sonnet-4-20250514, permissions: { allow: [Read, Edit, Bash] } }把这段放到项目根目录的 .claude/settings.json 或用户级配置里。ANTHROPIC_BASE_URL 决定请求走哪条通道ANTHROPIC_API_KEY 就是 TaoToken 控制台里创建的那把 Key。改完重启 Claude Code用/status确认通道生效。3.2 Cline config 片段Cline 在 VS Code 设置里选择 OpenAI Compatible 或 Anthropic 模式然后填{ cline.apiProvider: openai, cline.openAiBaseUrl: https://taotoken.net/api, cline.openAiApiKey: sk-你的统一Key, cline.openAiModelId: claude-sonnet-4-20250514 }如果你用的是 Anthropic 原生协议把 baseUrl 换成 https://taotoken.net/api 并选择 Anthropic 提供商即可。保存后新建一个对话问一句 返回当前配置的模型名能正常回复就说明通道通了。3.3 CC Switch config.toml 骨架[[providers]] name taotoken base_url https://taotoken.net/api api_key sk-你的统一Key model claude-sonnet-4-20250514 protocol anthropicCC Switch 的作用是在多个通道间切换把 TaoToken 作为一个 provider 写进去之后切换只改 name 字段。配置完成后用cc-switch list确认 provider 已加载。3.4 CalendarContract 插入日程的核心代码配置跑通后让 AI 工具生成或你自己写下面这段。注意三个关键点都体现在代码里。// 第一步查询日历账户按权限升序取最后一个最高权限 String[] projection new String[]{ CalendarContract.Calendars._ID, CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, CalendarContract.Calendars.ACCOUNT_NAME }; Cursor userCursor getContentResolver().query( CalendarContract.Calendars.CONTENT_URI, projection, null, null, CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL ASC ); long calId -1; if (userCursor ! null userCursor.getCount() 0) { userCursor.moveToLast(); // 最高权限账户 calId userCursor.getLong( userCursor.getColumnIndex(CalendarContract.Calendars._ID)); } if (userCursor ! null) userCursor.close(); // 第二步构造事件非重复事件只设 DTENDDURATION 置 null ContentValues event new ContentValues(); event.put(CalendarContract.Events.CALENDAR_ID, calId); event.put(CalendarContract.Events.TITLE, 项目评审); event.put(CalendarContract.Events.DTSTART, startMillis); event.put(CalendarContract.Events.DTEND, endMillis); event.put(CalendarContract.Events.DURATION, (byte[]) null); event.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().getID()); Uri eventUri getContentResolver().insert( CalendarContract.Events.CONTENT_URI, event); long eventId Long.parseLong(eventUri.getLastPathSegment()); // 第三步单独插入提醒Reminders 是另一张表 ContentValues reminder new ContentValues(); reminder.put(CalendarContract.Reminders.EVENT_ID, eventId); reminder.put(CalendarContract.Reminders.MINUTES, 10); reminder.put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT); getContentResolver().insert(CalendarContract.Reminders.CONTENT_URI, reminder);重复事件则相反DURATION 填值DTEND 置 null。这个约束在 CalendarContract 里是硬性的同时设置会导致插入失败或时间异常。4. 验证请求插入、查询、更新、删除四步动作写完代码别只看编译通过要按 CRUD 四步逐一验证。下面给出每步的验证动作和预期结果。4.1 插入验证调用上面的插入逻辑后立刻用查询确认Cursor c getContentResolver().query( CalendarContract.Events.CONTENT_URI, new String[]{CalendarContract.Events._ID, CalendarContract.Events.TITLE}, CalendarContract.Events._ID ?, new String[]{String.valueOf(eventId)}, null); if (c ! null c.moveToFirst()) { Log.d(CalendarTest, 插入成功: c.getString(1)); } if (c ! null) c.close();预期结果日志打印出日程标题系统日历 App 里能看到该日程并且长按有删除按钮。如果没有删除按钮说明 calId 取错了账户回到第一步检查权限排序。4.2 查询验证按时间范围查询确认日程落在正确的时间段long startRange System.currentTimeMillis() - 86400000L; long endRange System.currentTimeMillis() 86400000L; Cursor c getContentResolver().query( CalendarContract.Events.CONTENT_URI, null, CalendarContract.Events.DTSTART ? AND CalendarContract.Events.DTSTART ?, new String[]{String.valueOf(startRange), String.valueOf(endRange)}, CalendarContract.Events.DTSTART ASC); Log.d(CalendarTest, 范围内日程数: (c ! null ? c.getCount() : 0)); if (c ! null) c.close();4.3 更新验证更新日程本身和提醒要分两次调用// 更新日程 ContentValues updateEvent new ContentValues(); updateEvent.put(CalendarContract.Events.TITLE, 项目评审已改期); getContentResolver().update( CalendarContract.Events.CONTENT_URI, updateEvent, CalendarContract.Events._ID ?, new String[]{String.valueOf(eventId)}); // 更新提醒注意 Uri 是 Reminders ContentValues updateReminder new ContentValues(); updateReminder.put(CalendarContract.Reminders.MINUTES, 30); getContentResolver().update( CalendarContract.Reminders.CONTENT_URI, updateReminder, CalendarContract.Reminders.EVENT_ID ?, new String[]{String.valueOf(eventId)});只更新 Events 不更新 Reminders是提醒时间不生效的最常见原因。4.4 删除验证int rows getContentResolver().delete( CalendarContract.Events.CONTENT_URI, CalendarContract.Events._ID ?, new String[]{String.valueOf(eventId)}); Log.d(CalendarTest, 删除行数: rows);删除 Events 后关联的 Reminders 会由系统级联清理但前提是日程归属的账户权限正确。如果删除返回 0 行多半还是账户权限问题。5. 本篇常见错排查报错路径与定位方法下面按报错现象归类给出定位路径。现象可能原因排查动作插入成功但日历看不到calId 取了低权限账户检查权限排序是否 ASC 且 moveToLast日程无删除按钮账户非最高权限打印 ACCOUNT_NAME 和 ACCESS_LEVEL插入抛 IllegalArgumentExceptionDTEND 与 DURATION 同时设置非重复置 DURATION 为 null重复日程时间错乱重复事件误设 DTEND重复事件只设 DURATION提醒不生效只更新了 Events 表单独 update Reminders Uri更新返回 0 行_id 不匹配或账户无权限先查询确认 _id 存在权限拒绝 SecurityException未申请日历读写权限检查 READ/WRITE_CALENDAR权限部分补充一句Android 6.0 以上要动态申请 READ_CALENDAR 和 WRITE_CALENDARAndroid 10 以上还要注意分区存储对日历的影响但 CalendarContract 本身走的是 ContentProvider不受 Scoped Storage 直接限制。如果 AI 工具生成的代码报错先确认 TaoToken 通道是否正常。可以在模型对话页面发一条测试消息确认返回正常后再排查代码。通道问题和代码问题要分开定位否则容易互相干扰。6. 语义一致收尾把配置和 CRUD 串成一条线三个注意点其实是一条线账户权限决定日程归属时间字段约束决定事件类型多表更新决定提醒是否生效。任何一环出错表现都是日历不对但根因完全不同。我的建议是先把 TaoToken 的 Key 配好让 AI 工具稳定生成代码然后按插入、查询、更新、删除四步逐一验证每步都打印日志确认行数和 _id。如果你在排障阶段反复卡在接入配置上直接去 API Keys 页面重新确认密钥和 base_url如果是要长期跑编码 AgentCoding Plan 更适合单纯验证模型是否通用模型对话页面发一条消息最快。接入文档里有完整的字段说明遇到配置字段不确定时对照一下即可。
返回列表