Android系统闹钟定时功能框架,总体来说就是用数据库存储定时数据,有一个状态管理器来统一管理这些定时状态的触发和更新。在Andriod系统中实现定时功能,最终还是要用到系统提供的AlarmManager,只是当一个定时完成后怎么继续处理,或者中间怎么更新定时的时间或者状态,像闹钟这种应用程序,每天重复定时,或者一周选择其中的几天,闹钟响了延迟5分钟再次响铃,这时候就需要想一种好的办法来让管理这些数据和状态,下面就分析一下Android系统闹钟的实现。

1、基本结构


Alarm

代表一条定时数据

AlarmInstance

代表一个定时项目的实例,一个AlarmInstance对应到一个Alarm,相比Alarm多存储了一些状态信息

AlarmStateManager

状态管理器,对定时项目进行调度,添加、删除、更改状态,是一个BroadcastReciever,定时到点后发广播到这里进行下一步处理

AlarmService

响应结果,也就是定时到达后要做的事,响铃,停止响铃

ClockDataHelper

里面创建了三个表,ALARMS_TABLE,INSTANCE_TABLE,CITIES_TABLE,前两个分别对应到上面的Alarm和AlarmInstance。

  1. private static void createAlarmsTable(SQLiteDatabase db) {
  2. db.execSQL("CREATE TABLE " + ALARMS_TABLE_NAME + " (" +
  3. ClockContract.AlarmsColumns._ID + " INTEGER PRIMARY KEY," +
  4. ClockContract.AlarmsColumns.HOUR + " INTEGER NOT NULL, " +
  5. ClockContract.AlarmsColumns.MINUTES + " INTEGER NOT NULL, " +
  6. ClockContract.AlarmsColumns.DAYS_OF_WEEK + " INTEGER NOT NULL, " +
  7. ClockContract.AlarmsColumns.ENABLED + " INTEGER NOT NULL, " +
  8. ClockContract.AlarmsColumns.VIBRATE + " INTEGER NOT NULL, " +
  9. ClockContract.AlarmsColumns.LABEL + " TEXT NOT NULL, " +
  10. ClockContract.AlarmsColumns.RINGTONE + " TEXT, " +
  11. ClockContract.AlarmsColumns.DELETE_AFTER_USE + " INTEGER NOT NULL DEFAULT 0);");
  12. Log.i("Alarms Table created");
  13. }
  1. private static void createInstanceTable(SQLiteDatabase db) {
  2. db.execSQL("CREATE TABLE " + INSTANCES_TABLE_NAME + " (" +
  3. ClockContract.InstancesColumns._ID + " INTEGER PRIMARY KEY," +
  4. ClockContract.InstancesColumns.YEAR + " INTEGER NOT NULL, " +
  5. ClockContract.InstancesColumns.MONTH + " INTEGER NOT NULL, " +
  6. ClockContract.InstancesColumns.DAY + " INTEGER NOT NULL, " +
  7. ClockContract.InstancesColumns.HOUR + " INTEGER NOT NULL, " +
  8. ClockContract.InstancesColumns.MINUTES + " INTEGER NOT NULL, " +
  9. ClockContract.InstancesColumns.VIBRATE + " INTEGER NOT NULL, " +
  10. ClockContract.InstancesColumns.LABEL + " TEXT NOT NULL, " +
  11. ClockContract.InstancesColumns.RINGTONE + " TEXT, " +
  12. ClockContract.InstancesColumns.ALARM_STATE + " INTEGER NOT NULL, " +
  13. ClockContract.InstancesColumns.ALARM_ID + " INTEGER REFERENCES " +
  14. ALARMS_TABLE_NAME + "(" + ClockContract.AlarmsColumns._ID + ") " +
  15. "ON UPDATE CASCADE ON DELETE CASCADE" +
  16. ");");
  17. Log.i("Instance table created");
  18. }

这里说一下几个特殊的字段,对于Alarm表,DAYS_OF_WEEK表示一周内需要定时的天(闹钟有个功能是选择一周中的几天),这里是个int值,用位来表示设置的天数,源码中有个专门的类DaysOfWeek来存储和处理。

AlarmInstance表中有一个ALARM_ID,关联到一个Alarm,可以看到在AlarmInstance表里也有时间,为什么不和Alarm表合成一个表?应该是这样的,Alarm表示原始的定时项,是一个基础数据,而AlarmInstance则代表了一个使用中的定时项目,或者是一个已经激活的定时项目,它的时间是可以变化的,比如闹钟响了以后延时5分钟再响,就需要改变这里的时间,而基础数据不能变,还需要显示在那里。ALARM_STATE代表了当前定时项目的状态,具体调度都在AlarmStateManager中管理。

忘了在哪里看到的,“编程最重要的是设计数据结构,接下来是分解各种代码块”。数据结构是基础,就像建筑里的钢筋水泥砖瓦,有了基础的材料后,剩下的工作就是对这些材料处理,也就是设计具体的处理逻辑。

2、具体的类分析


Alarm

从上面也可以看出,Alarm类作为定时的基础数据结构,主要是封装了一些数据库操作,完成增删改查功能。额外有一个方法createInstanceAfter,根据自身来创建一个AlarmInstance实例。代码如下

  1. public AlarmInstance createInstanceAfter(Calendar time) {
  2. Calendar nextInstanceTime = Calendar.getInstance();
  3. nextInstanceTime.set(Calendar.YEAR, time.get(Calendar.YEAR));
  4. nextInstanceTime.set(Calendar.MONTH, time.get(Calendar.MONTH));
  5. nextInstanceTime.set(Calendar.DAY_OF_MONTH, time.get(Calendar.DAY_OF_MONTH));
  6. nextInstanceTime.set(Calendar.HOUR_OF_DAY, hour);
  7. nextInstanceTime.set(Calendar.MINUTE, minutes);
  8. nextInstanceTime.set(Calendar.SECOND, 0);
  9. nextInstanceTime.set(Calendar.MILLISECOND, 0);
  10. // If we are still behind the passed in time, then add a day
  11. if (nextInstanceTime.getTimeInMillis() <= time.getTimeInMillis()) {
  12. nextInstanceTime.add(Calendar.DAY_OF_YEAR, 1);
  13. }
  14. // The day of the week might be invalid, so find next valid one
  15. int addDays = daysOfWeek.calculateDaysToNextAlarm(nextInstanceTime);
  16. if (addDays > 0) {
  17. nextInstanceTime.add(Calendar.DAY_OF_WEEK, addDays);
  18. }
  19. AlarmInstance result = new AlarmInstance(nextInstanceTime, id);
  20. result.mVibrate = vibrate;
  21. result.mLabel = label;
  22. result.mRingtone = alert;
  23. return result;
  24. }

AlarmInstance

AlarmInstance与Alarm很相似,像Alarm中的增删改查操作在AlarmInstance中都有相似的方法。那有什么不同呢,就是上面说的AlarmInstance的时间是可以根据当前状态改变的,也就多了时间的set和get方法。

  1. public void setAlarmTime(Calendar calendar) {
  2. mYear = calendar.get(Calendar.YEAR);
  3. mMonth = calendar.get(Calendar.MONTH);
  4. mDay = calendar.get(Calendar.DAY_OF_MONTH);
  5. mHour = calendar.get(Calendar.HOUR_OF_DAY);
  6. mMinute = calendar.get(Calendar.MINUTE);
  7. }
  8. /**
  9. * Return the time when a alarm should fire.
  10. *
  11. * @return the time
  12. */
  13. public Calendar getAlarmTime() {
  14. Calendar calendar = Calendar.getInstance();
  15. calendar.set(Calendar.YEAR, mYear);
  16. calendar.set(Calendar.MONTH, mMonth);
  17. calendar.set(Calendar.DAY_OF_MONTH, mDay);
  18. calendar.set(Calendar.HOUR_OF_DAY, mHour);
  19. calendar.set(Calendar.MINUTE, mMinute);
  20. calendar.set(Calendar.SECOND, 0);
  21. calendar.set(Calendar.MILLISECOND, 0);
  22. return calendar;
  23. }

AlarmStateManager

闹钟定时的核心逻辑就在这里,AlarmStateManager就是管理所有定时项目状态的调度器。

可以看到上面大多是static类型的方法,用于设置各种状态值。

先看一下定时的几种状态:

SILENT_STATE,alarm被激活,但是不需要显示任何东西,下一个状态是LOW_NOTIFICATION_STATE;

LOW_NOTIFICATION_STATE,这个状态表示alarm离触发的时间不远了,时间差是AlarmInstance.LOW_NOTIFICATION_HOUR_OFFSET=-2,也就是2个小时。下一个状态会进入HIGH_NOTIFICATION_STATE,HIDE_NOTIFICATION_STATE,DISMISS_STATE;

HIDE_NOTIFICATION_STATE,这是一个暂时态,表示用户想隐藏掉通知,这个状态会一直持续到HIGH_NOTIFICATION_STATE;

HIGH_NOTIFICATION_STATE,这个状态和LOW_NOTIFICATION_STATE相似,但不允许用户隐藏通知,负责触发FIRED_STATE或者DISMISS_STATE;

SNOOZED_STATE,像HIGH_NOTIFICATION_STATE,但是会增加一点定时的时间来完成延迟功能;

FIRED_STATE,表示响铃状态,会启动AlarmService直到用户将其变为SNOOZED_STATE或者DISMISS_STATE,如果用户放任不管,会之后进入MISSED_STATE;

MISSED_STATE,这个状态在FIRED_STATE之后,会在通知栏给出一个提醒刚才响铃了;

DISMISS_STATE,这个状态表示定时结束了,会根据定时项目的设置判断是否需要重复,从而决定要删除这个项目还是继续设定一个新的定时。

上面的 setXXXState 方法就是对这些状态的处理,同时会规划一个定时转换到下一个状态。比如setSilentState:

  1. public static void setSilentState(Context context, AlarmInstance instance) {
  2. Log.v("Setting silent state to instance " + instance.mId);
  3. // Update alarm in db
  4. ContentResolver contentResolver = context.getContentResolver();
  5. instance.mAlarmState = AlarmInstance.SILENT_STATE;
  6. AlarmInstance.updateInstance(contentResolver, instance);
  7. // Setup instance notification and scheduling timers
  8. AlarmNotifications.clearNotification(context, instance);
  9. scheduleInstanceStateChange(context, instance.getLowNotificationTime(),
  10. instance, AlarmInstance.LOW_NOTIFICATION_STATE);
  11. }

更新AlarmInstance的信息,同时通过scheduleInstanceStateChange()规划下一个状态:

  1. private static void scheduleInstanceStateChange(Context context, Calendar time,
  2. AlarmInstance instance, int newState) {
  3. long timeInMillis = time.getTimeInMillis();
  4. Log.v("Scheduling state change " + newState + " to instance " + instance.mId +
  5. " at " + AlarmUtils.getFormattedTime(context, time) + " (" + timeInMillis + ")");
  6. Intent stateChangeIntent = createStateChangeIntent(context, ALARM_MANAGER_TAG, instance,
  7. newState);
  8. PendingIntent pendingIntent = PendingIntent.getBroadcast(context, instance.hashCode(),
  9. stateChangeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
  10. AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
  11. if (Utils.isKitKatOrLater()) {
  12. am.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
  13. } else {
  14. am.set(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
  15. }
  16. }

通过AlarmManager发起一个定时,定时的时间从调用处可以看到是有AlarmInstance得到的,比如在setSilentState()中的定时时间是instance.getLowNotificationTime():

  1. public Calendar getLowNotificationTime() {
  2. Calendar calendar = getAlarmTime();
  3. calendar.add(Calendar.HOUR_OF_DAY, LOW_NOTIFICATION_HOUR_OFFSET);
  4. return calendar;
  5. }

LOW_NOTIFICATION_HOUR_OFFSET值为-2,也就是在闹铃响之前的两小时那一刻会发这个LOW_NOTIFICATION_STATE的广播出来,AlarmStateManager接收到这个广播处理再转移到下一个。广播的接收在onReciever方法中,

  1. @Override
  2. public void onReceive(final Context context, final Intent intent) {
  3. final PendingResult result = goAsync();
  4. final PowerManager.WakeLock wl = AlarmAlertWakeLock.createPartialWakeLock(context);
  5. wl.acquire();
  6. AsyncHandler.post(new Runnable() {
  7. @Override
  8. public void run() {
  9. handleIntent(context, intent);
  10. result.finish();
  11. wl.release();
  12. }
  13. });
  14. }
  15. private void handleIntent(Context context, Intent intent) {
  16. final String action = intent.getAction();
  17. Log.v("AlarmStateManager received intent " + intent);
  18. if (CHANGE_STATE_ACTION.equals(action)) {
  19. Uri uri = intent.getData();
  20. AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(),
  21. AlarmInstance.getId(uri));
  22. if (instance == null) {
  23. // Not a big deal, but it shouldn't happen
  24. Log.e("Can not change state for unknown instance: " + uri);
  25. return;
  26. }
  27. int globalId = getGlobalIntentId(context);
  28. int intentId = intent.getIntExtra(ALARM_GLOBAL_ID_EXTRA, -1);
  29. int alarmState = intent.getIntExtra(ALARM_STATE_EXTRA, -1);
  30. if (intentId != globalId) {
  31. Log.i("Ignoring old Intent. IntentId: " + intentId + " GlobalId: " + globalId +
  32. " AlarmState: " + alarmState);
  33. return;
  34. }
  35. if (alarmState >= 0) {
  36. setAlarmState(context, instance, alarmState);
  37. } else {
  38. registerInstance(context, instance, true);
  39. }
  40. } else if (SHOW_AND_DISMISS_ALARM_ACTION.equals(action)) {
  41. Uri uri = intent.getData();
  42. AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(),
  43. AlarmInstance.getId(uri));
  44. long alarmId = instance.mAlarmId == null ? Alarm.INVALID_ID : instance.mAlarmId;
  45. Intent viewAlarmIntent = Alarm.createIntent(context, DeskClock.class, alarmId);
  46. viewAlarmIntent.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.ALARM_TAB_INDEX);
  47. viewAlarmIntent.putExtra(AlarmClockFragment.SCROLL_TO_ALARM_INTENT_EXTRA, alarmId);
  48. viewAlarmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  49. context.startActivity(viewAlarmIntent);
  50. setDismissState(context, instance);
  51. }
  52. }
  53. }

在handleIntent方法中统一处理,状态的分发在setAlarmState中:

  1. public void setAlarmState(Context context, AlarmInstance instance, int state) {
  2. switch(state) {
  3. case AlarmInstance.SILENT_STATE:
  4. setSilentState(context, instance);
  5. break;
  6. case AlarmInstance.LOW_NOTIFICATION_STATE:
  7. setLowNotificationState(context, instance);
  8. break;
  9. case AlarmInstance.HIDE_NOTIFICATION_STATE:
  10. setHideNotificationState(context, instance);
  11. break;
  12. case AlarmInstance.HIGH_NOTIFICATION_STATE:
  13. setHighNotificationState(context, instance);
  14. break;
  15. case AlarmInstance.FIRED_STATE:
  16. setFiredState(context, instance);
  17. break;
  18. case AlarmInstance.SNOOZE_STATE:
  19. setSnoozeState(context, instance);
  20. break;
  21. case AlarmInstance.MISSED_STATE:
  22. setMissedState(context, instance);
  23. break;
  24. case AlarmInstance.DISMISSED_STATE:
  25. setDismissState(context, instance);
  26. break;
  27. default:
  28. Log.e("Trying to change to unknown alarm state: " + state);
  29. }
  30. }

对没一个state又转移相应的setXXXState方法中,完成下一次状态的转换,形成一个定时的循环,直到在DISMISSED_STATE里停用或者删除定时项目,如果需要重复则获取下一次定时的时间。

整体的框架就是这样,在AlarmStateManager里使用AlarmManager形成了一个定时的状态机,不断转移到下一个状态处理。

源码在这里https://android.googlesource.com/platform/packages/apps/DeskClock/+/android-4.4.4_r2.0.1

android AlarmManager讲解的更多相关文章

  1. Android开发工程师文集-Android知识点讲解

    前言 大家好,给大家带来Android开发工程师文集-Android知识点讲解的概述,希望你们喜欢 WebView讲解 一般通过Intent调用系统的浏览器: Uri uri = Uri.parse( ...

  2. 四 Android Capabilities讲解

    本文转自:http://www.cnblogs.com/sundalian/p/5629429.html Android Capabilities讲解   1.Capabilities介绍 可以看下之 ...

  3. Android AlarmManager类的应用(实现闹钟功能)

    1.AlarmManager,顾名思义,就是“提醒”,是Android中常用的一种系统级别的提示服务,可以实现从指定时间开始,以一个固定的间隔时间执行某项操作,所以常常与广播(Broadcast)连用 ...

  4. [置顶] Android AlarmManager实现不间断轮询服务

    在消息的获取上是选择轮询还是推送得根据实际的业务需要来技术选型,例如对消息实时性比较高的需求,比如微博新通知或新闻等那就最好是用推送了.但如果只是一般的消息检测比如更新检查,可能是半个小时或一个小时一 ...

  5. Android AlarmManager的取消

    取消alarm使用AlarmManager.cancel()函数,传入参数是个PendingIntent实例. 该函数会将所有跟这个PendingIntent相同的Alarm全部取消,怎么判断两者是否 ...

  6. Android AlarmManager实现不间断轮询服务

    在消息的获取上是选择 轮询还是推送得根据实际的业务需要来技术选型,例如对消息实时性比较高的需求,比如微博新通知或新闻等那就最好是用推送了.但如果只是一般的消息检测比如 更新检查,可能是半个小时或一个小 ...

  7. Android AlarmManager报警的实现

    什么是AlarmManager? AlarmManager它是Android经常使用的系统-Level提醒服务,我们指定为广播中的特定时间Intent. 我们设定一个时间,然后在该时间到来时.Alar ...

  8. android AlarmManager采用

    Android的闹钟实现机制非常easy, 仅仅须要调用AlarmManager.Set()方法将闹钟设置提交给系统,当闹钟时间到后,系统会依照我们的设定发送指定的广播消息.我们写一个广播去接收消息做 ...

  9. 五 Android Capabilities讲解

    1.Capabilities介绍 可以看下之前代码里面设置的capabilities DesiredCapabilities capabilities = new DesiredCapabilitie ...

随机推荐

  1. 决战 状压dp

    决定在这个小巷里排兵布阵.小巷可以抽象成一个们彼此之间并不是十分和♂谐.具体来说,一个哲学家会有一个的矩形.每一位哲学家会占据一个格子.然而哲学家的01矩阵来表示他自己的守备范围.哲学家自己位于这个矩 ...

  2. 零开始:NetCore项目权限管理系统:基础框架搭建

    有兴趣的同学可以一起做 喜欢NetCore的朋友,欢迎加群QQ:86594082 源码地址:https://github.com/feiyit/SoaProJect 新建一个空的解决方案,建立对应的解 ...

  3. 谷歌发布 TensorFlow Serving

    TensorFlow服务是一个灵活的,高性能的机器学习模型的服务系统,专为生产环境而设计. TensorFlow服务可以轻松部署新的算法和实验,同时保持相同的服务器体系结构和API. TensorFl ...

  4. STM8操作LCD5110总结

    附上一小段代码: void LCD_init(void) { // 产生一个让LCD复位的低电平脉冲 //LCD_RST = 0; GPIO_WriteLow(LCD_PORTG, LCD_RST); ...

  5. JAVA NIO工作原理及代码示例

    简介:本文主要介绍了JAVA NIO中的Buffer, Channel, Selector的工作原理以及使用它们的若干注意事项,最后是利用它们实现服务器和客户端通信的代码实例. 欢迎探讨,如有错误敬请 ...

  6. Mac Webview OC与JS交互实现

    1.首先,需要定义一个JS可识别的变量(如external)用于OC与JS交互 - (void)webView:(WebView *)sender didClearWindowObject:(WebS ...

  7. 清空dataset中的某行某列的数据

    string tempSFZH = ""; foreach (DataRow rs in ds.Tables[0].Rows) {     tempSFZH = rs[ht[&qu ...

  8. localStorage存储数组以及取数组方法

    var weekArray = ['周一'.'周二'.'周三'.'周四'.'周五']; //存: localStorage.setItem('weekDay',JSON.stringify(weekA ...

  9. expect IDENTIFIER, actual IDENTIFIER 处理

    涉及到注入数据库的报错,这是很常见的了. 但是期望IDENTIFIER,实际IDENTIFIER 的报错,你们知道是什么意思吗? 我已开始看到的时候,是mybatis报错发神经了,报错了报错.再跑一次 ...

  10. Hibernate的条件查询的几种方式

    1. 第一种,用?占位符,如: //登录(用?占位符) public List<UserPO> LoginUser(UserPO up)throws Exception{ Session ...