在前面的文章中,我們在主螢幕上新增了一個應用小部件,但並不能顯示任何真實資料。看上去沒什麼實際用處。在這篇文章中,我們將在小部件上顯示時間。
為了在小部件上更新時間,我們將使用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下載。