Android日历操作

基础知识

内容提供程序用于存储数据,并使其可供应用访问。Android 平台提供的内容提供程序(包括日历提供程序)通常以一组基于关系型数据库模型的表格形式公开数据,表格内的每一行都是一条记录,每一列都是特定类型和含义的数据。应用和同步适配器可通过 Calendar Provider API 获得对储存用户日历数据的数据库表进行读取/写入的权限。

每个内容提供程序都会公开一个公共 URI(包装成 Uri 对象),从而对其数据集进行唯一标识。控制多个数据集(多个表)的内容提供程序会为每个数据集公开单独的 URI。所有提供程序 URI 都以字符串“content://”开头。这表示数据会受内容提供程序的控制。日历提供程序会为其每个类(表)定义 URI 常量。这些 URI 的格式为 *<class>*.CONTENT_URI。例如,Events.CONTENT_URI

Android intent 打开系统日历 android 日历权限_java



表(类)

描述

CalendarContract.Calendars

此表储存日历特定信息。此表中的每一行都包含一个日历的详细信息,例如名称、颜色、同步信息等。

CalendarContract.Events

此表储存事件特定信息。此表中的每一行都包含一个事件的相关信息,例如事件的标题、地点、开始时间、结束时间等。事件可一次性发生,也可多次重复发生。参加者、提醒和扩展属性存储在单独的表内。它们各自都有一个 EVENT_ID,用于引用 Events 表中的 _ID

CalendarContract.Instances

此表储存每个事件实例的开始时间和结束时间。此表中的每一行都表示一个事件实例。对于一次性事件,实例与事件为 1:1 映射。对于重复事件,系统会自动生成多个行,分别对应多个事件实例。

CalendarContract.Attendees

此表储存事件参加者(来宾)信息。每一行都表示事件的一位来宾。每一行会指定来宾类型,以及来宾是否参加该事件的响应。

CalendarContract.Reminders

此表储存提醒/通知数据。每一行都表示事件的一个提醒。一个事件可以有多个提醒。您可以在 MAX_REMINDERS 中指定每个事件的最大提醒数量,后者由拥有给定日历的同步适配器设置。提醒需指定为在事件发生前的某个时间(分钟)发出,而且其拥有一个可决定用户提醒方式的方法。



用户权限

如果需要读取日历数据,需要加入READ_CALENDAR 权限。若需要对日历数据进行删改操作,需要WRITE_CALENDAR权限:

<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />

日历表

CalendarContract.Calendars 表包含各日历的详细信息。应用和同步适配器均可写入以下日历列。



常量

描述

NAME

日历的名称。

CALENDAR_DISPLAY_NAME

显示给用户时,该日历所使用的名称。

VISIBLE

表示是否选择显示该日历的布尔值。值为 0 表示不应显示与该日历关联的事件。值为 1 表示应该显示与该日历关联的事件。此值会影响 CalendarContract.Instances 表中行的生成。

SYNC_EVENTS

一个布尔值,表示是否应同步日历并将其事件存储在设备上。值为 0 表示不同步该日历,也不将其事件存储在设备上。值为 1 表示同步该日历的事件,并将其事件存储在设备上。



包括适用于所有操作的账户类型

如果您查询 Calendars.ACCOUNT_NAME,还必须将 Calendars.ACCOUNT_TYPE 加入选定范围。原因是对于给定帐户,只有在同时指定其 ACCOUNT_NAMEACCOUNT_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 参考资料。


常量

描述

CALENDAR_ID

事件所属日历的 _ID

ORGANIZER

事件组织者(所有者)的电子邮件。

TITLE

事件的名称。

EVENT_LOCATION

事件的发生地点。

DESCRIPTION

事件的描述。

DTSTART

事件开始时间,以从公元纪年开始计算的协调世界时毫秒数表示。

DTEND

事件结束时间,以从公元纪年开始计算的协调世界时毫秒数表示。

EVENT_TIMEZONE

事件的时区。

EVENT_END_TIMEZONE

事件结束时间的时区。

DURATION

RFC5545 格式的事件持续时间。例如,值为 "PT1H" 表示事件应持续一小时,值为 "P2W" 表示持续 2 周。

ALL_DAY

值为 1 表示此事件占用一整天(按照本地时区的定义)。值为 0 表示它是常规事件,可在一天内的任何时间开始和结束。

RRULE

事件的重复发生规则格式。例如,"FREQ=WEEKLY;COUNT=10;WKST=SU"。您可以在此处找到更多示例。

RDATE

事件的重复发生日期。RDATERRULE 通常联合用于定义一组存在聚合关系的重复实例。如需查看更详细的介绍,请参阅 RFC5545 规范。

AVAILABILITY

将此事件视为忙碌时间还是可调度的空闲时间。

GUESTS_CAN_MODIFY

来宾是否可修改事件。

GUESTS_CAN_INVITE_OTHERS

来宾是否可邀请其他来宾。

GUESTS_CAN_SEE_GUESTS

来宾是否可查看参加者列表。


添加事件

当应用插入新事件时,我们建议您按使用 Intent 插入事件中所述使用 INSERT Intent。不过,如有需要,您也可直接插入事件。本部分描述如何执行此操作。

以下是插入新事件的规则:

  • 您必须加入 CALENDAR_IDDTSTART
  • 您必须加入 EVENT_TIMEZONE。如需获取系统中已安装时区 ID 的列表,请使用 getAvailableIDs()。请注意,如果您按使用 Intent 插入事件中所述通过 INSERT Intent 插入事件,则此规则不适用 — 在该情形下,系统会提供默认时区。
  • 对于非重复事件,您必须加入 DTEND
  • 对于重复事件,您必须加入 DURATION,以及 RRULERDATE。请注意,如果您按使用 Intent 插入事件中所述通过 INSERT Intent 插入事件,则此规则不适用 — 在该情形下,您可以将 RRULEDTSTARTDTEND 结合使用,日历应用会自动将其转换为持续时间。

以下是一个插入事件的示例。为简便起见,我们在界面线程内执行此操作。实际上,应该在异步线程中完成插入和更新,以便将操作移入后台线程。如需了解详细信息,请参阅 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);
    }