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);
更新alarminstance的信息,同时通过scheduleinstancestatechange()规划下一个状态:
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);
}
通过alarmmanager发起一个定时,定时的时间从调用处可以看到是有alarminstance得到的,比如在setsilentstate()中的定时时间是instance.getlownotificationtime():
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);
if (alarmstate >= 0) {
setalarmstate(context, instance, alarmstate);
} else {
registerinstance(context, instance, true);
} else if (show_and_dismiss_alarm_action.equals(action)) {
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);
case alarminstance.hide_notification_state:
sethidenotificationstate(context, instance);
case alarminstance.high_notification_state:
sethighnotificationstate(context, instance);
case alarminstance.fired_state:
setfiredstate(context, instance);
case alarminstance.snooze_state:
setsnoozestate(context, instance);
case alarminstance.missed_state:
setmissedstate(context, instance);
case alarminstance.dismissed_state:
setdismissstate(context, instance);
default:
log.e("trying to change to unknown alarm state: " + state);
对没一个state又转移相应的setxxxstate方法中,完成下一次状态的转换,形成一个定时的循环,直到在dismissed_state里停用或者删除定时项目,如果需要重复则获取下一次定时的时间。
整体的框架就是这样,在alarmstatemanager里使用alarmmanager形成了一个定时的状态机,不断转移到下一个状态处理。