日历提供程序是用户日历事件的存储库。日历提供程序 API 允许您对日历、事件、与会者、提醒等执行查询、插入、更新和删除操作。
应用和同步适配器可以使用日历提供程序 API。规则因进行调用的程序类型而异。本文档主要关注将日历提供程序 API 用作应用。有关同步适配器有何不同的讨论,请参阅同步适配器。
通常,要读取或写入日历数据,应用的清单必须包含正确的权限,如用户权限中所述。为了简化常用操作的执行,日历提供程序提供了一组意图,如日历意图中所述。这些意图将用户带到日历应用以插入、查看和编辑事件。用户与日历应用交互,然后返回到原始应用。因此,您的应用不需要请求权限,也不需要提供用户界面来查看或创建事件。
基础知识
内容提供程序存储数据并使其可供应用访问。Android 平台提供的内容提供程序(包括日历提供程序)通常会将数据显示为基于关系数据库模型的一组表,其中每一行都是一条记录,每一列都是特定类型和含义的数据。通过日历提供程序 API,应用和同步适配器可以获得对保存用户日历数据的数据库表的读/写访问权限。
每个内容提供商都会公开一个公共 URI(封装为Uri
对象),用于唯一标识其数据集。控制多个数据集(多个表)的内容提供商为每个数据集公开一个单独的 URI。所有提供商的 URI 都以字符串“content://”开头。这标识数据由内容提供商控制。日历提供商为其每个类(表)的 URI 定义常量。这些 URI 的格式为<class>.CONTENT_URI
。例如,Events.CONTENT_URI
。
图 1 显示了日历提供商数据模型的图形表示。它显示了主要的表以及将它们相互链接的字段。
用户可以拥有多个日历,不同的日历可以与不同类型的帐户(Google 日历、Exchange 等)关联。
CalendarContract
定义了日历和事件相关信息的日期模型。这些数据存储在许多表中,如下所示。
表(类) | 描述 |
---|---|
此表保存日历特定信息。此表中的每一行都包含单个日历的详细信息,例如名称、颜色、同步信息等等。 | |
CalendarContract.Events |
此表保存事件特定信息。此表中的每一行都包含单个事件的信息,例如事件标题、位置、开始时间、结束时间等等。事件可以是一次性的,也可以多次重复。与会者、提醒和扩展属性存储在单独的表中。它们每个都有一个EVENT_ID ,它引用 Events 表中的_ID 。 |
CalendarContract.Instances |
此表保存每个事件发生的开始和结束时间。此表中的每一行都代表单个事件发生。对于一次性事件,实例与事件之间存在 1:1 的映射。对于重复事件,会自动生成多行,对应于该事件的多次发生。 |
CalendarContract.Attendees |
此表保存事件参与者(来宾)信息。每一行都代表事件的单个来宾。它指定来宾的类型以及来宾对该事件的出席回应。 |
CalendarContract.Reminders |
此表保存警报/通知数据。每一行都代表事件的单个警报。一个事件可以有多个提醒。每个事件的提醒最大数量在MAX_REMINDERS 中指定,该值由拥有给定日历的同步适配器设置。提醒以事件之前的分钟数指定,并有一种方法确定将如何提醒用户。 |
日历提供商 API 旨在灵活且功能强大。同时,提供良好的最终用户体验并保护日历及其数据的完整性非常重要。为此,在使用 API 时请记住以下几点
- 插入、更新和查看日历事件。 要直接从日历提供商插入、修改和读取事件,您需要相应的权限。但是,如果您不是构建完整的日历应用程序或同步适配器,则无需请求这些权限。您可以改用 Android 的日历应用程序支持的意图,将读写操作交给该应用程序。当您使用意图时,您的应用程序会将用户发送到日历应用程序,以便在预先填写的表单中执行所需的操作。完成后,他们将返回您的应用程序。通过设计您的应用程序以通过日历执行常见操作,您可以为用户提供一致、强大的用户界面。这是推荐的方法。有关更多信息,请参阅日历意图。
- 同步适配器。 同步适配器将用户设备上的日历数据与另一个服务器或数据源同步。
CalendarContract.Calendars
和CalendarContract.Events
表中有一些列是为同步适配器保留的。提供商和应用程序不应修改它们。事实上,除非它们作为同步适配器访问,否则它们是不可见的。有关同步适配器的更多信息,请参阅同步适配器。
用户权限
要读取日历数据,应用程序必须在其清单文件中包含READ_CALENDAR
权限。它必须包含WRITE_CALENDAR
权限才能删除、插入或更新日历数据。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"...> <uses-sdk android:minSdkVersion="14" /> <uses-permission android:name="android.permission.READ_CALENDAR" /> <uses-permission android:name="android.permission.WRITE_CALENDAR" /> ... </manifest>
日历表
CalendarContract.Calendars
表包含各个日历的详细信息。应用程序和同步适配器都可以写入以下日历列。有关支持字段的完整列表,请参阅CalendarContract.Calendars
参考。
常量 | 描述 |
---|---|
NAME |
日历的名称。 |
CALENDAR_DISPLAY_NAME |
显示给用户的此日历的名称。 |
VISIBLE |
一个布尔值,指示是否选中日历以显示。值为 0 表示不应显示与此日历关联的事件。值为 1 表示应显示与此日历关联的事件。此值会影响CalendarContract.Instances 表中行的生成。 |
SYNC_EVENTS |
一个布尔值,指示是否应同步日历并在设备上存储其事件。值为 0 表示不应同步此日历或将其事件存储在设备上。值为 1 表示同步此日历的事件并在设备上存储其事件。 |
为所有操作包含帐户类型
如果您查询Calendars.ACCOUNT_NAME
,则还必须在选择中包含Calendars.ACCOUNT_TYPE
。这是因为,给定帐户仅在其ACCOUNT_NAME
和ACCOUNT_TYPE
都给定的情况下才被认为是唯一的。ACCOUNT_TYPE
是帐户注册到AccountManager
时使用的帐户身份验证器对应的字符串。还有一种称为ACCOUNT_TYPE_LOCAL
的特殊帐户类型,用于与设备帐户无关的日历。ACCOUNT_TYPE_LOCAL
帐户不会同步。
查询日历
这是一个示例,它展示了如何获取特定用户拥有的日历。为简单起见,在此示例中,查询操作显示在用户界面线程(“主线程”)中。实际上,这应该在异步线程中完成,而不是在主线程中完成。有关更多讨论,请参阅加载器。如果您不仅读取数据而且修改数据,请参阅AsyncQueryHandler
。
Kotlin
// Projection array. Creating indices for this array instead of doing // dynamic lookups improves performance. private val EVENT_PROJECTION: Array<String> = arrayOf( CalendarContract.Calendars._ID, // 0 CalendarContract.Calendars.ACCOUNT_NAME, // 1 CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, // 2 CalendarContract.Calendars.OWNER_ACCOUNT // 3 ) // The indices for the projection array above. private const val PROJECTION_ID_INDEX: Int = 0 private const val PROJECTION_ACCOUNT_NAME_INDEX: Int = 1 private const val PROJECTION_DISPLAY_NAME_INDEX: Int = 2 private const val PROJECTION_OWNER_ACCOUNT_INDEX: Int = 3
Java
// 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;
在示例的下一部分中,您将构建查询。选择指定查询的条件。在此示例中,查询正在查找具有ACCOUNT_NAME
“[email protected]”,ACCOUNT_TYPE
“com.example”和OWNER_ACCOUNT
“[email protected]”的日历。如果您想查看用户查看的所有日历,而不仅仅是用户拥有的日历,请省略OWNER_ACCOUNT
。查询返回一个Cursor
对象,您可以使用它来遍历数据库查询返回的结果集。有关在内容提供商中使用查询的更多讨论,请参阅内容提供商。
Kotlin
// Run query val uri: Uri = CalendarContract.Calendars.CONTENT_URI val selection: String = "((${CalendarContract.Calendars.ACCOUNT_NAME} = ?) AND (" + "${CalendarContract.Calendars.ACCOUNT_TYPE} = ?) AND (" + "${CalendarContract.Calendars.OWNER_ACCOUNT} = ?))" val selectionArgs: Array<String> = arrayOf("[email protected]", "com.example", "[email protected]") val cur: Cursor = contentResolver.query(uri, EVENT_PROJECTION, selection, selectionArgs, null)
Java
// 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[] {"[email protected]", "com.example", "[email protected]"}; // Submit the query and get a Cursor object back. cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);
下一节使用游标遍历结果集。它使用在示例开头设置的常量来返回每个字段的值。
Kotlin
// Use the cursor to step through the returned records while (cur.moveToNext()) { // Get the field values val calID: Long = cur.getLong(PROJECTION_ID_INDEX) val displayName: String = cur.getString(PROJECTION_DISPLAY_NAME_INDEX) val accountName: String = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX) val ownerName: String = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX) // Do something with the values... }
Java
// 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... ... }
修改日历
要执行日历更新,您可以将日历的_ID
作为附加 ID 提供给 Uri (withAppendedId()
)或作为第一个选择项。选择应以"_id=?"
开头,第一个selectionArg
应为日历的_ID
。您还可以通过在 URI 中编码 ID 来进行更新。此示例使用 (withAppendedId()
) 方法更改日历的显示名称。
Kotlin
const val DEBUG_TAG: String = "MyActivity" ... val calID: Long = 2 val values = ContentValues().apply { // The new display name for the calendar put(CalendarContract.Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar") } val updateUri: Uri = ContentUris.withAppendedId(CalendarContract.Calendars.CONTENT_URI, calID) val rows: Int = contentResolver.update(updateUri, values, null, null) Log.i(DEBUG_TAG, "Rows updated: $rows")
Java
private static final String DEBUG_TAG = "MyActivity"; ... long calID = 2; ContentValues values = new ContentValues(); // The new display name for the calendar values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar"); Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID); int rows = getContentResolver().update(updateUri, values, null, null); Log.i(DEBUG_TAG, "Rows updated: " + rows);
插入日历
日历旨在主要由同步适配器管理,因此您应该只作为同步适配器插入新日历。在大多数情况下,应用程序只能对日历进行表面更改,例如更改显示名称。如果应用程序需要创建本地日历,它可以通过将日历插入作为同步适配器来执行此操作,使用ACCOUNT_TYPE
为ACCOUNT_TYPE_LOCAL
。ACCOUNT_TYPE_LOCAL
是与设备帐户无关的日历的特殊帐户类型。此类型的日历不会同步到服务器。有关同步适配器的讨论,请参阅同步适配器。
事件表
CalendarContract.Events
表包含各个事件的详细信息。要添加、更新或删除事件,应用程序必须在其清单文件中包含WRITE_CALENDAR
权限。
应用程序和同步适配器都可以写入以下事件列。有关支持字段的完整列表,请参阅CalendarContract.Events
参考。
常量 | 描述 |
---|---|
CALENDAR_ID |
事件所属日历的 _ID 。 |
组织者 |
事件组织者(所有者)的邮箱地址。 |
标题 |
事件的标题。 |
事件地点 |
事件发生的地点。 |
描述 |
事件的描述。 |
开始时间 (DTSTART) |
事件开始时间,以自纪元以来的 UTC 毫秒数表示。 |
结束时间 (DTEND) |
事件结束时间,以自纪元以来的 UTC 毫秒数表示。 |
事件时区 (EVENT_TIMEZONE) |
事件的时区。 |
事件结束时区 (EVENT_END_TIMEZONE) |
事件结束时间的时区。 |
持续时间 (DURATION) |
事件的持续时间,采用 RFC5545 格式。例如,值 "PT1H" 表示事件持续一小时,值 "P2W" 表示持续 2 周。 |
全天事件 (ALL_DAY) |
值为 1 表示此事件占据全天,由本地时区定义。值为 0 表示这是一个常规事件,可以在一天中的任何时间开始和结束。 |
重复规则 (RRULE) |
事件的重复规则格式。例如,"FREQ=WEEKLY;COUNT=10;WKST=SU" 。您可以在 此处 找到更多示例。 |
重复日期 (RDATE) |
事件的重复日期。通常将 RDATE 与 RRULE 结合使用来定义重复事件的集合。有关更多讨论,请参阅 RFC5545 规范。 |
可用性 (AVAILABILITY) |
此事件是否计为忙碌时间或可以安排在其上的空闲时间。 |
客人可修改 (GUESTS_CAN_MODIFY) |
客人是否可以修改事件。 |
客人可邀请其他人 (GUESTS_CAN_INVITE_OTHERS) |
客人是否可以邀请其他客人。 |
客人可见客人列表 (GUESTS_CAN_SEE_GUESTS) |
客人是否可以看到与会者列表。 |
添加事件
当您的应用程序插入新事件时,我们建议您使用 INSERT
Intent,如 使用 Intent 插入事件 中所述。但是,如果需要,您可以直接插入事件。本节介绍如何执行此操作。
以下是插入新事件的规则
- 必须包含
CALENDAR_ID
和DTSTART
。 - 必须包含
EVENT_TIMEZONE
。要获取系统已安装时区 ID 的列表,请使用getAvailableIDs()
。请注意,如果您通过 使用 Intent 插入事件 中描述的INSERT
Intent 插入事件,则此规则不适用,在这种情况下,会提供默认时区。 - 对于非重复事件,必须包含
DTEND
。 - 对于重复事件,除了
RRULE
或RDATE
之外,还必须包含DURATION
。请注意,如果您通过 使用 Intent 插入事件 中描述的INSERT
Intent 插入事件,则此规则不适用,在这种情况下,您可以将RRULE
与DTSTART
和DTEND
结合使用,日历应用程序会自动将其转换为持续时间。
这是一个插入事件的示例。为简单起见,此操作在 UI 线程中执行。实际上,插入和更新应在异步线程中执行,以将操作移至后台线程。有关更多信息,请参阅 AsyncQueryHandler
。
Kotlin
val calID: Long = 3 val startMillis: Long = Calendar.getInstance().run { set(2012, 9, 14, 7, 30) timeInMillis } val endMillis: Long = Calendar.getInstance().run { set(2012, 9, 14, 8, 45) timeInMillis } ... val values = ContentValues().apply { put(CalendarContract.Events.DTSTART, startMillis) put(CalendarContract.Events.DTEND, endMillis) put(CalendarContract.Events.TITLE, "Jazzercise") put(CalendarContract.Events.DESCRIPTION, "Group workout") put(CalendarContract.Events.CALENDAR_ID, calID) put(CalendarContract.Events.EVENT_TIMEZONE, "America/Los_Angeles") } val uri: Uri = contentResolver.insert(CalendarContract.Events.CONTENT_URI, values) // get the event ID that is the last element in the Uri val eventID: Long = uri.lastPathSegment.toLong() // // ... do something with event ID // //
Java
long calID = 3; long startMillis = 0; long endMillis = 0; Calendar beginTime = Calendar.getInstance(); beginTime.set(2012, 9, 14, 7, 30); startMillis = beginTime.getTimeInMillis(); Calendar endTime = Calendar.getInstance(); endTime.set(2012, 9, 14, 8, 45); endMillis = endTime.getTimeInMillis(); ... ContentResolver cr = getContentResolver(); ContentValues values = new ContentValues(); values.put(Events.DTSTART, startMillis); values.put(Events.DTEND, endMillis); values.put(Events.TITLE, "Jazzercise"); values.put(Events.DESCRIPTION, "Group workout"); values.put(Events.CALENDAR_ID, calID); values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles"); Uri uri = cr.insert(Events.CONTENT_URI, values); // get the event ID that is the last element in the Uri long eventID = Long.parseLong(uri.getLastPathSegment()); // // ... do something with event ID // //
注意:请查看此示例如何在创建事件后捕获事件 ID。这是获取事件 ID 的最简单方法。您通常需要事件 ID 来执行其他日历操作,例如向事件添加与会者或提醒。
更新事件
当您的应用程序允许用户编辑事件时,我们建议您使用 EDIT
Intent,如 使用 Intent 编辑事件 中所述。但是,如果需要,您可以直接编辑事件。要执行事件的更新,您可以将事件的 _ID
作为附加 ID 附加到 Uri (withAppendedId()
) 或作为第一个选择项提供。选择应以 "_id=?"
开头,第一个 selectionArg
应为事件的 _ID
。您还可以使用没有 ID 的选择进行更新。这是一个更新事件的示例。它使用 withAppendedId()
方法更改事件的标题
Kotlin
val DEBUG_TAG = "MyActivity" ... val eventID: Long = 188 ... val values = ContentValues().apply { // The new title for the event put(CalendarContract.Events.TITLE, "Kickboxing") } val updateUri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID) val rows: Int = contentResolver.update(updateUri, values, null, null) Log.i(DEBUG_TAG, "Rows updated: $rows")
Java
private static final String DEBUG_TAG = "MyActivity"; ... long eventID = 188; ... ContentResolver cr = getContentResolver(); ContentValues values = new ContentValues(); Uri updateUri = null; // The new title for the event values.put(Events.TITLE, "Kickboxing"); updateUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); int rows = cr.update(updateUri, values, null, null); Log.i(DEBUG_TAG, "Rows updated: " + rows);
删除事件
您可以通过事件的 _ID
作为附加到 URI 的 ID 来删除事件,也可以使用标准选择来删除。如果您使用附加 ID,则也不能进行选择。删除有两个版本:作为应用程序和作为同步适配器。应用程序删除会将 *deleted* 列设置为 1。此标志告诉同步适配器该行已删除,并且应将此删除传播到服务器。同步适配器删除会从数据库中删除事件及其所有关联数据。这是一个通过其 _ID
删除事件的应用程序示例
Kotlin
val DEBUG_TAG = "MyActivity" ... val eventID: Long = 201 ... val deleteUri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID) val rows: Int = contentResolver.delete(deleteUri, null, null) Log.i(DEBUG_TAG, "Rows deleted: $rows")
Java
private static final String DEBUG_TAG = "MyActivity"; ... long eventID = 201; ... ContentResolver cr = getContentResolver(); Uri deleteUri = null; deleteUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); int rows = cr.delete(deleteUri, null, null); Log.i(DEBUG_TAG, "Rows deleted: " + rows);
与会者表
CalendarContract.Attendees
表的每一行都表示事件的单个与会者或客人。调用 query()
将返回具有给定 EVENT_ID
的事件的与会者列表。此 EVENT_ID
必须与特定事件的 _ID
匹配。
下表列出了可写入字段。插入新的与会者时,必须包含所有字段,除了 ATTENDEE_NAME
。
常量 | 描述 |
---|---|
EVENT_ID |
事件的 ID。 |
ATTENDEE_NAME |
与会者的姓名。 |
ATTENDEE_EMAIL |
与会者的电子邮件地址。 |
ATTENDEE_RELATIONSHIP |
与会者与事件的关系。以下之一: |
ATTENDEE_TYPE |
与会者的类型。以下之一: |
ATTENDEE_STATUS |
与会者的出席状态。以下之一: |
添加与会者
这是一个将单个与会者添加到事件的示例。请注意,需要 EVENT_ID
Kotlin
val eventID: Long = 202 ... val values = ContentValues().apply { put(CalendarContract.Attendees.ATTENDEE_NAME, "Trevor") put(CalendarContract.Attendees.ATTENDEE_EMAIL, "[email protected]") put( CalendarContract.Attendees.ATTENDEE_RELATIONSHIP, CalendarContract.Attendees.RELATIONSHIP_ATTENDEE ) put(CalendarContract.Attendees.ATTENDEE_TYPE, CalendarContract.Attendees.TYPE_OPTIONAL) put( CalendarContract.Attendees.ATTENDEE_STATUS, CalendarContract.Attendees.ATTENDEE_STATUS_INVITED ) put(CalendarContract.Attendees.EVENT_ID, eventID) } val uri: Uri = contentResolver.insert(CalendarContract.Attendees.CONTENT_URI, values)
Java
long eventID = 202; ... ContentResolver cr = getContentResolver(); ContentValues values = new ContentValues(); values.put(Attendees.ATTENDEE_NAME, "Trevor"); values.put(Attendees.ATTENDEE_EMAIL, "[email protected]"); values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL); values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED); values.put(Attendees.EVENT_ID, eventID); Uri uri = cr.insert(Attendees.CONTENT_URI, values);
提醒表
CalendarContract.Reminders
表的每一行都表示事件的单个提醒。调用 query()
将返回具有给定 EVENT_ID
的事件的提醒列表。
下表列出了提醒的可写入字段。插入新的提醒时,必须包含所有这些字段。请注意,同步适配器在 CalendarContract.Calendars
表中指定其支持的提醒类型。有关详细信息,请参阅 ALLOWED_REMINDERS
。
常量 | 描述 |
---|---|
EVENT_ID |
事件的 ID。 |
分钟数 (MINUTES) |
提醒应在事件之前几分钟发出。 |
方法 (METHOD) |
服务器上设置的警报方法。以下之一: |
添加提醒
此示例向事件添加提醒。提醒在事件开始前 15 分钟发出。
Kotlin
val eventID: Long = 221 ... val values = ContentValues().apply { put(CalendarContract.Reminders.MINUTES, 15) put(CalendarContract.Reminders.EVENT_ID, eventID) put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT) } val uri: Uri = contentResolver.insert(CalendarContract.Reminders.CONTENT_URI, values)
Java
long eventID = 221; ... ContentResolver cr = getContentResolver(); ContentValues values = new ContentValues(); values.put(Reminders.MINUTES, 15); values.put(Reminders.EVENT_ID, eventID); values.put(Reminders.METHOD, Reminders.METHOD_ALERT); Uri uri = cr.insert(Reminders.CONTENT_URI, values);
实例表
CalendarContract.Instances
表保存事件发生的开始和结束时间。此表中的每一行都表示单个事件发生。实例表不可写,仅提供查询事件发生的方法。
下表列出了一些可以查询实例的字段。请注意,时区由 KEY_TIMEZONE_TYPE
和 KEY_TIMEZONE_INSTANCES
定义。
常量 | 描述 |
---|---|
开始时间 (BEGIN) |
实例的开始时间,以 UTC 毫秒数表示。 |
结束时间 (END) |
实例的结束时间,以 UTC 毫秒数表示。 |
结束日 (END_DAY) |
实例的儒略结束日,相对于日历的时区。 |
结束分钟 (END_MINUTE) |
实例的结束分钟,从日历时区的午夜开始计算。 |
EVENT_ID |
此实例的事件的 _ID 。 |
开始日 (START_DAY) |
实例的儒略开始日,相对于日历的时区。 |
开始分钟 (START_MINUTE) |
实例的起始分钟数,从午夜开始计算,相对于日历的时区。 |
查询实例表
要查询 Instances 表,您需要在 URI 中指定查询的时间范围。在这个例子中,CalendarContract.Instances
通过实现 CalendarContract.EventsColumns
接口来访问 TITLE
字段。换句话说,TITLE
是通过数据库视图返回的,而不是通过查询原始的 CalendarContract.Instances
表。
Kotlin
const val DEBUG_TAG: String = "MyActivity" val INSTANCE_PROJECTION: Array<String> = arrayOf( CalendarContract.Instances.EVENT_ID, // 0 CalendarContract.Instances.BEGIN, // 1 CalendarContract.Instances.TITLE // 2 ) // The indices for the projection array above. const val PROJECTION_ID_INDEX: Int = 0 const val PROJECTION_BEGIN_INDEX: Int = 1 const val PROJECTION_TITLE_INDEX: Int = 2 // Specify the date range you want to search for recurring // event instances val startMillis: Long = Calendar.getInstance().run { set(2011, 9, 23, 8, 0) timeInMillis } val endMillis: Long = Calendar.getInstance().run { set(2011, 10, 24, 8, 0) timeInMillis } // The ID of the recurring event whose instances you are searching // for in the Instances table val selection: String = "${CalendarContract.Instances.EVENT_ID} = ?" val selectionArgs: Array<String> = arrayOf("207") // Construct the query with the desired date range. val builder: Uri.Builder = CalendarContract.Instances.CONTENT_URI.buildUpon() ContentUris.appendId(builder, startMillis) ContentUris.appendId(builder, endMillis) // Submit the query val cur: Cursor = contentResolver.query( builder.build(), INSTANCE_PROJECTION, selection, selectionArgs, null ) while (cur.moveToNext()) { // Get the field values val eventID: Long = cur.getLong(PROJECTION_ID_INDEX) val beginVal: Long = cur.getLong(PROJECTION_BEGIN_INDEX) val title: String = cur.getString(PROJECTION_TITLE_INDEX) // Do something with the values. Log.i(DEBUG_TAG, "Event: $title") val calendar = Calendar.getInstance().apply { timeInMillis = beginVal } val formatter = SimpleDateFormat("MM/dd/yyyy") Log.i(DEBUG_TAG, "Date: ${formatter.format(calendar.time)}") }
Java
private static final String DEBUG_TAG = "MyActivity"; public static final String[] INSTANCE_PROJECTION = new String[] { Instances.EVENT_ID, // 0 Instances.BEGIN, // 1 Instances.TITLE // 2 }; // The indices for the projection array above. private static final int PROJECTION_ID_INDEX = 0; private static final int PROJECTION_BEGIN_INDEX = 1; private static final int PROJECTION_TITLE_INDEX = 2; ... // Specify the date range you want to search for recurring // event instances Calendar beginTime = Calendar.getInstance(); beginTime.set(2011, 9, 23, 8, 0); long startMillis = beginTime.getTimeInMillis(); Calendar endTime = Calendar.getInstance(); endTime.set(2011, 10, 24, 8, 0); long endMillis = endTime.getTimeInMillis(); Cursor cur = null; ContentResolver cr = getContentResolver(); // The ID of the recurring event whose instances you are searching // for in the Instances table String selection = Instances.EVENT_ID + " = ?"; String[] selectionArgs = new String[] {"207"}; // Construct the query with the desired date range. Uri.Builder builder = Instances.CONTENT_URI.buildUpon(); ContentUris.appendId(builder, startMillis); ContentUris.appendId(builder, endMillis); // Submit the query cur = cr.query(builder.build(), INSTANCE_PROJECTION, selection, selectionArgs, null); while (cur.moveToNext()) { String title = null; long eventID = 0; long beginVal = 0; // Get the field values eventID = cur.getLong(PROJECTION_ID_INDEX); beginVal = cur.getLong(PROJECTION_BEGIN_INDEX); title = cur.getString(PROJECTION_TITLE_INDEX); // Do something with the values. Log.i(DEBUG_TAG, "Event: " + title); Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(beginVal); DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy"); Log.i(DEBUG_TAG, "Date: " + formatter.format(calendar.getTime())); } }
日历意图
您的应用程序不需要 权限 来读取和写入日历数据。它可以使用 Android 日历应用程序支持的意图将读取和写入操作交给该应用程序处理。下表列出了日历提供程序支持的意图
操作 | URI | 描述 | 额外信息 |
---|---|---|---|
VIEW |
CalendarContract.CONTENT_URI 来引用 URI。有关使用此意图的示例,请参阅 使用意图查看日历数据。 |
打开日历到由 <ms_since_epoch> 指定的时间。 |
无。 |
Events.CONTENT_URI 来引用 URI。有关使用此意图的示例,请参阅 使用意图查看日历数据。 |
查看由 <event_id> 指定的事件。 |
CalendarContract.EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_END_TIME |
|
EDIT |
Events.CONTENT_URI 来引用 URI。有关使用此意图的示例,请参阅 使用意图编辑事件。 |
编辑由 <event_id> 指定的事件。 |
CalendarContract.EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_END_TIME |
EDIT INSERT |
Events.CONTENT_URI 来引用 URI。有关使用此意图的示例,请参阅 使用意图插入事件。 |
创建一个事件。 | 下表中列出的任何额外信息。 |
下表列出了日历提供程序支持的意图额外信息
意图额外信息 | 描述 |
---|---|
Events.TITLE |
事件名称。 |
CalendarContract.EXTRA_EVENT_BEGIN_TIME |
事件开始时间(自纪元以来的毫秒数)。 |
CalendarContract.EXTRA_EVENT_END_TIME |
事件结束时间(自纪元以来的毫秒数)。 |
CalendarContract.EXTRA_EVENT_ALL_DAY |
一个布尔值,指示事件是否为全天事件。值可以是 true 或 false 。 |
Events.EVENT_LOCATION |
事件地点。 |
Events.DESCRIPTION |
事件描述。 |
Intent.EXTRA_EMAIL |
邀请对象的电子邮件地址(以逗号分隔的列表)。 |
Events.RRULE |
事件的重复规则。 |
Events.ACCESS_LEVEL |
事件是私有的还是公开的。 |
Events.AVAILABILITY |
此事件是否计为忙碌时间或可以安排在其上的空闲时间。 |
以下部分描述了如何使用这些意图。
使用意图插入事件
使用 INSERT
意图可以让您的应用程序将事件插入任务交给日历本身处理。使用这种方法,您的应用程序甚至不需要在其 清单文件 中包含 WRITE_CALENDAR
权限。
当用户运行使用此方法的应用程序时,应用程序会将他们带到日历中以完成添加事件的操作。 INSERT
意图使用额外的字段来预填充日历中事件详细信息的表单。然后,用户可以取消事件、根据需要编辑表单或将事件保存到他们的日历中。
这是一个代码片段,它计划在 2012 年 1 月 19 日安排一个从上午 7:30 到上午 8:30 运行的事件。请注意此代码片段的以下内容:
- 它将
Events.CONTENT_URI
指定为 Uri。 - 它使用
CalendarContract.EXTRA_EVENT_BEGIN_TIME
和CalendarContract.EXTRA_EVENT_END_TIME
额外字段来预填充事件时间的表单。这些时间的值必须以自纪元以来的 UTC 毫秒为单位。 - 它使用
Intent.EXTRA_EMAIL
额外字段来提供受邀者的逗号分隔列表,由电子邮件地址指定。
Kotlin
val startMillis: Long = Calendar.getInstance().run { set(2012, 0, 19, 7, 30) timeInMillis } val endMillis: Long = Calendar.getInstance().run { set(2012, 0, 19, 8, 30) timeInMillis } val intent = Intent(Intent.ACTION_INSERT) .setData(CalendarContract.Events.CONTENT_URI) .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis) .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis) .putExtra(CalendarContract.Events.TITLE, "Yoga") .putExtra(CalendarContract.Events.DESCRIPTION, "Group class") .putExtra(CalendarContract.Events.EVENT_LOCATION, "The gym") .putExtra(CalendarContract.Events.AVAILABILITY, CalendarContract.Events.AVAILABILITY_BUSY) .putExtra(Intent.EXTRA_EMAIL, "[email protected],[email protected]") startActivity(intent)
Java
Calendar beginTime = Calendar.getInstance(); beginTime.set(2012, 0, 19, 7, 30); Calendar endTime = Calendar.getInstance(); endTime.set(2012, 0, 19, 8, 30); Intent intent = new Intent(Intent.ACTION_INSERT) .setData(Events.CONTENT_URI) .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis()) .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis()) .putExtra(Events.TITLE, "Yoga") .putExtra(Events.DESCRIPTION, "Group class") .putExtra(Events.EVENT_LOCATION, "The gym") .putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY) .putExtra(Intent.EXTRA_EMAIL, "[email protected],[email protected]"); startActivity(intent);
使用意图编辑事件
您可以直接更新事件,如 更新事件 中所述。但是,使用 EDIT
意图允许没有权限的应用程序将事件编辑交给日历应用程序处理。当用户在日历中完成事件编辑后,他们将返回到原始应用程序。
这是一个意图示例,它为指定的事件设置一个新标题,并允许用户在日历中编辑该事件。
Kotlin
val eventID: Long = 208 val uri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID) val intent = Intent(Intent.ACTION_EDIT) .setData(uri) .putExtra(CalendarContract.Events.TITLE, "My New Title") startActivity(intent)
Java
long eventID = 208; Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); Intent intent = new Intent(Intent.ACTION_EDIT) .setData(uri) .putExtra(Events.TITLE, "My New Title"); startActivity(intent);
使用意图查看日历数据
日历提供程序提供了两种不同的方法来使用 VIEW
意图
- 打开日历到特定日期。
- 查看一个事件。
这是一个示例,演示如何打开日历到特定日期
Kotlin
val startMillis: Long ... val builder: Uri.Builder = CalendarContract.CONTENT_URI.buildUpon() .appendPath("time") ContentUris.appendId(builder, startMillis) val intent = Intent(Intent.ACTION_VIEW) .setData(builder.build()) startActivity(intent)
Java
// A date-time specified in milliseconds since the epoch. long startMillis; ... Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon(); builder.appendPath("time"); ContentUris.appendId(builder, startMillis); Intent intent = new Intent(Intent.ACTION_VIEW) .setData(builder.build()); startActivity(intent);
这是一个示例,演示如何打开事件进行查看
Kotlin
val eventID: Long = 208 ... val uri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventID) val intent = Intent(Intent.ACTION_VIEW).setData(uri) startActivity(intent)
Java
long eventID = 208; ... Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); Intent intent = new Intent(Intent.ACTION_VIEW) .setData(uri); startActivity(intent);
同步适配器
应用程序和同步适配器访问日历提供程序的方式只有细微的差别
- 同步适配器需要通过将
CALLER_IS_SYNCADAPTER
设置为true
来指定它是一个同步适配器。 - 同步适配器需要在 URI 中提供
ACCOUNT_NAME
和ACCOUNT_TYPE
作为查询参数。 - 同步适配器对列的写入访问权限比应用程序或窗口小部件更多。例如,应用程序只能修改日历的几个特性,例如其名称、显示名称、可见性设置以及日历是否已同步。相比之下,同步适配器不仅可以访问这些列,还可以访问许多其他列,例如日历颜色、时区、访问级别、位置等等。但是,同步适配器仅限于其指定的
ACCOUNT_NAME
和ACCOUNT_TYPE
。
这是一个您可以使用的辅助方法,用于返回可与同步适配器一起使用的 URI
Kotlin
fun asSyncAdapter(uri: Uri, account: String, accountType: String): Uri { return uri.buildUpon() .appendQueryParameter(CalendarContract.CALLER_IS_SYNCADAPTER, "true") .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_NAME, account) .appendQueryParameter(CalendarContract.Calendars.ACCOUNT_TYPE, accountType).build() }
Java
static Uri asSyncAdapter(Uri uri, String account, String accountType) { return uri.buildUpon() .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,"true") .appendQueryParameter(Calendars.ACCOUNT_NAME, account) .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build(); }