Android应用添加日历提醒功能

功能

在安卓应用里调用系统日历,直接创建一个带提醒的日历事件,甚至不需要跳转到日历界面,只需要获取系统日历的读取权限即可。

需要的权限

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);
    }
相关推荐
骐骥13 小时前
2025-09-08升级问题记录:app提示“此应用专为旧版Android打造..”或“此应用与最新版 Android 不兼容”
android·升级·不兼容·target sdk·专为旧版 android 系统
Zender Han4 小时前
Flutter 视频播放器——flick_video_player 介绍与使用
android·flutter·ios·音视频
尚久龙5 小时前
安卓学习 之 用户登录界面的简单实现
android·运维·服务器·学习·手机·android studio·安卓
Modu_MrLiu5 小时前
Android实战进阶 - 启动页
android·实战进阶·启动页·倒计时场景
出门吃三碗饭6 小时前
编译器构造:从零手写汇编与反汇编程序(一)
android·汇编
Just_Paranoid6 小时前
【WorkManager】无法在 Direct Boot 模式下初始化
android·jetpack·usermanager·workmanager·directboot
前端小超超6 小时前
如何配置capacitor 打包的安卓app固定竖屏展示?
android·前端·gitee
顾林海7 小时前
探秘Android JVM TI:虚拟机背后的"隐形管家"
android·面试·性能优化
刘大国8 小时前
<android>反编译魔改安卓系统应用并替换
android