功能
在安卓应用里调用系统日历,直接创建一个带提醒的日历事件,甚至不需要跳转到日历界面,只需要获取系统日历的读取权限即可。
需要的权限
在AndroidManifest.xml
里添加
xml
<uses-permission android:name="android.permission.READ_CALENDAR"/>
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
注意: 如果是Android 6.0(API 23)以上,需要动态申请权限。
代码
创建一个CalendarHelper
工具类,包含:
- 获取系统日历账户
- 自动写入事件
- 添加提醒
- 自动处理没有日历账户的情况(可提示用户手动创建)
- 动态申请权限(当用户拒绝权限时,我这里会弹出一个提示框,提示的内容可以从外部传入,也可以使用默认的。或者你不是使用默认,直接打开系统的设置页面也是可以的(下面屏蔽了这部分的代码))
- 判断是否已存在相同时间的逻辑,避免重复添加
java
package com.cocos.calender;
import android.app.Activity;
import android.Manifest;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.provider.CalendarContract;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.Calendar;
import java.util.TimeZone;
import org.json.JSONObject;
import org.json.JSONException;
import android.widget.Toast;
public class CalendarHelper {
private static final String TAG = "CalendarHelper";
/** 用来存放拒绝权限时的提示语 */
private static String denyPermissionMessage = "未获得日历权限,无法添加提醒事件";
/** 日历权限请求码 */
public static final int REQUEST_CALENDAR_PERMISSION = 1010;
/** 临时存储待执行事件 */
private static PendingEvent pendingEvent;
private static class PendingEvent {
String title;
String description;
String location;
long beginTime;
long endTime;
int reminderMinutes;
PendingEvent(String title, String description, String location,
long beginTime, long endTime, int reminderMinutes) {
this.title = title;
this.description = description;
this.location = location;
this.beginTime = beginTime;
this.endTime = endTime;
this.reminderMinutes = reminderMinutes;
}
}
/**
* 检查权限并添加事件(带权限请求)
*/
public static void addEventWithPermission(Activity activity,
String title,
String description,
String location,
long beginTimeMillis,
long endTimeMillis,
int reminderMinutes) {
// 检查日历读写权限
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_CALENDAR)
!= PackageManager.PERMISSION_GRANTED
|| ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_CALENDAR)
!= PackageManager.PERMISSION_GRANTED) {
// 保存事件等待用户授权
pendingEvent = new PendingEvent(title, description, location, beginTimeMillis, endTimeMillis, reminderMinutes);
// 这里可以加解释,但不强制
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.READ_CALENDAR)) {
Log.i(TAG, "需要日历权限来添加提醒事件");
}
// ✅ 直接请求权限(即使用户上次拒绝,这里依旧会再弹一次)
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.READ_CALENDAR,
Manifest.permission.WRITE_CALENDAR},
REQUEST_CALENDAR_PERMISSION);
} else {
// 权限已授权,直接添加
addEvent(activity, title, description, location, beginTimeMillis, endTimeMillis, reminderMinutes);
}
}
/**
* 在 Activity 的 onRequestPermissionsResult 中调用
*/
public static void onRequestPermissionsResultCalendar(Activity activity,
int requestCode,
@NonNull int[] grantResults) {
if (requestCode == REQUEST_CALENDAR_PERMISSION) {
if (grantResults.length >= 2
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "日历权限申请成功");
if (pendingEvent != null) {
addEvent(activity,
pendingEvent.title,
pendingEvent.description,
pendingEvent.location,
pendingEvent.beginTime,
pendingEvent.endTime,
pendingEvent.reminderMinutes);
pendingEvent = null;
}
} else {
Log.e(TAG, "用户拒绝了日历权限");
Toast.makeText(activity,
denyPermissionMessage,
Toast.LENGTH_SHORT).show();
// 如果用户永久拒绝,可跳转设置
// if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, android.Manifest.permission.READ_CALENDAR)) {
// Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
// intent.setData(Uri.parse("package:" + activity.getPackageName()));
// activity.startActivity(intent);
// }
}
}
}
/** 获取系统日历账户 ID */
private static long getCalendarAccountId(Context context) {
Cursor userCursor = context.getContentResolver().query(
CalendarContract.Calendars.CONTENT_URI,
new String[]{CalendarContract.Calendars._ID},
null, null, null);
if (userCursor != null) {
try {
if (userCursor.moveToFirst()) {
return userCursor.getLong(0);
}
} finally {
userCursor.close();
}
}
return -1;
}
/** 判断事件是否已存在(避免重复) */
private static boolean isEventAlreadyExists(Context context, String title, long beginTimeMillis) {
long oneMinuteBefore = beginTimeMillis - 60 * 1000;
long oneMinuteAfter = beginTimeMillis + 60 * 1000;
Cursor cursor = context.getContentResolver().query(
CalendarContract.Events.CONTENT_URI,
new String[]{CalendarContract.Events._ID},
CalendarContract.Events.TITLE + "=? AND " +
CalendarContract.Events.DTSTART + ">=? AND " +
CalendarContract.Events.DTSTART + "<=?",
new String[]{title, String.valueOf(oneMinuteBefore), String.valueOf(oneMinuteAfter)},
null
);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
return true; // 已存在
}
} finally {
cursor.close();
}
}
return false;
}
/** 插入日历事件 + 提醒 */
private static boolean addEvent(Context context,
String title,
String description,
String location,
long beginTimeMillis,
long endTimeMillis,
int reminderMinutes) {
long calId = getCalendarAccountId(context);
if (calId == -1) {
Log.e(TAG, "没有找到系统日历账户,请先在系统日历中添加一个账户");
return false;
}
if (isEventAlreadyExists(context, title, beginTimeMillis)) {
Log.w(TAG, "事件已存在,跳过添加: " + title);
return false;
}
ContentValues eventValues = new ContentValues();
eventValues.put(CalendarContract.Events.CALENDAR_ID, calId);
eventValues.put(CalendarContract.Events.TITLE, TextUtils.isEmpty(title) ? "未命名事件" : title);
eventValues.put(CalendarContract.Events.DESCRIPTION, description);
eventValues.put(CalendarContract.Events.EVENT_LOCATION, location);
eventValues.put(CalendarContract.Events.DTSTART, beginTimeMillis);
eventValues.put(CalendarContract.Events.DTEND, endTimeMillis);
eventValues.put(CalendarContract.Events.HAS_ALARM, 1);
eventValues.put(CalendarContract.Events.EVENT_TIMEZONE, TimeZone.getDefault().getID());
Uri newEvent = context.getContentResolver().insert(CalendarContract.Events.CONTENT_URI, eventValues);
if (newEvent == null) {
Log.e(TAG, "插入日历事件失败");
return false;
}
long eventId = ContentUris.parseId(newEvent);
ContentValues reminderValues = new ContentValues();
reminderValues.put(CalendarContract.Reminders.EVENT_ID, eventId);
reminderValues.put(CalendarContract.Reminders.MINUTES, reminderMinutes);
reminderValues.put(CalendarContract.Reminders.METHOD, CalendarContract.Reminders.METHOD_ALERT);
Uri reminderUri = context.getContentResolver().insert(CalendarContract.Reminders.CONTENT_URI, reminderValues);
if (reminderUri == null) {
Log.e(TAG, "插入提醒失败");
return false;
}
Log.i(TAG, "日历事件添加成功,eventId=" + eventId);
return true;
}
public static void creatroCalendarReminder(Context context,String data){
try {
// 将传入的字符串转成 JSON 对象
JSONObject json = new JSONObject(data);
// 从 JSON 中取字段,如果没有就用默认值
String title = json.optString("title", "测试");
String description = json.optString("description", "测试");
String location = json.optString("location", "测试");
int startHour = json.optInt("startHour", 1);
int startMinute = json.optInt("startMinute", 10);
int endHour = json.optInt("endHour", startHour + 1);
Calendar begin = Calendar.getInstance();
begin.add(Calendar.DAY_OF_MONTH, 0); // 哪天开始,Calendar.DAY_OF_MONTH当前时间 + 后面参数值,比如我这里为0,就是今天,如果为1就是明天
begin.set(Calendar.HOUR_OF_DAY, startHour); // 开始的小时,这里是24小时制 startHour的取值范围为0~23
begin.set(Calendar.MINUTE, startMinute); // 开始的分钟
Calendar end = (Calendar) begin.clone();
end.set(Calendar.HOUR_OF_DAY, endHour); // 结束的时间,参数和上面开始时间一样,赋值方式为end.set()
if (context == null) {
Log.e("Calendar", "Context is null");
return;
}
// 添加事件
CalendarHelper.addEventWithPermission(
(Activity) context,
title,
description,
location,
begin.getTimeInMillis(), //事件开始时间的毫秒值
end.getTimeInMillis(), //事件结束时间的毫秒值
5 // 提前5分钟提醒
);
} catch (JSONException e) {
e.printStackTrace();
Log.e("Calendar", "JSON解析失败:" + data);
}
}
/**
* 从外部传提示文本过来
* @param message
*/
public static void setDenyPermissionMessage(String message) {
if (!TextUtils.isEmpty(message)) {
denyPermissionMessage = message;
}
}
}
Activity中的逻辑
先在Activity
中引入CalendarHelper
类,并调用CalendarHelper.creatroCalendarReminder()
方法,传入参数,实现日历添加功能。
java
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
// 处理日历权限
if (requestCode == CalendarHelper.REQUEST_CALENDAR_PERMISSION) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
CalendarHelper.onRequestPermissionsResultCalendar(this, requestCode, grantResults);
}
}
}
添加创建日历提醒事件和传入提示文本
java
/**
* 创建日历提醒事件
* @param data
*/
public static void creatroCalendarReminder(String data){
Context context = AppActivity.getInstance();
CalendarHelper.creatroCalendarReminder(context,data);
}
/**
* 获取读取日历权限被拒绝时的提示文本
* @param str
*/
public static void setDenyPermissionMessage(String str){
CalendarHelper.setDenyPermissionMessage(str);
}