在前一篇文章中,我们已经可以在文本时钟小部件上显示时间,但是这个时间无法自动更新。在本文中我们将会使用AlarmManager
定时更新小部件。
之前,我们讨论过IntentService
以及如何使用它去执行Action
。AlarmManager
为我们提供了一个定时启动IntentService
的完美机制。希望本文能帮你了解如何使用Intent
启动Android Service(之前介绍并使用过)。如何设定AlarmManager
在某段时间间隔后执行这个Action
,并且可以选择时间间隔重复执行。不止局限于启动服务(Service
),AlarmManager
还可以执行许多其它Action
。它可以发送广播并启动活动(Activity
)。PendingIntent
用来表示将要被执行的Action
,由AlarmManager
安排这个Action将要执行的时间。
让我们考虑一下我们的应用小部件应当如何更新。当用户在主页面创建一个小部件时会调用TextClockAppWidget
的onUpdate()
方法。到目前为止,我们只是在这个函数里更新小部件的时间,没有做任何其他事情。但其实我们还可以在这个方法里安排即将要进行的更新。当然,我们仍然希望在这里更新初始时间,所以我们保持startService()
不变。我们希望做的是生成一个Alarm,这个Alarm会每分钟触发一个PendingIntent
,调用一次startService()
(当时间从59秒变到00秒的时候,分钟数加一)确保小部件显示精确的时间。
有一点要注意,用户可能安装多个小部件实例。但是我们只需要一个Alarm更新。我们的时钟小部件本来就应该在同一时间更新,因此启动多个服务就有些浪费了。坏消息是,AlarmManager
不允许我们查询设置了哪些Alarm,这让事情变得有些复杂。好消息是,我们可以通过PendingIntent
检测是否有一个已经设置的Alarm。即使是创建在不同的线程、进程、甚至应用程序中,如果PendingIntent
使用相同的操作(Operation)、Intent Action
、数据、分类、组件和标记(Flag),那它就是唯一的PendingIntent
。我们可以使用它管理Alarm。
为了便于说明,我们重点关注用PendingIntent
来启动服务。同样的技术也可以应用在其他场合。我们无法直接创建PendingIntent
,但是PendingIntent
类为我们提供了一些静态工厂方法。PendingIntent.getService()
能够接受四个参数:
Context context
:PendingIntent
启动的服务上下文。int requestCode
:忽略。Intent intent
:用作startService()
的参数,此函数会在刚才提供的context
上调用。int flags
:用来控制是否需要以及如何创建或更新PendingIntent
。
如果我们使用FLAG_NO_CREATE
标记,那么PendingIntent.getService()
会检查是否有一个带有同样参数的PendingIntent
已经存在于此设备上。如果存在返回它的实例,否则返回null
。因此我们可以使用这个方法确保只存在一个PendingIntent
:
1 2 3 4 5 6 7 8 9 10 11 12 |
; html-script: false ] Intent update = new Intent(TextClockService.ACTION_UPDATE); PendingIntent pi = PendingIntent.getService(context, REQUEST_CODE, update, PendingIntent.FLAG_NO_CREATE); if (pi == null) { pi = PendingIntent.getService(context, REQUEST_CODE, update, PendingIntent.FLAG_CANCEL_CURRENT); } |
如果创建PendingIntent
时只创建了一个Alarm
,那就可以保证只有一个Alarm
。
我们还可以应用于TextClockAppWidget
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
; html-script: false ] public class TextClockAppWidget extends AppWidgetProvider { private static final String TAG = "TextClockWidget"; private static final Intent update = new Intent(TextClockService.ACTION_UPDATE); private static final int REQUEST_CODE = 1; private Context context = null; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { Log.d(TAG, "onUpdate"); this.context = context; this.context.startService(update); scheduleTimer(); } private void scheduleTimer() { Calendar date = Calendar.getInstance(); date.set(Calendar.SECOND, 0); date.set(Calendar.MILLISECOND, 0); date.add(Calendar.MINUTE, 1); AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); PendingIntent pi = PendingIntent.getService(context, REQUEST_CODE, update, PendingIntent.FLAG_NO_CREATE); if (pi == null) { pi = PendingIntent.getService(context, REQUEST_CODE, update, PendingIntent.FLAG_CANCEL_CURRENT); am.setRepeating(AlarmManager.RTC, date.getTimeInMillis(), 60 * 1000, pi); Log.d(TAG, "Alarm created"); } } } |
我们使用Calendar
对象去寻找分钟边界。在每一个分钟边界处,我们设置一个每分钟重复的Alarm
。我们需要指定Alarm
类型为RTC
而不是RTC_WAKEUP
,以此达到省电的目的。即使你的手机处于休眠状态RTC_WAKEUP
也会启动服务,然而在用户没有看手机的情况下唤醒设备并更新时间太费电。而使用RTC
时,服务会在下一次设备醒来时启动(因此总是显示准确时间),但不会唤醒设备。
作为拥有良好习惯的Android程序员,需要在用户移除我们的应用小部件实例时删除Alarm
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
; html-script: false ] @Override public void onDeleted(Context context, int[] appWidgetIds) { Log.d(TAG, "onDeleted"); AppWidgetManager mgr = AppWidgetManager.getInstance(context); int[] remainingIds = mgr.getAppWidgetIds(new ComponentName(context, this.getClass())); if (remainingIds == null || remainingIds.length <= 0) { PendingIntent pi = PendingIntent.getService(context, REQUEST_CODE, update, PendingIntent.FLAG_NO_CREATE); if (pi != null) { AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); am.cancel(pi); pi.cancel(); Log.d(TAG, "Alarm cancelled"); } } } |
onDelete()
在用户每次移除小部件实例时触发,所以我们需要检查是否还有其他实例。如果已经没有任何实例,那就移除Alarm
。我们必须在这里移除PendingIntent
,如果不这样做即使已经没有与它关联的Alarm
,小部件还会一直在系统中处于活跃(Active)状态。这样会导致我们在scheduleTimer()
中检查PendingIntent
失效。
如果我们运行程序,将会看到这个版本的文本时钟显示的是精确时间!
现在我们有了一个实现基本功能的App:可以精确显示时间。这就是Google Play上这个App的1.0.0版本。
我们将在下一章中继续学习如何改进。
本文的源代码可以在这里找到,文本使用可以从Google Play下载。