Android创建日历事件及提醒时触发SQLiteException异常求助
解决Android日历提醒插入的SQLiteException问题
看起来你遇到的是典型的跨设备兼容性问题,这种部分设备正常、部分崩溃的情况,通常和硬编码值、权限处理或者字段使用不当有关。我来帮你拆解问题并给出修复方案:
核心问题分析
你的代码里有几个容易触发跨设备异常的点:
1. 硬编码日历ID(CALENDAR_ID=1)
不同设备的默认日历ID不一定是1,有些设备的主日历可能是2或者其他值,硬编码直接插入会导致SQLite写入失败,因为目标日历不存在。
2. 时区字段使用错误
你这里写的CalendarContract.Calendars.CALENDAR_TIME_ZONE是字段名常量(字符串值是calendar_timezone),而不是实际的时区ID。插入事件时需要传入真实的时区值(比如Asia/Shanghai),否则系统无法解析时区,导致事件插入失败,后续插入提醒自然也会抛异常。
3. 权限处理不完整
虽然你声明了SecurityException捕获,但Android 6.0+需要动态申请WRITE_CALENDAR和READ_CALENDAR权限,部分设备可能没有授予权限,导致数据库写入被拒绝。
4. 事件插入后的空值判断缺失
如果事件插入失败(比如权限不足、日历ID错误),uri会是null,这时候调用uri.getLastPathSegment()会直接抛出空指针异常,进而引发后续的SQLiteException。
修复后的完整代码
package com.medikoe.connect.androidmedikoeconnect.utilities; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; import android.content.pm.PackageManager; import android.net.Uri; import android.provider.CalendarContract; import android.widget.Toast; import java.util.TimeZone; public class PushReminder { private Activity activity; private static final int REQUEST_CALENDAR_PERMISSIONS = 1001; public PushReminder(Activity activity) { this.activity = activity; } public void addReminder(long startTime) { // 先检查权限 if (activity.checkSelfPermission(android.Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED || activity.checkSelfPermission(android.Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) { activity.requestPermissions(new String[]{android.Manifest.permission.WRITE_CALENDAR, android.Manifest.permission.READ_CALENDAR}, REQUEST_CALENDAR_PERMISSIONS); Toast.makeText(activity, "请授予日历权限以创建提醒", Toast.LENGTH_SHORT).show(); return; } ContentResolver cr = activity.getContentResolver(); // 获取用户默认日历ID(替代硬编码的1) long defaultCalendarId = getDefaultCalendarId(cr); if (defaultCalendarId == -1) { Toast.makeText(activity, "未找到可用的日历", Toast.LENGTH_SHORT).show(); return; } ContentValues calEvent = new ContentValues(); calEvent.put(CalendarContract.Events.CALENDAR_ID, defaultCalendarId); calEvent.put(CalendarContract.Events.TITLE, "Add Shop Reminder"); calEvent.put(CalendarContract.Events.DTSTART, startTime); calEvent.put(CalendarContract.Events.DTEND, startTime + 5 * 60 * 1000); // 直接计算,避免Date对象的额外开销 calEvent.put(CalendarContract.Events.HAS_ALARM, 1); calEvent.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().getID()); // 使用真实时区ID Uri uri = cr.insert(CalendarContract.Events.CONTENT_URI, calEvent); if (uri == null) { Toast.makeText(activity, "创建日历事件失败", Toast.LENGTH_SHORT).show(); return; } int eventId = Integer.parseInt(uri.getLastPathSegment()); Toast.makeText(activity, "Created Calendar Event " + eventId, Toast.LENGTH_SHORT).show(); 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, 10); Uri reminderUri = cr.insert(CalendarContract.Reminders.CONTENT_URI, reminders); if (reminderUri != null) { Toast.makeText(activity, "Reminder have been saved successfully", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(activity, "创建提醒失败", Toast.LENGTH_SHORT).show(); } } // 获取用户默认日历ID的工具方法 private long getDefaultCalendarId(ContentResolver cr) { Uri calendarUri = CalendarContract.Calendars.CONTENT_URI; String[] projection = {CalendarContract.Calendars._ID}; String selection = CalendarContract.Calendars.IS_PRIMARY + " = ?"; String[] selectionArgs = {"1"}; // 先查询主日历 android.database.Cursor cursor = cr.query(calendarUri, projection, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { long id = cursor.getLong(0); cursor.close(); return id; } // 如果没有主日历,取第一个可用日历 cursor = cr.query(calendarUri, projection, null, null, null); if (cursor != null && cursor.moveToFirst()) { long id = cursor.getLong(0); cursor.close(); return id; } if (cursor != null) { cursor.close(); } return -1; } }
额外注意事项
- 权限申请回调:记得在Activity中重写
onRequestPermissionsResult方法,在用户授予权限后重新调用addReminder方法。 - 时区兼容性:使用
TimeZone.getDefault().getID()可以适配不同地区的设备,避免时区解析错误。 - 异常捕获:可以在代码中添加
try-catch块捕获SQLiteException,方便定位具体的设备兼容性问题。
内容的提问来源于stack exchange,提问作者Arpit
相关产品推荐
相关产品推荐

