Android 13下Java自动添加日历事件失效(小米/三星设备)
小米/三星设备日历事件自动创建失效问题排查与修复
问题场景
我开发的预订应用会在用户完成预订时后台无感知自动添加日历事件,该功能在多数设备上正常运行,但在小米、三星部分设备上失效,暂未明确是系统版本还是其他原因导致。当前使用的核心代码如下:
ContentValues event = new ContentValues(); event.put(CalendarContract.Events.CALENDAR_ID, 1); event.put(CalendarContract.Events.TITLE, "title"); event.put(CalendarContract.Events.DESCRIPTION, "description"); event.put(CalendarContract.Events.EVENT_LOCATION, "location"); event.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().getID()); event.put(CalendarContract.Events.DTSTART, date + (begin * 3600000) + 1800000); event.put(CalendarContract.Events.DTEND, date + (end * 3600000) + 1800000); event.put(CalendarContract.Events.ALL_DAY, 0); // 0 per false, 1 per true event.put("eventStatus", 1); event.put(CalendarContract.Events.HAS_ALARM, 0); String eventUriString = "content://com.android.calendar/events"; Uri eventUri = requireContext().getApplicationContext() .getContentResolver() .insert(Uri.parse(eventUriString), event); long eventID = Long.parseLong(eventUri.getLastPathSegment()); int minutes=30; // add reminder for the event ContentValues reminders = new ContentValues(); reminders.put("event_id", eventID); reminders.put("method", "1"); reminders.put("minutes", minutes); String reminderUriString = "content://com.android.calendar/reminders"; getContext().getContentResolver() .insert(Uri.parse(reminderUriString), reminders);
注:我曾找到打开日历应用预填字段的实现,但需求是完全无感知的后台自动创建,无需用户介入。
核心问题分析
代码中存在多个厂商适配隐患,这是小米、三星设备失效的主要原因:
- 硬编码日历ID:默认日历ID并非所有设备都是1,小米、三星系统可能有多个内置日历,ID为1的日历可能不存在或无写入权限
- 自定义Content URI:
content://com.android.calendar/events是非官方硬编码URI,部分定制系统会修改日历Provider的URI路径 - 字段名硬编码:
"eventStatus"、"method"这类自定义字段名,厂商定制系统可能不兼容,应该使用官方常量 - 无权限校验与异常处理:小米、三星权限管控更严格,未检查权限或捕获异常会导致静默失败
- 提醒配置冲突:设置
HAS_ALARM=0后又添加提醒,逻辑矛盾,部分系统会忽略提醒请求
修复后的代码
// 先校验日历写入权限(Android 6.0+必须申请运行时权限) if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED) { return; } // 获取系统默认主日历ID,避免硬编码 long defaultCalendarId = -1; Uri calendarsUri = CalendarContract.Calendars.CONTENT_URI; String[] projection = { CalendarContract.Calendars._ID, CalendarContract.Calendars.IS_PRIMARY }; // 先查询主日历 Cursor cursor = requireContext().getContentResolver().query( calendarsUri, projection, CalendarContract.Calendars.IS_PRIMARY + "=1", null, null ); if (cursor != null) { if (cursor.moveToFirst()) { defaultCalendarId = cursor.getLong(cursor.getColumnIndexOrThrow(CalendarContract.Calendars._ID)); } cursor.close(); } // 找不到主日历则取第一个可用日历 if (defaultCalendarId == -1) { cursor = requireContext().getContentResolver().query( calendarsUri, new String[]{CalendarContract.Calendars._ID}, null, null, null ); if (cursor != null) { if (cursor.moveToFirst()) { defaultCalendarId = cursor.getLong(cursor.getColumnIndexOrThrow(CalendarContract.Calendars._ID)); } cursor.close(); } } // 无可用日历直接返回 if (defaultCalendarId == -1) { return; } // 创建事件ContentValues ContentValues event = new ContentValues(); event.put(CalendarContract.Events.CALENDAR_ID, defaultCalendarId); event.put(CalendarContract.Events.TITLE, "title"); event.put(CalendarContract.Events.DESCRIPTION, "description"); event.put(CalendarContract.Events.EVENT_LOCATION, "location"); event.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().getID()); event.put(CalendarContract.Events.DTSTART, date + (begin * 3600000) + 1800000); event.put(CalendarContract.Events.DTEND, date + (end * 3600000) + 1800000); event.put(CalendarContract.Events.ALL_DAY, 0); event.put(CalendarContract.Events.STATUS, CalendarContract.Events.STATUS_CONFIRMED); // 使用官方状态常量 event.put(CalendarContract.Events.HAS_ALARM, 1); // 要添加提醒需设置为1 // 使用官方Content URI插入事件 Uri eventUri = requireContext().getContentResolver().insert(CalendarContract.Events.CONTENT_URI, event); if (eventUri == null) { // 插入失败,可添加日志或错误处理 return; } long eventID = Long.parseLong(eventUri.getLastPathSegment()); // 添加提醒 int minutes = 30; ContentValues reminders = new ContentValues(); reminders.put(CalendarContract.Reminders.EVENT_ID, eventID); reminders.put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT); // 官方提醒方式常量 reminders.put(CalendarContract.Reminders.MINUTES, minutes); // 使用官方提醒URI插入 Uri reminderUri = requireContext().getContentResolver().insert(CalendarContract.Reminders.CONTENT_URI, reminders); if (reminderUri == null) { // 提醒添加失败,可添加日志或错误处理 }
额外适配注意事项
- 权限细节:部分小米/三星设备需要用户手动开启应用的「日历」权限,甚至在MIUI/One UI中需要开启「后台权限」或「特殊权限」才能后台写入日历
- 系统版本适配:Android 12+对日历API有更严格的限制,确保目标SDK版本≥31,避免使用废弃API
- 异常捕获:建议在插入操作外层添加
try-catch块,捕获SecurityException、IllegalArgumentException等异常,便于日志排查 - 测试覆盖:重点测试MIUI 13/14、One UI 4/5等主流定制系统版本,确认是否存在版本特定问题
内容的提问来源于stack exchange,提问作者Marco Ferrante
相关产品推荐
相关产品推荐

