Android文本时钟 — Part3

645 查看

前面的文章中,我们在主屏幕上添加了一个应用小部件,但并不能显示任何真实数据。看上去没什么实际用处。在这篇文章中,我们将在小部件上显示时间。

为了在小部件上更新时间,我们将使用IntentService。相对于通过调用Context.startService(Intent intent)启动的一般Android Service,IntentService比较特殊——一旦动作(Action)执行完毕就会自动关闭。IntentService非常适合定期执行的细粒度操作。与后台服务不同,这些操作的不会执行很长时间,因此任务管理器无法发现并杀死这些操作。

为了实现IntentService接口,必须要覆盖构造函数和onHandleIntent()方法,还需要声明一个DateFormat,后面将会用到。

每次IntentService启动时都会调用onHandleIntent()方法,方法执行完毕服务会自动关闭。我们还需要一个自定义动作用来触发组件更新。

如果想让获取的时间尽可能有意义,建议使用WakeLock阻止设备休眠(这会阻止我们的服务运行),或者使用Mark Murphy的WakefulIntentService。然而,我们只需要执行很短的时间,所以不需要这样做。

当然,现在我们需要在Manifest里面声明过滤器响应我们自定义的动作。

这样我们的IntentService就定义好了,但是怎样更新应用小部件?

一个应用小部件和一个标准的Activity有很大不同:在Activity里你可以做任何你想做的事情;应用小部件会在主界面运行,可能还有其他小部件在运行,我们不能对他们干扰。例如,正常的Activity区域内可以绘制超过自身的区域,甚至可以通过clipChildern属性处理父控件的布局。然而,在主界面这样做会干扰其他部件。为了阻止小部件这种行为,它们不能直接访问小部件布局及其子视图。于是,小部件需要使用RemoteViews对象更新这些视图。RemoteViews 是一个代理,它会提供对这些视图带限制的访问。所以,可以在我们的应用小部件中使用下面这些小部件。它们是:

  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextView
  • ViewFlipper
  • ListView
  • GridView
  • StackView
  • AdapterViewFlipper

同样的限制,我们的小部件只能使用下列布局:

  • FrameLayout
  • LinearLayout
  • RelativeLayout
  • GridLayout

虽然看上去有些限制,但是我们仍能做出很酷的东西。下面我们通过服务更新时间,每当服务启动时执行下列代码:

updateTime()方法包含一个AppWidgetManager实例,这样可以在系统上更新组件。我们查找AppWidgetManager实例的组件名称,使用它得到一个应用部件的ID列表。这里可能会有多个组件实例,因为用户可能不止在一处添加,所以列举出所有活动的应用组件对全部小部件完成更新。根据由在这个系列教程第一篇文章中定义的业务逻辑,生成表示当前时间的文字。然后通过对小部件ID进行迭代,基于在前一篇文章中应用组件布局创建RemoteView。在实际通过AppWidgetManager请求更新前,调用updateTime()更新这些视图。

updateTime()方法会根据我们需要显示的字数去改变布局中TextView的可见性,同时设置文字。RemoteView 允许这种控制,虽然会有一些限制。

为了让它工作,我们需要调用AppWidgetProvider的onUpdate方法启动IntentService。当组件被添加到主界面时会调用该方法。

运行后,可以看到应用小部件的文字进行了更新。但是,如果比较应用组件的时间和状态栏的时间,就会发现这个小部件并没有更新时间。

widget-no-update

在下一篇文章中,我们将获取更新后的时间。

本文的代码可以从这里获取,TextClock应用可以从Google Play下载。