Android系统闹钟定时功能框架,总体来说就是用数据库存储定时数据,有一个状态管理器来统一管理这些定时状态的触发和更新。在Andriod系统中实现定时功能,最终还是要用到系统提供的AlarmManager,只是当一个定时完成后怎么继续处理,或者中间怎么更新定时的时间或者状态,像闹钟这种应用程序,每天重复定时,或者一周选择其中的几天,闹钟响了延迟5分钟再次响铃,这时候就需要想一种好的办法来让管理这些数据和状态,下面就分析一下Android系统闹钟的实现。
代表一条定时数据
代表一个定时项目的实例,一个AlarmInstance对应到一个Alarm,相比Alarm多存储了一些状态信息
状态管理器,对定时项目进行调度,添加、删除、更改状态,是一个BroadcastReciever,定时到点后发广播到这里进行下一步处理
响应结果,也就是定时到达后要做的事,响铃,停止响铃
里面创建了三个表,ALARMS_TABLE,INSTANCE_TABLE,CITIES_TABLE,前两个分别对应到上面的Alarm和AlarmInstance。
private static void createAlarmsTable(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + ALARMS_TABLE_NAME + " (" + ClockContract.AlarmsColumns._ID + " INTEGER PRIMARY KEY," + ClockContract.AlarmsColumns.HOUR + " INTEGER NOT NULL, " + ClockContract.AlarmsColumns.MINUTES + " INTEGER NOT NULL, " + ClockContract.AlarmsColumns.DAYS_OF_WEEK + " INTEGER NOT NULL, " + ClockContract.AlarmsColumns.ENABLED + " INTEGER NOT NULL, " + ClockContract.AlarmsColumns.VIBRATE + " INTEGER NOT NULL, " + ClockContract.AlarmsColumns.LABEL + " TEXT NOT NULL, " + ClockContract.AlarmsColumns.RINGTONE + " TEXT, " + ClockContract.AlarmsColumns.DELETE_AFTER_USE + " INTEGER NOT NULL DEFAULT 0);"); Log.i("Alarms Table created"); }
private static void createInstanceTable(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + INSTANCES_TABLE_NAME + " (" + ClockContract.InstancesColumns._ID + " INTEGER PRIMARY KEY," + ClockContract.InstancesColumns.YEAR + " INTEGER NOT NULL, " + ClockContract.InstancesColumns.MONTH + " INTEGER NOT NULL, " + ClockContract.InstancesColumns.DAY + " INTEGER NOT NULL, " + ClockContract.InstancesColumns.HOUR + " INTEGER NOT NULL, " + ClockContract.InstancesColumns.MINUTES + " INTEGER NOT NULL, " + ClockContract.InstancesColumns.VIBRATE + " INTEGER NOT NULL, " + ClockContract.InstancesColumns.LABEL + " TEXT NOT NULL, " + ClockContract.InstancesColumns.RINGTONE + " TEXT, " + ClockContract.InstancesColumns.ALARM_STATE + " INTEGER NOT NULL, " + ClockContract.InstancesColumns.ALARM_ID + " INTEGER REFERENCES " + ALARMS_TABLE_NAME + "(" + ClockContract.AlarmsColumns._ID + ") " + "ON UPDATE CASCADE ON DELETE CASCADE" + ");"); Log.i("Instance table created"); }
这里说一下几个特殊的字段,对于Alarm表,DAYS_OF_WEEK表示一周内需要定时的天(闹钟有个功能是选择一周中的几天),这里是个int值,用位来表示设置的天数,源码中有个专门的类DaysOfWeek来存储和处理。
AlarmInstance表中有一个ALARM_ID,关联到一个Alarm,可以看到在AlarmInstance表里也有时间,为什么不和Alarm表合成一个表?应该是这样的,Alarm表示原始的定时项,是一个基础数据,而AlarmInstance则代表了一个使用中的定时项目,或者是一个已经激活的定时项目,它的时间是可以变化的,比如闹钟响了以后延时5分钟再响,就需要改变这里的时间,而基础数据不能变,还需要显示在那里。ALARM_STATE代表了当前定时项目的状态,具体调度都在AlarmStateManager中管理。
忘了在哪里看到的,“编程最重要的是设计数据结构,接下来是分解各种代码块”。数据结构是基础,就像建筑里的钢筋水泥砖瓦,有了基础的材料后,剩下的工作就是对这些材料处理,也就是设计具体的处理逻辑。
从上面也可以看出,Alarm类作为定时的基础数据结构,主要是封装了一些数据库操作,完成增删改查功能。额外有一个方法createInstanceAfter,根据自身来创建一个AlarmInstance实例。代码如下
public AlarmInstance createInstanceAfter(Calendar time) { Calendar nextInstanceTime = Calendar.getInstance(); nextInstanceTime.set(Calendar.YEAR, time.get(Calendar.YEAR)); nextInstanceTime.set(Calendar.MONTH, time.get(Calendar.MONTH)); nextInstanceTime.set(Calendar.DAY_OF_MONTH, time.get(Calendar.DAY_OF_MONTH)); nextInstanceTime.set(Calendar.HOUR_OF_DAY, hour); nextInstanceTime.set(Calendar.MINUTE, minutes); nextInstanceTime.set(Calendar.SECOND, 0); nextInstanceTime.set(Calendar.MILLISECOND, 0); // If we are still behind the passed in time, then add a day if (nextInstanceTime.getTimeInMillis() <= time.getTimeInMillis()) { nextInstanceTime.add(Calendar.DAY_OF_YEAR, 1); } // The day of the week might be invalid, so find next valid one int addDays = daysOfWeek.calculateDaysToNextAlarm(nextInstanceTime); if (addDays > 0) { nextInstanceTime.add(Calendar.DAY_OF_WEEK, addDays); } AlarmInstance result = new AlarmInstance(nextInstanceTime, id); result.mVibrate = vibrate; result.mLabel = label; result.mRingtone = alert; return result; }
AlarmInstance与Alarm很相似,像Alarm中的增删改查操作在AlarmInstance中都有相似的方法。那有什么不同呢,就是上面说的AlarmInstance的时间是可以根据当前状态改变的,也就多了时间的set和get方法。
public void setAlarmTime(Calendar calendar) { mYear = calendar.get(Calendar.YEAR); mMonth = calendar.get(Calendar.MONTH); mDay = calendar.get(Calendar.DAY_OF_MONTH); mHour = calendar.get(Calendar.HOUR_OF_DAY); mMinute = calendar.get(Calendar.MINUTE); } /** * Return the time when a alarm should fire. * * @return the time */ public Calendar getAlarmTime() { Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.YEAR, mYear); calendar.set(Calendar.MONTH, mMonth); calendar.set(Calendar.DAY_OF_MONTH, mDay); calendar.set(Calendar.HOUR_OF_DAY, mHour); calendar.set(Calendar.MINUTE, mMinute); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); return calendar; }
闹钟定时的核心逻辑就在这里,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:
public static void setSilentState(Context context, AlarmInstance instance) { Log.v("Setting silent state to instance " + instance.mId); // Update alarm in db ContentResolver contentResolver = context.getContentResolver(); instance.mAlarmState = AlarmInstance.SILENT_STATE; AlarmInstance.updateInstance(contentResolver, instance); // Setup instance notification and scheduling timers AlarmNotifications.clearNotification(context, instance); scheduleInstanceStateChange(context, instance.getLowNotificationTime(), instance, AlarmInstance.LOW_NOTIFICATION_STATE); }
private static void scheduleInstanceStateChange(Context context, Calendar time, AlarmInstance instance, int newState) { long timeInMillis = time.getTimeInMillis(); Log.v("Scheduling state change " + newState + " to instance " + instance.mId + " at " + AlarmUtils.getFormattedTime(context, time) + " (" + timeInMillis + ")"); Intent stateChangeIntent = createStateChangeIntent(context, ALARM_MANAGER_TAG, instance, newState); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, instance.hashCode(), stateChangeIntent, PendingIntent.FLAG_UPDATE_CURRENT); AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); if (Utils.isKitKatOrLater()) { am.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent); } else { am.set(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent); } }
public Calendar getLowNotificationTime() { Calendar calendar = getAlarmTime(); calendar.add(Calendar.HOUR_OF_DAY, LOW_NOTIFICATION_HOUR_OFFSET); return calendar; }
LOW_NOTIFICATION_HOUR_OFFSET值为-2,也就是在闹铃响之前的两小时那一刻会发这个LOW_NOTIFICATION_STATE的广播出来,AlarmStateManager接收到这个广播处理再转移到下一个。广播的接收在onReciever方法中,
@Override public void onReceive(final Context context, final Intent intent) { final PendingResult result = goAsync(); final PowerManager.WakeLock wl = AlarmAlertWakeLock.createPartialWakeLock(context); wl.acquire(); AsyncHandler.post(new Runnable() { @Override public void run() { handleIntent(context, intent); result.finish(); wl.release(); } }); } private void handleIntent(Context context, Intent intent) { final String action = intent.getAction(); Log.v("AlarmStateManager received intent " + intent); if (CHANGE_STATE_ACTION.equals(action)) { Uri uri = intent.getData(); AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(), AlarmInstance.getId(uri)); if (instance == null) { // Not a big deal, but it shouldn't happen Log.e("Can not change state for unknown instance: " + uri); return; } int globalId = getGlobalIntentId(context); int intentId = intent.getIntExtra(ALARM_GLOBAL_ID_EXTRA, -1); int alarmState = intent.getIntExtra(ALARM_STATE_EXTRA, -1); if (intentId != globalId) { Log.i("Ignoring old Intent. IntentId: " + intentId + " GlobalId: " + globalId + " AlarmState: " + alarmState); return; } if (alarmState >= 0) { setAlarmState(context, instance, alarmState); } else { registerInstance(context, instance, true); } } else if (SHOW_AND_DISMISS_ALARM_ACTION.equals(action)) { Uri uri = intent.getData(); AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(), AlarmInstance.getId(uri)); long alarmId = instance.mAlarmId == null ? Alarm.INVALID_ID : instance.mAlarmId; Intent viewAlarmIntent = Alarm.createIntent(context, DeskClock.class, alarmId); viewAlarmIntent.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.ALARM_TAB_INDEX); viewAlarmIntent.putExtra(AlarmClockFragment.SCROLL_TO_ALARM_INTENT_EXTRA, alarmId); viewAlarmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(viewAlarmIntent); setDismissState(context, instance); } } }
在handleIntent方法中统一处理,状态的分发在setAlarmState中:
public void setAlarmState(Context context, AlarmInstance instance, int state) { switch(state) { case AlarmInstance.SILENT_STATE: setSilentState(context, instance); break; case AlarmInstance.LOW_NOTIFICATION_STATE: setLowNotificationState(context, instance); break; case AlarmInstance.HIDE_NOTIFICATION_STATE: setHideNotificationState(context, instance); break; case AlarmInstance.HIGH_NOTIFICATION_STATE: setHighNotificationState(context, instance); break; case AlarmInstance.FIRED_STATE: setFiredState(context, instance); break; case AlarmInstance.SNOOZE_STATE: setSnoozeState(context, instance); break; case AlarmInstance.MISSED_STATE: setMissedState(context, instance); break; case AlarmInstance.DISMISSED_STATE: setDismissState(context, instance); break; default: Log.e("Trying to change to unknown alarm state: " + state); } }
整体的框架就是这样,在AlarmStateManager里使用AlarmManager形成了一个定时的状态机,不断转移到下一个状态处理。
源码在这里https://android.googlesource.com/platform/packages/apps/DeskClock/+/android-4.4.4_r2.0.1