功能

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

需要的权限

AndroidManifest.xml里添加

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

注意: 如果是Android 6.0(API 23)以上,需要动态申请权限。

代码

创建一个CalendarHelper工具类,包含:

  • 获取系统日历账户
  • 自动写入事件
  • 添加提醒
  • 自动处理没有日历账户的情况(可提示用户手动创建)
  • 动态申请权限(当用户拒绝权限时,我这里会弹出一个提示框,提示的内容可以从外部传入,也可以使用默认的。或者你不是使用默认,直接打开系统的设置页面也是可以的(下面屏蔽了这部分的代码))
  • 判断是否已存在相同时间的逻辑,避免重复添加
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~23begin.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()方法,传入参数,实现日历添加功能。

    @Overridepublic 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);}}}

添加创建日历提醒事件和传入提示文本

    /*** 创建日历提醒事件* @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);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/921944.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/921944.shtml
英文地址,请注明出处:http://en.pswp.cn/news/921944.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

‌Git Bisect 二分查找定位错误总结

# Git Bisect 二分查找指南## 1. 基本原理&#xff08;ASCII示意图&#xff09; 假设提交历史是一条时间线&#xff0c;Ggood&#xff08;正常&#xff09;&#xff0c;Bbad&#xff08;异常&#xff09;&#xff1a;提交顺序: G --- G --- G --- B --- B --- B | | | 初始正常…

ThingsKit物联网平台 v2.0.0 发布|前端UI重构、底层架构升级

v2.0.0 Release发布日期&#xff1a;2025/08/25 代码标签&#xff1a;v2.0.0_Release&#x1f947; 新增功能国标级联&#xff08;支持上级、下级国标级联&#xff09;视频回放、录像计划&#xff08;用户可以通过录像计划生成对应的视频回放并查看&#xff09;Modbus_TCP协…

Lua > Mac Mini M4安装openresty

Mac Mini M4安装openresty 主要参考 https://www.cnblogs.com/helios-fz/p/15703260.html brew uninstall nginxbrew update brew install pcre openssl #brew install geoip# brew tap openresty/brew # brew install openresty # brew install openresty/brew/openresty# VER…

【多线程案例】:单例模式

多线程案例8.1 单例模式饿汉模式懒汉模式懒汉模式-单线程版懒汉模式-多线程版懒汉模式-多线程版(改进)8.1 单例模式 单个实例. 在一个 java 进程中, 要求指定的类,只能有唯–个实例。&#xff08;尝试 new 多个实例的时候, 就会直接编译报错&#xff09; 单例模式是校招中最常…

【Python/Pytorch】-- 贝叶斯定理

文章目录 文章目录01 贝叶斯定理的理解02 在MRI重建领域应用01 贝叶斯定理的理解 贝叶斯定理的基本公式&#xff1a;P(A|B)P(B|A)*P(A) / P(B) 首先是如何理解这个公式&#xff1f; 在B事件发生的条件下&#xff0c;A发生的概率 P(A|B) 在B事件发生的条件下&#xff0c;A和B同…

子网掩码的隐形陷阱:为何能ping通却无法HTTPS访问

问题现象深度解析在近期企业网络维护中&#xff0c;运维团队发现一个具有教学意义的典型案例&#xff1a;某台部署在10.165.111.0/24网段的业务服务器&#xff08;10.165.111.71&#xff09;可以成功ping通目标中间件主机(10.165.110.11)&#xff0c;但通过HTTPS协议访问https:…

【ArcGIS】如何编辑图层的属性表

GIS按属性选择后删除所选项呈现灰色_arcgis删除字段灰色-CSDN博客

大数据各组件flume,datax,presto,DolphinScheduler,findBI在大数据数仓架构中的作用和功能。

一、数据仓库核心价值铺垫在讲具体技术前&#xff0c;先明确数据仓库&#xff08;Data Warehouse&#xff0c;简称数仓&#xff09; 的核心作用&#xff1a; 数据仓库是 “整合企业多源数据、按业务主题组织、支持决策分析” 的结构化数据存储体系&#xff0c;核心价值是打破数…

React From表单使用Formik和yup进行校验

一、Formik的使用 官方文档地址&#xff1a;https://formik.org/docs/tutorial#validation 首先安装依赖 yarn add formik2.导入并初始化 import { useFormik } from formik; initialValues&#xff1a;初始化 输入框的密码和账号 onSubmit&#xff1a;当点击提交按钮时&am…

netty-scoket.io路径配置

1、服务端代码 package com.yh.service.socket;import com.corundumstudio.socketio.SocketIOServer; import com.corundumstudio.socketio.store.RedissonStoreFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory…

20250910荣品RD-RK3588-MID开发板在Android13系统下解决点卡迪的屏闪屏的问题

20250910荣品RD-RK3588-MID开发板在Android13系统下解决点卡迪的屏闪屏的问题 2025/9/5 15:44缘起&#xff1a;荣品RD-RK3588-MID开发板在Android13系统下解决点卡迪的屏。 按 POWER按键 关机之后&#xff0c;2s之内再次短按 POWER按键&#xff0c;开机之后屏会抖动。 2s后短按…

正态分布 - 计算 Z-Score 的 无偏估计

正态分布 - 计算 Z-Score 的 无偏估计 flyfish Z-Score公式与计算步骤 1 公式&#xff08;样本Z-Score&#xff09; 实际应用中&#xff0c;我们几乎不知道“总体均值/标准差”&#xff0c;所以常用样本数据计算&#xff1a; zixi−xˉsz_i \frac{x_i - \bar{x}}{s}zi​sxi​−…

ai生成文章,流式传输(uniapp,微信小程序)

1.环境nutui-uniappvue3tsunocss2.功能源码包含ai生成逻辑&#xff0c;内容生成实时打字机功能&#xff0c;ai数据处理等<script setup lang"ts"> import {queryAIParams, } from /api/pagesA import { submitFn } from /api/aiimport Navbar from /component…

Linux设备内存不足如何处理

[rootlocalhost ~]# free -mtotal used free shared buff/cache available Mem: 31208 14317 1280 1551 15610 14657 Swap: 15927 2781 13146 [rootlocalhost ~]#从 free -m 输出来看&…

中间件八股

文章目录RedisRedis为什么快&#xff1f;Redis Redis为什么快&#xff1f; 首先它是内存数据库&#xff0c;所有数据直接操作内存而非磁盘&#xff0c;避免了 I/O 瓶颈&#xff1b;其次采用单线程模型&#xff0c;消除了多线程切换的开销&#xff0c;同时通过非阻塞 I/O 多路…

【参数详解与使用指南】PyTorch MNIST数据集加载

# 加载MNIST数据集 train_dataset datasets.MNIST(root./data, trainTrue, downloadTrue, transformtransform) # 下载训练集 test_dataset datasets.MNIST(root./data, trainFalse, downloadTrue, transformtransform) # 下载测试集在深度学习入门过程中&#xff0c;MNIST手…

闭包面试题

闭包&#xff08;Closure&#xff09; 是指一个函数能够记住并访问其词法作用域&#xff08;定义时的作用域&#xff09;&#xff0c;即使该函数在其词法作用域之外执行。一、通俗理解&#xff08;面试可这样开头&#xff09;&#xff1a;> 闭包就是一个函数“记住”了它出生…

WebSocket 双向通信实战:SCADA 移动端实时操控响应优化

引言&#xff1a;SCADA 移动端的 “延迟烦恼” 与破局之道在电力调度、水厂监控、智能制造等场景中&#xff0c;SCADA 系统&#xff08;数据采集与监视控制系统&#xff09;是当之无愧的 “工业指挥官”—— 它能实时采集设备运行数据&#xff08;如电网负荷、水泵压力、机床转…

SafeEar:浙大和清华联合推出的AI音频伪造检测框架,错误率低至2.02%

本文转载自&#xff1a;https://www.hello123.com/safeear ** 一、&#x1f512; SafeEar&#xff1a;你的声音 “防火墙”&#xff0c;让 AI 伪造音频无所遁形 担心自己的声音被 AI 模仿甚至伪造&#xff1f;SafeEar就是来帮你解决这个难题的&#xff01;它是由浙江大学和清…

uni-app iOS 日志与崩溃分析全流程 多工具协作的实战指南

在 uni-app 跨平台开发中&#xff0c;iOS 应用的日志与崩溃分析往往是开发者最头疼的问题。 日志分散&#xff1a;uni-app 的 JS 日志、原生插件日志、系统日志分布在不同位置&#xff1b;崩溃难复现&#xff1a;用户反馈的崩溃往往无法在开发机还原&#xff1b;符号化复杂&…