笔记78 | 解读一个闹钟代码

效果图:

最近因工作需要做一个定时器,一看需求,深思极恐: 1.定时发送开关指令; 2.可设置周期循环; 这不就是一个标准的闹钟吗? 哎呀,烧脑~

还好有GITHUB, 拥有git爸,走到哪里都不怕! 下面看代码,看看这个闹钟是怎么实现的!

编号1:是处理弹出提示窗口的一个Activity; 编号2:Main类,设置时间周期等操作 编号3:核心类,负责计算周期时间,然后将时间通过AlarmManager发送定时广播; 编号4:广播类,负责处理3发送的广播类型,弹出1; 编号5:设置的时间信息的存取类; 编号6:配合5的一个SharedPreferenceUtil类; 编号7:设置时间的工具类; 编号8:设置星期的工具类; 面向对象编程的概念是:你办事,我放心! 从这个程序里,可以体现一二,大家都是各司其职!

先从编号2开始看:

核心代码:

@Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.switch_in://开始时间开关
                if(v.isSelected()) {
                    alarmsSetting.setInEnble(false);
                    v.setSelected(false);
                    AlarmOpreation.cancelAlert(AlarmMainActivity.this, AlarmsSetting.ALARM_SETTING_TYPE_IN);
                }else {
                    alarmsSetting.setInEnble(true);
                    v.setSelected(true);
                    AlarmOpreation.enableAlert(AlarmMainActivity.this, AlarmsSetting.ALARM_SETTING_TYPE_IN, alarmsSetting);
                }
                break;
            case R.id.switch_out://结束时间开关
                if(v.isSelected()) {
                    alarmsSetting.setOutEnble(false);
                    v.setSelected(false);
                    AlarmOpreation.cancelAlert(AlarmMainActivity.this, AlarmsSetting.ALARM_SETTING_TYPE_OUT);
                }else {
                    alarmsSetting.setOutEnble(true);
                    v.setSelected(true);
                    AlarmOpreation.enableAlert(AlarmMainActivity.this, AlarmsSetting.ALARM_SETTING_TYPE_OUT, alarmsSetting);
                }
                break;
            case R.id.set_in_time:
                showTimePickerDialog(AlarmsSetting.ALARM_SETTING_TYPE_IN); //设置开始时间
                break;
            case R.id.set_out_time:
                showTimePickerDialog(AlarmsSetting.ALARM_SETTING_TYPE_OUT);//设置结束时间
                break;
        }
    }

是的,此君是BOS,核心内容就是几个指挥操作! 开始结束时间开关: 可以看到是将不同的 ALARM_SETTING_TYPE值发送给了 AlarmOpreationcancelAlert方法;

/×
×将AlarmManager注销
×/
    public static void cancelAlert(Context context, int type) {
//        Log.e("<<<<<<<<<<<<<<<<<", "cancelAlert");
        AlarmManager mAlarmManager = (AlarmManager)
                context.getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(AlarmsSetting.ALARM_ALERT_ACTION);
        intent.putExtra("type", type);
        intent.setClass(context, AlarmReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(context, type, intent,
                PendingIntent.FLAG_CANCEL_CURRENT);
        mAlarmManager.cancel(pi);
    }

也就是分别开启和关闭AlarmManager并发送对应的广播,关闭的好理解,仔细看看开启:

/×
×启动AlarmManager
×/
 public static void enableAlert(Context context, int type, AlarmsSetting alarmsSetting) {
//        Log.e("<<<<<<<<<<<<<<<<<", "enableAlert");
        if(type==AlarmsSetting.ALARM_SETTING_TYPE_IN && !alarmsSetting.isInEnble()){
            return ;
        }else if(type==AlarmsSetting.ALARM_SETTING_TYPE_OUT && !alarmsSetting.isOutEnble()){
            return;
        }
        AlarmManager mAlarmManager = (AlarmManager)
                context.getSystemService(Context.ALARM_SERVICE);
        int hours = 0,minute=0,dayOfweek=0;
        if(type==AlarmsSetting.ALARM_SETTING_TYPE_IN){
            hours = alarmsSetting.getInHour();
            minute=alarmsSetting.getInMinutes();
            dayOfweek = alarmsSetting.getInDays();
        }else if(type==AlarmsSetting.ALARM_SETTING_TYPE_OUT){
            hours = alarmsSetting.getOutHour();
            minute=alarmsSetting.getOutMinutes();
            dayOfweek=alarmsSetting.getOutDays();
        }
        Calendar mCalendar = cacluteNextAlarm(hours, minute, dayOfweek);
//        Log.e("<<<<<<<<<<<<<<<<<", "alarmsSetting" + alarmsSetting.getInHour() + "-" + alarmsSetting.getInMinutes());
//        Log.e("<<<<<<<<<<<<<<<<<", " mCalendar" + mCalendar.get(Calendar.DAY_OF_WEEK));
        if (mCalendar.getTimeInMillis() < System.currentTimeMillis()) {
            Log.e("!!!!!!!!!!!","setAlarm FAIL:设置时间不能小于当前系统时间,本?"+mCalendar.getTimeInMillis()+"闹钟无效");
            return;
        }

        Log.i("md", "type "+type);

        Intent intent = new Intent(AlarmsSetting.ALARM_ALERT_ACTION);
        intent.putExtra("type", type);
        intent.setClass(context, AlarmReceiver.class);
        PendingIntent pi = PendingIntent.getBroadcast(context, type, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        mAlarmManager.set(AlarmManager.RTC_WAKEUP, mCalendar.getTimeInMillis(), pi);
        alarmsSetting.setNextAlarm(mCalendar.getTimeInMillis());
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy年MM月dd日??HH时mm分ss秒SSS毫秒");
        Log.e("###########闹钟时间#######", "alarmsSetting.getNextAlarm()" + formatter.format(new Date(alarmsSetting.getNextAlarm())));
    }

一行一行看下去就知道,主要是将存储好的时间设置信息(小时,分钟,星期),通过 cacluteNextAlarm方法设置成一个特殊的 Calendar值用于定时,然后将对应的 typeAction组成一个通过广播 pi!通过 AlarmManagerset方法定时, mAlarmManager.set(AlarmManager.RTC_WAKEUP,mCalendar.getTimeInMillis(),pi);,定时将 pi中的内容发送出去! 核心就是这样!

知道了核心,首先需要知道大神是怎么将时间处理成周期信息的!cacluteNextAlarm方法:

Calendar mCalendar = Calendar.getInstance();
        mCalendar.setTimeInMillis(System.currentTimeMillis());
        mCalendar.set(Calendar.HOUR_OF_DAY,hour);
        mCalendar.set(Calendar.MINUTE, minute);
        int differDays = getNextAlarmDifferDays(dayOfweek,mCalendar.get(Calendar.DAY_OF_WEEK), mCalendar.getTimeInMillis());
        int nextYear = getNextAlarmYear(mCalendar.get(Calendar.YEAR), mCalendar.get(Calendar.DAY_OF_YEAR), mCalendar.getActualMaximum(Calendar.DAY_OF_YEAR), differDays);
        int nextMonth = getNextAlarmMonth(mCalendar.get(Calendar.MONTH), mCalendar.get(Calendar.DAY_OF_MONTH), mCalendar.getActualMaximum(Calendar.DATE), differDays);
        int nextDay = getNextAlarmDay(mCalendar.get(Calendar.DAY_OF_MONTH), mCalendar.getActualMaximum(Calendar.DATE), differDays);
        mCalendar.set(Calendar.YEAR,nextYear);
        mCalendar.set(Calendar.MONTH, nextMonth % 12);//月份
        mCalendar.set(Calendar.DAY_OF_MONTH, nextDay);
        mCalendar.set(Calendar.SECOND, 0);
        mCalendar.set(Calendar.MILLISECOND, 0);
        return mCalendar;
    }


    //获取下次闹钟相差的天?
    private static int getNextAlarmDifferDays(int data, int currentDayOfWeek,long timeInMills){
        int nextDayOfWeek =  getNextDayOfWeek(data, currentDayOfWeek,timeInMills);
        return currentDayOfWeek<=nextDayOfWeek?(nextDayOfWeek-currentDayOfWeek):(7 - currentDayOfWeek + nextDayOfWeek);
    }


    //考虑年进位的情况
    private static int getNextAlarmYear(int year,int dayOfYears, int actualMaximum, int differDays) {
        int temp = actualMaximum-dayOfYears-differDays;
        return temp >= 0?year:year+1;
    }

    //考虑月进位的情况
    private static int getNextAlarmMonth(int month,int dayOfMonth,int actualMaximum, int differDays) {
        int temp = actualMaximum-dayOfMonth-differDays;
        return temp >= 0?month:month+1;
    }

    //获取下次闹钟的day
    private static int getNextAlarmDay(int thisDayOfMonth, int actualMaximum, int differDays) {
        int temp = actualMaximum - thisDayOfMonth-differDays;
        if (temp<0){
            return thisDayOfMonth + differDays - actualMaximum;
        }
        return thisDayOfMonth + differDays;
    }

    //获取下次显示是星期几
    private static int getNextDayOfWeek(int data, int cWeek,long timeInMillis) {
        int tempBack = data >> cWeek - 1;
        int tempFront = data ;

        if(tempBack%2==1){
            if(System.currentTimeMillis()<timeInMillis)  return cWeek;
        }
        tempBack = tempBack>>1;
        int m=1,n=0;
        while (tempBack != 0) {
            if (tempBack % 2 == 1 ) return cWeek + m;
            tempBack = tempBack / 2;
            m++;
        }
        while(n<cWeek){
            if (tempFront % 2 == 1)  return n+1;
            tempFront =tempFront/2;
            n++;
        }
        return 0;
    }

大神写的计算的时间周期等方法,理解好了就好!

编号5和6是两个互相配合存储数据的基友,一个实现具体操作,一个提供存取方法! 编号5:

private SharedPreferenceUtil spUtil;
public boolean isShake() {
        return spUtil.getBoolean(ALARM_SETTING_SHAKE , true);
    }

编号6:

/**
     * 读取boolean类型值,默认为false;
     * 
     * @param key
     * @return
     */
    public boolean getBoolean(String key, boolean deafultValue) {
        return sharedPreferences.getBoolean(key, deafultValue);
    }

看到这,然后回到编号2类中: 刚刚是从开始结束时间开关一直往下看,就基本打通的这个程序主心轴,其他的基本都是簇拥在这个轴心旁的东西: 设置时间按钮:

public void showTimePickerDialog(final int type){
        TimePickerFragment  timePicker = new TimePickerFragment();
        if(type==AlarmsSetting.ALARM_SETTING_TYPE_IN) {
            timePicker.setTime(alarmsSetting.getInHour(),alarmsSetting.getInMinutes());
        }else{
            timePicker.setTime(alarmsSetting.getOutHour(), alarmsSetting.getOutMinutes());
        }
        timePicker.show(getFragmentManager(),"timePicker" );
        timePicker.setOnSelectListener(new TimePickerFragment.OnSelectListener() {
            @Override
            public void getValue(int hourOfDay, int minute) {
                if(type==AlarmsSetting.ALARM_SETTING_TYPE_IN) {
                    alarmsSetting.setInHour(hourOfDay);
                    alarmsSetting.setInMinutes(minute);
                }else{
                    alarmsSetting.setOutHour(hourOfDay);
                    alarmsSetting.setOutMinutes(minute);
                }
                setTime(hourOfDay, minute, type);
                AlarmOpreation.cancelAlert(AlarmMainActivity.this,type);
                AlarmOpreation.enableAlert(AlarmMainActivity.this,type,alarmsSetting);
            }
        });
    }

弹出一个 TimePickerFragment窗口,并将获得的小时和分钟存起来 setInHour``setInMinutes而这个时间弹出窗口就是编号7: 此类实质上就是继承至 DialogFragment调用 TimePickerDialog向外提供获取小时和分钟的接口! 而星期的周期复杂些,此行星期选项列表是一排 GridView,编号8就是它的 Adapter,在构造方法的 GetView中,可以看出,大神将周一至周日,组成一个二进制数据:

if(v.isSelected()){
                    selected = selected - (int)(1 << position);
                    if(selected <= 0) {
                        selected = selected + (int)(1 << position);
                        return ;
                    }
                    v.setSelected(false);
                }else{
                    selected = selected + (int)(1 << position);
                    v.setSelected(true);
                }

并将最终组成的数据发送给了 AlarmOpreation

AlarmOpreation.cancelAlert(context,type);
                AlarmOpreation.enableAlert(context, type, alarmsSetting);

这样一路看下来,从设置时间到组成时间到发送时间,基本就差不多了,下面就是编号4 AlarmReceiver接受定时发送过来的广播内容了:

if(intent.getAction().equals(AlarmsSetting.ALARM_ALERT_ACTION) && type !=0) {
...
intent.setClass(context, AlarmAlertActivity.class);
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        context.startActivity(intent);
}

判断 ATIONtype,启动编号1 AlarmAlertActivity,显示对应的开始和结束操作就可以了;

今天感冒了流鼻涕打喷嚏,整理得比较凌乱,看官们将就一下,有什么问题,或者需要源码的可以留言!

原文发布于微信公众号 - 项勇(xiangy_life)

原文发表时间:2018-07-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Coding01

深入浅出 Laravel Echo (3)

看完 public channel 的流程,我们该来说说怎么跑通 private channel 了。

2463
来自专栏GopherCoder

『No19: Gorm 上手指南』

如果你是做后端开发的,日常工作中,除了熟悉编程语言之外,数据库怕是最常用的技术了吧。

6071
来自专栏猿湿Xoong

Android SystemUI(二):启动流程和初始化

2574
来自专栏机器人网

一文教你从PLC编程菜鸟变成高手

PLC编程软件由系统程序和用户程序两部分组成。系统程序包括监控程序、编译程序、诊断程序等,主要用于管理全机、将程序语言翻译成机器语言,诊断机器故障。PLC编程软...

5185
来自专栏技术墨客

Spring核心——上下文与IoC 原

前面3篇分别介绍了IoC容器与Bean的关系、Bean与Bean之间的关系以及Bean自身的控制和管理。在了解Spinrg核心模式时,一定要谨记他的基本工作元素...

923
来自专栏互联网技术栈

Netflix Archaius 分布式配置管理依赖构件

archaius是Netflix公司开源项目之一,基于java的配置管理类库,主要用于多配置存储的动态获取。主要功能是对apache common config...

1422
来自专栏一个番茄说

Swift中防止ptrace依附

在移动开发中,安全是一个很重要的话题,当然安全是没有绝对的,只能说尽可能的提高安全性。在iOS的开发中,为了防止别人窥视我们的App,我们得采用一些手段来进行防...

1153
来自专栏Java后端技术栈

【面试题】2018年最全Java面试通关秘籍第三套!

注:本文是从众多面试者的面试经验中整理而来,其中不少是本人出的一些题目,网络资源众多,如有雷同,纯属巧合!禁止一切形式的碰瓷行为!未经允许禁止一切形式的转载和复...

1251
来自专栏林冠宏的技术文章

关于Android中为什么主线程不会因为Looper.loop()里的死循环卡死?引发的思考,事实可能不是一个 epoll 那么 简单。

( 转载请务必标明出处:https://cloud.tencent.com/developer/user/1148436/activities) 前序 本文将...

3255
来自专栏刘望舒

Android系统层Watchdog机制源码分析

一:为什么需要看门狗? Watchdog,初次见到这个词语是在大学的单片机书上, 谈到了看门狗定时器. 在很早以前那个单片机刚发展的时候, 单片机容易受到外界工...

2507

扫码关注云+社区

领取腾讯云代金券