在前面的文章中,我们在主屏幕上添加了一个应用小部件,但并不能显示任何真实数据。看上去没什么实际用处。在这篇文章中,我们将在小部件上显示时间。
为了在小部件上更新时间,我们将使用IntentService。相对于通过调用Context.startService(Intent intent)启动的一般Android Service,IntentService比较特殊——一旦动作(Action)执行完毕就会自动关闭。IntentService非常适合定期执行的细粒度操作。与后台服务不同,这些操作的不会执行很长时间,因此任务管理器无法发现并杀死这些操作。
为了实现IntentService接口,必须要覆盖构造函数和onHandleIntent()方法,还需要声明一个DateFormat,后面将会用到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class TextClockService extends IntentService { private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); private static final String TAG = "TextClockService"; public static final String ACTION_UPDATE = "com.stylingandroid.textclock.ACTION_UPDATE"; public TextClockService() { super(TAG); } @Override protected void onHandleIntent(Intent intent) { if (intent.getAction().equals(ACTION_UPDATE)) { // TODO: handle the Intent } } } |
每次IntentService启动时都会调用onHandleIntent()方法,方法执行完毕服务会自动关闭。我们还需要一个自定义动作用来触发组件更新。
如果想让获取的时间尽可能有意义,建议使用WakeLock阻止设备休眠(这会阻止我们的服务运行),或者使用Mark Murphy的WakefulIntentService。然而,我们只需要执行很短的时间,所以不需要这样做。
当然,现在我们需要在Manifest里面声明过滤器响应我们自定义的动作。
1 2 3 4 5 6 |
<service android:name=".TextClockService"> <intent-filter> <action android:name="com.stylingandroid.textclock.ACTION_UPDATE"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </service> |
这样我们的IntentService就定义好了,但是怎样更新应用小部件?
一个应用小部件和一个标准的Activity有很大不同:在Activity里你可以做任何你想做的事情;应用小部件会在主界面运行,可能还有其他小部件在运行,我们不能对他们干扰。例如,正常的Activity区域内可以绘制超过自身的区域,甚至可以通过clipChildern属性处理父控件的布局。然而,在主界面这样做会干扰其他部件。为了阻止小部件这种行为,它们不能直接访问小部件布局及其子视图。于是,小部件需要使用RemoteViews对象更新这些视图。RemoteViews 是一个代理,它会提供对这些视图带限制的访问。所以,可以在我们的应用小部件中使用下面这些小部件。它们是:
- AnalogClock
- Button
- Chronometer
- ImageButton
- ImageView
- ProgressBar
- TextView
- ViewFlipper
- ListView
- GridView
- StackView
- AdapterViewFlipper
同样的限制,我们的小部件只能使用下列布局:
- FrameLayout
- LinearLayout
- RelativeLayout
- GridLayout
虽然看上去有些限制,但是我们仍能做出很酷的东西。下面我们通过服务更新时间,每当服务启动时执行下列代码:
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 |
@Override protected void onHandleIntent(Intent intent) { if (intent.getAction().equals(ACTION_UPDATE)) { Calendar now = Calendar.getInstance(); updateTime(now); } } private void updateTime(Calendar date) { Log.d(TAG, "Update: " + dateFormat.format(date.getTime())); AppWidgetManager manager = AppWidgetManager.getInstance(this); ComponentName name = new ComponentName(this, TextClockAppWidget.class); int[] appIds = manager.getAppWidgetIds(name); String[] words = TimeToWords.timeToWords(date); for (int id : appIds) { RemoteViews v = new RemoteViews(getPackageName(), R.layout.appwidget); updateTime(words, v); manager.updateAppWidget(id, v); } } private void updateTime(String[] words, RemoteViews views) { views.setTextViewText(R.id.hours, words[0]); if (words.length == 1) { views.setViewVisibility(R.id.minutes, View.INVISIBLE); views.setViewVisibility(R.id.tens, View.INVISIBLE); } else if (words.length == 2) { views.setViewVisibility(R.id.minutes, View.INVISIBLE); views.setViewVisibility(R.id.tens, View.VISIBLE); views.setTextViewText(R.id.tens, words[1]); } else { views.setViewVisibility(R.id.minutes, View.VISIBLE); views.setViewVisibility(R.id.tens, View.VISIBLE); views.setTextViewText(R.id.tens, words[1]); views.setTextViewText(R.id.minutes, words[2]); } } |
updateTime()方法包含一个AppWidgetManager实例,这样可以在系统上更新组件。我们查找AppWidgetManager实例的组件名称,使用它得到一个应用部件的ID列表。这里可能会有多个组件实例,因为用户可能不止在一处添加,所以列举出所有活动的应用组件对全部小部件完成更新。根据由在这个系列教程第一篇文章中定义的业务逻辑,生成表示当前时间的文字。然后通过对小部件ID进行迭代,基于在前一篇文章中应用组件布局创建RemoteView。在实际通过AppWidgetManager请求更新前,调用updateTime()更新这些视图。
updateTime()方法会根据我们需要显示的字数去改变布局中TextView的可见性,同时设置文字。RemoteView 允许这种控制,虽然会有一些限制。
为了让它工作,我们需要调用AppWidgetProvider的onUpdate方法启动IntentService。当组件被添加到主界面时会调用该方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class TextClockAppWidget extends AppWidgetProvider { private static final String TAG = "TextClockWidget"; private static final Intent update = new Intent(TextClockService.ACTION_UPDATE); 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); } } |
运行后,可以看到应用小部件的文字进行了更新。但是,如果比较应用组件的时间和状态栏的时间,就会发现这个小部件并没有更新时间。
在下一篇文章中,我们将获取更新后的时间。
本文的代码可以从这里获取,TextClock应用可以从Google Play下载。