android AlarmManager讲解
Android系统闹钟定时功能框架,总体来说就是用数据库存储定时数据,有一个状态管理器来统一管理这些定时状态的触发和更新。在Andriod系统中实现定时功能,最终还是要用到系统提供的AlarmManager,只是当一个定时完成后怎么继续处理,或者中间怎么更新定时的时间或者状态,像闹钟这种应用程序,每天重复定时,或者一周选择其中的几天,闹钟响了延迟5分钟再次响铃,这时候就需要想一种好的办法来让管理这些数据和状态,下面就分析一下Android系统闹钟的实现。
1、基本结构
Alarm
代表一条定时数据
AlarmInstance
代表一个定时项目的实例,一个AlarmInstance对应到一个Alarm,相比Alarm多存储了一些状态信息
AlarmStateManager
状态管理器,对定时项目进行调度,添加、删除、更改状态,是一个BroadcastReciever,定时到点后发广播到这里进行下一步处理
AlarmService
响应结果,也就是定时到达后要做的事,响铃,停止响铃
ClockDataHelper
里面创建了三个表,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中管理。
忘了在哪里看到的,“编程最重要的是设计数据结构,接下来是分解各种代码块”。数据结构是基础,就像建筑里的钢筋水泥砖瓦,有了基础的材料后,剩下的工作就是对这些材料处理,也就是设计具体的处理逻辑。
2、具体的类分析
Alarm
从上面也可以看出,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
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
闹钟定时的核心逻辑就在这里,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);
- 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);
- }
- }
对没一个state又转移相应的setXXXState方法中,完成下一次状态的转换,形成一个定时的循环,直到在DISMISSED_STATE里停用或者删除定时项目,如果需要重复则获取下一次定时的时间。
整体的框架就是这样,在AlarmStateManager里使用AlarmManager形成了一个定时的状态机,不断转移到下一个状态处理。
源码在这里https://android.googlesource.com/platform/packages/apps/DeskClock/+/android-4.4.4_r2.0.1
android AlarmManager讲解的更多相关文章
- Android开发工程师文集-Android知识点讲解
前言 大家好,给大家带来Android开发工程师文集-Android知识点讲解的概述,希望你们喜欢 WebView讲解 一般通过Intent调用系统的浏览器: Uri uri = Uri.parse( ...
- 四 Android Capabilities讲解
本文转自:http://www.cnblogs.com/sundalian/p/5629429.html Android Capabilities讲解 1.Capabilities介绍 可以看下之 ...
- Android AlarmManager类的应用(实现闹钟功能)
1.AlarmManager,顾名思义,就是“提醒”,是Android中常用的一种系统级别的提示服务,可以实现从指定时间开始,以一个固定的间隔时间执行某项操作,所以常常与广播(Broadcast)连用 ...
- [置顶] Android AlarmManager实现不间断轮询服务
在消息的获取上是选择轮询还是推送得根据实际的业务需要来技术选型,例如对消息实时性比较高的需求,比如微博新通知或新闻等那就最好是用推送了.但如果只是一般的消息检测比如更新检查,可能是半个小时或一个小时一 ...
- Android AlarmManager的取消
取消alarm使用AlarmManager.cancel()函数,传入参数是个PendingIntent实例. 该函数会将所有跟这个PendingIntent相同的Alarm全部取消,怎么判断两者是否 ...
- Android AlarmManager实现不间断轮询服务
在消息的获取上是选择 轮询还是推送得根据实际的业务需要来技术选型,例如对消息实时性比较高的需求,比如微博新通知或新闻等那就最好是用推送了.但如果只是一般的消息检测比如 更新检查,可能是半个小时或一个小 ...
- Android AlarmManager报警的实现
什么是AlarmManager? AlarmManager它是Android经常使用的系统-Level提醒服务,我们指定为广播中的特定时间Intent. 我们设定一个时间,然后在该时间到来时.Alar ...
- android AlarmManager采用
Android的闹钟实现机制非常easy, 仅仅须要调用AlarmManager.Set()方法将闹钟设置提交给系统,当闹钟时间到后,系统会依照我们的设定发送指定的广播消息.我们写一个广播去接收消息做 ...
- 五 Android Capabilities讲解
1.Capabilities介绍 可以看下之前代码里面设置的capabilities DesiredCapabilities capabilities = new DesiredCapabilitie ...
随机推荐
- hihocoder #1142 : 三分·三分求极值
时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 这一次我们就简单一点了,题目在此: 在直角坐标系中有一条抛物线y=ax^2+bx+c和一个点P(x,y),求点P到抛物线的 ...
- [bzoj4866] [Ynoi2017]由乃的商场之旅
来自FallDream的博客,未经允许,请勿转载,谢谢, 由乃有一天去参加一个商场举办的游戏.商场派了一些球王排成一行.每个人面前有几堆球.说来也巧,由乃和你一样,觉得这游戏很无聊,于是决定换一个商场 ...
- bzoj4361isn dp+容斥
4361: isn Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 370 Solved: 182[Submit][Status][Discuss] ...
- URL、网址、域名
URL (Uniform Resource Locator)统一资源定位符是对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址.互联网上的每个文件都有一个唯一的URL ...
- 安装yum源和gcc编译器遇到的问题
这两天我试着在VMware虚拟机里安装gcc,遇到了不少问题 1. 安装yum源 我搭建的是光盘yum源(有两种方法搭建yum源,另外一种是网络yum源,但至今没弄懂我的网络yum源为什么不成功) ...
- MySQL DATE_SUB()
DATE_SUB(date,INTERVAL expr type) 函数从日期减去指定的时间间隔. date 参数是合法的日期表达式.expr 参数是您希望添加的时间间隔. type 参数可以是下列值 ...
- 三种方法,刷新 Android 的 MediaStore!让你保存的图片立即出现在相册里!
公众号原标题:测试:"系统相册里怎么看不到我刚保存的图片,是我操作不对吗?" 一.序 Hi,大家好,我是承香墨影! App 内,创建一个文件并保存文件到本地的需求,是很常见的 I/ ...
- TCP/IP学习笔记__mbuf
Socket发送和接收数据都是写入和读取mbuf(存储器缓存)来完成的.下面着重介绍下Sendto函数与mbuf的关系: 以UDP协议为例: 1.UDP的输出执行过程: UDP的输出执行过程 2.协议 ...
- Linux的管理类命令及其使用方法
文件操作相关有一些命令可以帮助我们"修剪"之前看到的文件树. $touch a.txt 如果a.txt不存在,生成一个新的空文档a.txt.如果a.txt存在,那么只更改该文档的时 ...
- Java Socket通信代码片
package zhang; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOExcept ...