Android日历操作
基础知识
内容提供程序用于存储数据,并使其可供应用访问。Android 平台提供的内容提供程序(包括日历提供程序)通常以一组基于关系型数据库模型的表格形式公开数据,表格内的每一行都是一条记录,每一列都是特定类型和含义的数据。应用和同步适配器可通过 Calendar Provider API 获得对储存用户日历数据的数据库表进行读取/写入的权限。
每个内容提供程序都会公开一个公共 URI(包装成 Uri
对象),从而对其数据集进行唯一标识。控制多个数据集(多个表)的内容提供程序会为每个数据集公开单独的 URI。所有提供程序 URI 都以字符串“content://”开头。这表示数据会受内容提供程序的控制。日历提供程序会为其每个类(表)定义 URI 常量。这些 URI 的格式为 *<class>*.CONTENT_URI
。例如,Events.CONTENT_URI
。
表(类) | 描述 |
| 此表储存日历特定信息。此表中的每一行都包含一个日历的详细信息,例如名称、颜色、同步信息等。 |
| 此表储存事件特定信息。此表中的每一行都包含一个事件的相关信息,例如事件的标题、地点、开始时间、结束时间等。事件可一次性发生,也可多次重复发生。参加者、提醒和扩展属性存储在单独的表内。它们各自都有一个 |
| 此表储存每个事件实例的开始时间和结束时间。此表中的每一行都表示一个事件实例。对于一次性事件,实例与事件为 1:1 映射。对于重复事件,系统会自动生成多个行,分别对应多个事件实例。 |
| 此表储存事件参加者(来宾)信息。每一行都表示事件的一位来宾。每一行会指定来宾类型,以及来宾是否参加该事件的响应。 |
| 此表储存提醒/通知数据。每一行都表示事件的一个提醒。一个事件可以有多个提醒。您可以在 |
用户权限
如果需要读取日历数据,需要加入READ_CALENDAR
权限。若需要对日历数据进行删改操作,需要WRITE_CALENDAR
权限:
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
日历表
CalendarContract.Calendars
表包含各日历的详细信息。应用和同步适配器均可写入以下日历列。
常量 | 描述 |
| 日历的名称。 |
| 显示给用户时,该日历所使用的名称。 |
| 表示是否选择显示该日历的布尔值。值为 0 表示不应显示与该日历关联的事件。值为 1 表示应该显示与该日历关联的事件。此值会影响 |
| 一个布尔值,表示是否应同步日历并将其事件存储在设备上。值为 0 表示不同步该日历,也不将其事件存储在设备上。值为 1 表示同步该日历的事件,并将其事件存储在设备上。 |
包括适用于所有操作的账户类型
如果您查询 Calendars.ACCOUNT_NAME
,还必须将 Calendars.ACCOUNT_TYPE
加入选定范围。原因是对于给定帐户,只有在同时指定其 ACCOUNT_NAME
和 ACCOUNT_TYPE
的情况下,才能将其视为唯一帐户。ACCOUNT_TYPE
字符串对应在 AccountManager
处注册帐户时所使用的帐户验证器。还有一种称为 ACCOUNT_TYPE_LOCAL
的特殊帐户类型,其用于未关联设备帐户的日历。ACCOUNT_TYPE_LOCAL
帐户不会进行同步。
日历操作
查询日历
原则上该操作需要在非主线程中完成。
// Projection array. Creating indices for this array instead of doing
// dynamic lookups improves performance.
public static final String[] EVENT_PROJECTION = new String[] {
Calendars._ID, // 0
Calendars.ACCOUNT_NAME, // 1
Calendars.CALENDAR_DISPLAY_NAME, // 2
Calendars.OWNER_ACCOUNT // 3
};
// The indices for the projection array above.
private static final int PROJECTION_ID_INDEX = 0;
private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1;
private static final int PROJECTION_DISPLAY_NAME_INDEX = 2;
private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;
// Run query
Cursor cur = null;
ContentResolver cr = getContentResolver();
Uri uri = Calendars.CONTENT_URI;
String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND ("
+ Calendars.ACCOUNT_TYPE + " = ?) AND ("
+ Calendars.OWNER_ACCOUNT + " = ?))";
String[] selectionArgs = new String[] {"hera@example.com", "com.example",
"hera@example.com"};
// Submit the query and get a Cursor object back.
cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
// Use the cursor to step through the returned records
while (cur.moveToNext()) {
long calID = 0;
String displayName = null;
String accountName = null;
String ownerName = null;
// Get the field values
calID = cur.getLong(PROJECTION_ID_INDEX);
displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX);
accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX);
ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX);
// Do something with the values...
...
}
如果查询之后发现没有日历,那就需要先创建一个日历账户。
public static void createCalendar(Context context){
TimeZone timeZone = TimeZone.getDefault();
ContentValues values = new ContentValues();
values.put(CalendarContract.Calendars.NAME, "yiyi");
values.put(CalendarContract.Calendars.ACCOUNT_NAME, "1138154255@qq.com");
values.put(CalendarContract.Calendars.ACCOUNT_TYPE, "com.android.exchange");
values.put(CalendarContract.Calendars.VISIBLE, 1);
values.put(CalendarContract.Calendars.CALENDAR_COLOR, -9206951);
values.put(CalendarContract.Calendars.CALENDAR_ACCESS_LEVEL, CalendarContract.Calendars.CAL_ACCESS_OWNER);
values.put(CalendarContract.Calendars.SYNC_EVENTS, 1);
values.put(CalendarContract.Calendars.CALENDAR_TIME_ZONE, timeZone.getID());
values.put(CalendarContract.Calendars.OWNER_ACCOUNT, "1138154255@qq.com");
values.put(CalendarContract.Calendars.CAN_ORGANIZER_RESPOND, 0);
values.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, "KiLin");
Uri calendarUri = CalendarContract.Calendars.CONTENT_URI;
calendarUri = calendarUri.buildUpon()
.appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true")
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, "1138154255@qq.com")
.appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, "com.android.exchange")
.build();
context.getContentResolver().insert(calendarUri, values);
}
如上可以创建一个日历账户,其中的参数可以自己进行定义。
如果需要修改日历,比如修改一下日历的DISPLAY_NAME:
//修改日历表
ContentValues values = new ContentValues();
values.put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, "KiLin");
Uri updateUri = ContentUris.withAppendedId(CalendarContract.Calendars.CONTENT_URI, 1);
int rows = getContentResolver().update(updateUri, values, null, null);
Log.d(TAG, String.valueOf(rows));
事件表
CalendarContract.Events
表包含各事件的详细信息。如要添加、更新或删除事件,则应用必须在其清单文件中加入 WRITE_CALENDAR
权限。
应用和同步适配器均可写入以下事件列。如需查看所支持字段的完整列表,请参阅 CalendarContract.Events
参考资料。
常量 | 描述 |
| 事件所属日历的 |
| 事件组织者(所有者)的电子邮件。 |
| 事件的名称。 |
| 事件的发生地点。 |
| 事件的描述。 |
| 事件开始时间,以从公元纪年开始计算的协调世界时毫秒数表示。 |
| 事件结束时间,以从公元纪年开始计算的协调世界时毫秒数表示。 |
| 事件的时区。 |
| 事件结束时间的时区。 |
| RFC5545 格式的事件持续时间。例如,值为 |
| 值为 1 表示此事件占用一整天(按照本地时区的定义)。值为 0 表示它是常规事件,可在一天内的任何时间开始和结束。 |
| 事件的重复发生规则格式。例如, |
| 事件的重复发生日期。 |
| 将此事件视为忙碌时间还是可调度的空闲时间。 |
| 来宾是否可修改事件。 |
| 来宾是否可邀请其他来宾。 |
| 来宾是否可查看参加者列表。 |
添加事件
当应用插入新事件时,我们建议您按使用 Intent 插入事件中所述使用 INSERT
Intent。不过,如有需要,您也可直接插入事件。本部分描述如何执行此操作。
以下是插入新事件的规则:
- 您必须加入
CALENDAR_ID
和DTSTART
。 - 您必须加入
EVENT_TIMEZONE
。如需获取系统中已安装时区 ID 的列表,请使用getAvailableIDs()
。请注意,如果您按使用 Intent 插入事件中所述通过INSERT
Intent 插入事件,则此规则不适用 — 在该情形下,系统会提供默认时区。 - 对于非重复事件,您必须加入
DTEND
。 - 对于重复事件,您必须加入
DURATION
,以及RRULE
或RDATE
。请注意,如果您按使用 Intent 插入事件中所述通过INSERT
Intent 插入事件,则此规则不适用 — 在该情形下,您可以将RRULE
与DTSTART
和DTEND
结合使用,日历应用会自动将其转换为持续时间。
以下是一个插入事件的示例。为简便起见,我们在界面线程内执行此操作。实际上,应该在异步线程中完成插入和更新,以便将操作移入后台线程。如需了解详细信息,请参阅 AsyncQueryHandler
。
@RequiresApi(api = Build.VERSION_CODES.N)
public static void insertEvent(Context context, long calID){
Calendar beginTime = Calendar.getInstance();
beginTime.set(2022, 5, 1, 22, 0);
long startMillis = beginTime.getTimeInMillis();
Calendar endTime = Calendar.getInstance();
endTime.set(2022, 5, 1, 23, 30);
long endMillis = endTime.getTimeInMillis();
ContentResolver cr = context.getContentResolver();
ContentValues values = new ContentValues();
values.put(CalendarContract.Events.DTSTART, startMillis);
values.put(CalendarContract.Events.DTEND, endMillis);
values.put(CalendarContract.Events.TITLE, "日历开发");
values.put(CalendarContract.Events.DESCRIPTION, "增加日历的增删改功能");
values.put(CalendarContract.Events.CALENDAR_ID, calID);
values.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().getID());
Uri uri = cr.insert(CalendarContract.Events.CONTENT_URI, values);
long eventID = Long.parseLong(uri.getLastPathSegment());
System.out.println("EventId: " + eventID);
}
通过上面的案例我们可以看到,修改日历比较重要的一个参数就是日历账户的ID,Android的日历管理是将数据以账户为单位的,没有ID就不知道要对哪一个日历账户进行修改,所以一般在修改日历数据的时候都会先查询是否有存在的日历账户,若是没有则需要先新建一个日历账户,然后再进行日历的增删改查。
更新事件
更新的时候可以使用eventID进行,但是google建议是通过Intent编辑事件进行。
public static void updateEvent(Context context, long eventID){
ContentResolver cr = context.getContentResolver();
ContentValues values = new ContentValues();
Uri updateUri = null;
values.put(CalendarContract.Events.TITLE, "日历修改测试");
updateUri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID);
int rows = cr.update(updateUri, values, null, null);
System.out.println("EventsRows: " + rows);
}
删除事件
当然也可以如类似上文更新事件的方法进行事件的删除。
public static void deleteEvent(Context context, long eventID){
ContentResolver cr = context.getContentResolver();
Uri deleteUri = null;
deleteUri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID);
int rows = cr.delete(deleteUri, null, null);
System.out.println("EventsRows: " + rows);
}