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);
    }
相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui