前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >笔记78 | 解读一个闹钟代码

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

作者头像
项勇
发布2018-08-01 14:38:55
4730
发布2018-08-01 14:38:55
举报
文章被收录于专栏:项勇项勇

效果图:

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

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

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

先从编号2开始看:

核心代码:

代码语言:javascript
复制
@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方法;

代码语言:javascript
复制
/×
×将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并发送对应的广播,关闭的好理解,仔细看看开启:

代码语言:javascript
复制
/×
×启动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方法:

代码语言:javascript
复制
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:

代码语言:javascript
复制
private SharedPreferenceUtil spUtil;
public boolean isShake() {
        return spUtil.getBoolean(ALARM_SETTING_SHAKE , true);
    }

编号6:

代码语言:javascript
复制
/**
     * 读取boolean类型值,默认为false;
     * 
     * @param key
     * @return
     */
    public boolean getBoolean(String key, boolean deafultValue) {
        return sharedPreferences.getBoolean(key, deafultValue);
    }

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

代码语言:javascript
复制
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中,可以看出,大神将周一至周日,组成一个二进制数据:

代码语言:javascript
复制
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

代码语言:javascript
复制
AlarmOpreation.cancelAlert(context,type);
                AlarmOpreation.enableAlert(context, type, alarmsSetting);

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

代码语言:javascript
复制
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,显示对应的开始和结束操作就可以了;

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-07-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 项勇 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档