对于Android万年历中数据提醒问题的探讨 - xulijun6564/Myblog GitHub Wiki

1、背景

提醒类型的数据是万年历活跃的保证,也可以说是万年历的客户端核心。提醒类型有很多:闹钟,日程,节气,节假日,公众提醒,天气变化等等。客户端必须要保证这些数据在后台准时准点能够进行提醒,在李恒研究的service保活的前提下,就是要正确的设置这些数据的提醒时间。

2、Android提醒使用技术

Android中采用系统级别的提醒服务类AlarmManager进行定时提醒,在特定的时刻为我们广播一个指定的Intent。(网上有很多关于这个类的博客,这里就不细讲)http://blog.csdn.net/wangxingwu_314/article/details/8060312 添加提提醒使用的地方:

/**
     * 添加一个提醒, requestCode作为一个唯一的标示 这里传相关bean对象的id值
     */
    private void startPublicNoticeManager(Context ctx, int requestCode, int hour, int minute, int second) {
        AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(ACTION_PUBLIC_NOTICE_SHOW);
        intent.putExtra("id", requestCode);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(ctx,
                UtilsManager.getDateRemindRequestCode(ctx, requestCode, EDataProrerties.RemindDataIDFrom.PUBLIC_NOTICES),
                intent, PendingIntent.FLAG_CANCEL_CURRENT);
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.HOUR_OF_DAY, hour);
        cal.set(Calendar.MINUTE, minute);
        cal.set(Calendar.SECOND, second);
        UtilsManager.setAlarm(am, AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pendingIntent);
    }

从上面代码的注释可以看到,需要一个唯一的requestCode,这个唯一的requestCode使用在初始化PendingIntent当中,初始化参数中又传递了一个自定义的行为,最后这个PendingIntent对象设置给了AlarmManager对象,从而做到到了对应的时间去执行自定义的行为。 PendingIntent这里不做多的介绍:http://blog.csdn.net/yuzhiboyi/article/details/8484771 需要注意:它的三个方法类的静态方法,分别对应着Intent的3个行为,想要使用不同的行为请使用对应的方法。不然达不到想要的效果。 ​最重要的一点:requestCode要唯一

原因:

由于万年历数据有不同的类型,在相同的时间点有可能有不同的提醒数据,那么在这个时候就需要设置多个AlarmManager去进行管理,如果PendingIntent传人的requestCode相同,这个时候后设置的AlarmManager将会覆盖前一个值,这样肯定不符合我们的要求了,因为我们要求这些数据都要进行提醒。 ​

关于requestCode要唯一的问题描述:

现在在万年历当中有很多不同的数据类型,日程,待办,闹钟等是存放在用户数据库表中,当然使用它的数据库唯一id作为requestCode,就能将其分开;对于节气,计算出来的节假日等,这些提醒数据不是在数据库用户数据表当中,这个requestCode怎么定呢?当然比如现在又加了公众提醒数据,又是放在数据库公众提醒表当中,因为数据格式不一样,公众提醒数据和用户数据没有办法合并到一张表当中,这个时候两个数据表的唯一id就有可能相同了,就不能作为唯一的requestCode使用了,假设以后还有更多的需要提醒的数据,存放在了不同的数据表当中,requestCode的唯一性就需要更多的考虑了。 ​

之前线上使用版本的漏洞:

比如一条一天多次的数据,在用户数据表中实际只存入了一条数据,这条数据就对应着一个唯一的id,在数据处理的时候将这条一天多次转换成了多条数据,但是它的id并没有变化,在设置提醒的时候我们设置了这个id作为requestCode的提醒,在弹出提醒的时候,去点击小睡10分钟,也就是这条数据10分钟后将进行再次提醒。如果这条一天多次数据是5分钟后会再提醒一次的,那么在设置提醒的时候这条小睡10分钟就会将理应5分钟后提醒的这次提醒给覆盖了,将不再提醒。 由于这种数据现在已经没有了,并且就算有覆盖,还是会提醒小睡的那次,暂且先不予解决了。 ​

之前线上使用版本的不足:

在设置节气等不在用户数据表中的数据的时候,全用的固定死的负数来做唯一requestCode,那么我们必须要记住哪里使用的是哪个数值来处理,不能重复,在之前的代码中都赋值到了-90000,从原理上解决了唯一性,但是这种是不符合长久性考虑的,代码的可读性也差了很多。由于使用的地方太多,暂且不也不去处理使用到负数的地方。 ​

当前版本(6.6.0发生):

在这个版本加入了公众提醒,又增加了一种数据类型和数据表的情况,或者以后会增加更多个数据类型和数据表。 ​

解决办法之前:

由于有两个表中id了,但是它们可能相同,要让它们不同,我们考虑过很多方式,1、id使用一个uuid,,但是requestCode必须是int类型,放弃。2、用int的最大值减去一个表的id,另一个表用自己的id,这个int的最大值数值很大,实际上是很难相交的,倘若有三种提醒数据,三张提醒数据表,没办法解决,而且也是会有值相交的漏洞,放弃。3、将数据通过加密的方式,转换成其他数据,比如不同的表有不同的方式,后面发现还是数据会相交的可能,放弃。 ​

解决办法:

实际从原理上讲,如果所有数据放到一张表里,我们使用这个表中的唯一id作为requestCode当然可以达到唯一性,但是我们不可能去做到将其他数据表都合并到之前一张表里面去。那么,通过上面的原理,我们可以在使用id的时候通过一张表来进行聚合得到新的id,相当于是将不同数据表数据放到了一起,只是我们使用到的只有id。 新表的列:唯一id(id), 数据id(data_id), 数据id来自哪个表(data_id_from),这三列足以。 有了这个表,通过数据id和数据id来自哪个表就能查询到唯一的id,将其用做提醒是的requestCode,假设再来一种数据类型和数据类型表,我们只需要新增一个数据id来自哪个表这一列的值就好了,包括节气等等数据,都能通过这个去转换, ​

取消提醒:
 /**
     * 移除一个提醒
     *
     * @param ctx
     * @param requestCode        作为一个唯一的标示 ,这里传相关bean对象的id值
     * @param isNeedDelOtherData
     */
    public static void removeAlarmManager(Context ctx, int requestCode, boolean isNeedDelOtherData) {
        if (isNeedDelOtherData) {
            DBManager db = DBManager.open(ctx);
            db.clearOneDataToEcalendarTableDataOnOtherData(requestCode);
        }
        AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(
                NoticesReceiver.ACTION_SUISENT_GETNEEDREMINDID);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(ctx,
                UtilsManager.getDateRemindRequestCode(ctx, requestCode, EDataProrerties.RemindDataIDFrom.UGC_DATA), intent, 0);
        am.cancel(pendingIntent);
    }

取消提醒就是使用AlarmManager的cancel方法 ###需要注意两点: 1、取消肯定是取消我们设置的提醒,所以我们在取消传人的PendingIntent对象的requestCode要和我们设置的一致 2、在设置提醒的时候的Intent初始化的时候设置了Action 例如: Intent intent = new Intent(NoticesReceiver.ACTION_SUISENT_GETNEEDREMINDID); 在取消的时候必须也要使用设置时相同Action的intent对象,否则不能cancel掉。