在前一篇文章中,我們已經可以在文字時鐘小部件上顯示時間,但是這個時間無法自動更新。在本文中我們將會使用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下載。