案例
场景:商城会在10点发起对一件商品的秒杀,要求客户端在10点之前五分钟通知到客户打开app准备参与秒杀活动
面对这一需求是否我们第一反应就是如果程序被关闭了如何通知到用户呢?是否需要设计程序保活的方案?是否无法做到这个需求?这篇文章主要按照这个需求来说一说这样的产品需求下,app应该如何优雅的应对。
写在前面
android风风雨雨一路过来,正在变得越来越完善,越来越规范。从这一路走过来的程序员相信都面临过:一方面来自自家公司产品要求准时提醒用户做也一些业务(如参加抢购活动),这就涉及到了 程序保活
的问题,另一方面面临这国产手机
各种白名单
以外的,后台进程的意外回收(主要手机厂商处于省电,和节省内存这方面来考虑)。可以看到网上各种程序保活的方案屡见不鲜。
其实稍懂得操作系统的程序员都知道,android的普通应用只是运行在系统单独分配的独立的 “小空间” 里,级别要比系统应用的权限低。所以说到底猴子是跳不出如来佛祖的手掌心的,如果跳出来了那就是手机系统的bug
其实我们普通的应用程序如果想运行的稳定,最好的方式还是中规中矩的按照系统要求来,同时去想一些折中的用户可以接受的一些方案。啰嗦了这么多,主要来说一下今天的主题为app添加日历事件
为什么要添加日历事件,有哪些好处
前面说了一堆,其实只有一个主题就是背着系统偷偷干的坏事,不是长久之计,增加了app的异常风险。而系统日历事件提醒这个功能,确实是在系统允许的范围之内,允许第三方应用保存通知事件在日历的程序中。这个是从2.2以上的系统就已经有的功能。
- 好处1:可以不用在考虑那烦人不靠谱的保活的方案
- 好处2:代码实现非常方便,而且系统应用通知会更稳定
- 好处3:用户可以接受 ,这一点很关键
这里大概列一些常用的一些保活方案,读者有兴趣可以逐个去尝试
- 双进程互相监控存活状态,并重启
- 利用第三方的推送服务
- 前台service
- AlarmClock方案
- 注册系统广播如开机,开关网,来电等等注册一堆然后来唤醒。
等等…
添加日历事件方案
下面内容我就默认读者已经知道什么是日历事件了,如果不知道的请打开自己的手机日历,然后选择日期并且添加选择日期的提醒事件熟悉一下具体的流程。这里说的只是不通过程序来实现其他程序往日历程序里面添加提醒事件
相关数据表
日历事件的操作,说到底就是对日历应用做增删改查的数据库操作,这里就用到了ContentProvider
,跨应用操作数据库,主要涉及以下几张数据表(找个模拟器直接导出calendar.db):
如上的几张表分别对应关系如下图示1
:
一个用户可以拥有多个 Calendar,每个 Calendar 可以与不同类型的帐号关联(Google Calendar、Exchange 等)。
CalendarContract 定义了 Calendar 和 Event 的数据模型。这些数据存放在以下数据表中。
数据表 | 说明 |
---|---|
Calendars | 该表存放日程的定义数据。每行表示一条日程的详细信息,如名称、颜色、同步信息等。 |
Events | 该表存放事件的定义数据。每行表示一个事件,内容包括 — 事件标题、位置、起始时间、结束时间等等。 事件可以是一次性的,也可以重复多次触发。 参与人员、提醒闹钟及附加属性都存放在其他表中,并通过 EVENT_ID 字段与 Events 表中的 _ID 关联。 |
Instances | 该表存放事件每次触发时的起始时间和结束时间。一次性事件只会1:1对应一条实例记录。 对于重复触发的事件而言,则会自动生成多条实例记录,对应每一次的触发。 |
Reminders | 该表存放闹钟/通知数据。每行代表一次闹钟提醒。 一个事件可以拥有多个闹钟提醒。每个事件可拥有的最大提醒数在 MAX_REMINDERS 中定义,这是由拥有该日程的 sync adapter 设置的。 提醒定义了事件触发前的分钟数,以及提醒用户的方式。 |
我们下面简单的介绍一下使用calendars,events,reninders三张张表来实现事件的新增
calendars
表结构
字段名称 | 含义 | 类型 |
---|---|---|
account_type | 账户类型 | TEXT |
account_name | 账户名称 | TEXT |
calendar_displayName | TEXT | |
calendar_color | 日历显示的颜色背景 | int(RGB) |
visible | 0不可见1可见 | INTEGER |
calendar_timezone | 日历时区 | TEXT |
sync_events | 0不同步1同步 | TEXT |
sync_events | 最大提醒次数 | int |
— | — | — |
events
表结构
字段名称 | 含义 | 类型 |
---|---|---|
calendar_id | 账户id对应calendars表的id | INTEGER |
dtstart | 开始时间戳 | INTEGER |
dtend | 结束时间戳 | INTEGER |
title | 事件标题 | TEXT |
description | 事件描述 | TEXT |
eventLocation | 事件发生的位置(如果设置的话可以直接点击可进入地图) | TEXT |
eventTimezone | 时区,使用系统默认即可 | TEXT |
eventStatus | 时间状态设置1为正常 | INTEGER |
rrule | 提醒重复规则(FREQ=DAILY;UNTIL=结束时间(yyyyMMdd)+T235959Z(T开头Z结尾即可)),详细的设置我们可以在看资料 | TEXT |
— | — | — |
reminders
表结构
字段名称 | 含义 | 类型 |
---|---|---|
eventID | events表对应的id | Integer |
minutes | 提前几分钟通知 | Integer |
method | 通知方式(见下图) | Integer |
添加权限
- Manifest添加对日历数据的读写权限
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
- 动态权限申请
6.0之后的动态权限检查这块也是不可少的,操作之前要做权限检查
private boolean checkPromession(){
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_CALENDAR) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.WRITE_CALENDAR,
Manifest.permission.READ_CALENDAR}, 1);
return false;
}
return true;
}
日历账户
日历账户这个必须是要有,如果没有的话日历事件就没有归属,相当于我们要创建的事件归属于哪个账户下
/**
* 检查是否存在日历账户
* @return 存在:日历账户ID 不存在:-1
*/
private static long checkCalendarAccount(Context context) {
try (Cursor cursor = context.getContentResolver().query(CalendarContract.Calendars.CONTENT_URI,
null, null, null, null)) {
// 不存在日历账户
if (null == cursor) {
return -1;
}
int count = cursor.getCount();
// 存在日历账户,获取第一个账户的ID
if (count > 0) {
cursor.moveToFirst();
return cursor.getInt(cursor.getColumnIndex(CalendarContract.Calendars._ID))