2012-10-09

【Android】AlarmManager - 於指定時刻執行特定工作

就像閙鐘一樣,AlarmManager 可以指定在某日某時執行某一特定工作,它的原理是向 Android 系統註冊要在指定時刻執行某一工作;當到達指定時間的時候,系統會進行廣播,告訴所有註冊要在此時執行的程式,程式接收到廣播通知時,就知道要執行工作了。

如果您程式指定的時刻是在現在 (now) 之前,則當 AlarmManager 一完成註冊時,因為系統認為已經錯過了,所以指定的工作會立即補執行。例如:現在是 2013/09/25 21:30:00,而您指定的執行時間是 2012/09/24 09:30:00,則 AlarmManager 一完成註冊時,您指定的工作便會立即執行。


當執行的時刻一到,即使設備(手機或平板) 狀態處於休眠中,系統仍然會執行指定的工作,但若設備關機了,已註冊但尚未執行的工作也會一併被清除;所以如果您要使用 AlarmManager,您的 app 應該要有一段對應 Android 設備重開機後,重新註冊要執行指定工作的程式碼。

由上述可知,您的 app 需要至少一個接收系統廣播的程式

先在專案的 AndroidManifest.xml application 區段寫入要接收廣播的程式
  1. <application>
  2. ...
  3. ...
  4. <!-- 當鬧鈴時間到達時要執行的程式 -->
  5. <receiver
  6. android:name=".PlayReceiver"
  7. android:exported="true"> ← Android12 起要再加這個
  8. <intent-filter>
  9. <action android:name="play_hskay" />
  10. </intent-filter>
  11. </receiver>
  12. ...
  13. ...
  14. </application>
intent-filter 可用來讓接收廣播的程式過濾判別要處理的情況及要執行的工作。

接下來建立 接收廣播 的類別:

  1. public class PlayReceiver extends BroadcastReceiver
  2. {
  3. @Override
  4. public void onReceive(Context context, Intent intent)
  5. {
  6. Bundle bData = intent.getExtras();
  7. if(bData.get("msg").equals("play_hskay"))
  8. {
  9. ...
  10. ...
  11. 要執行的工作
  12. ...
  13. ...
  14. }
  15. }
  16. }

我們用了一個 Bundle 類別接收廣播通知 intent,並判別傳進來的是否是要執行的通知,若是,則執行指定的工作。



接下來就在主程式註冊 AlarmManager 執行時間
  1. // 按下設定鈕
  2. public void onClickSetup(View view)
  3. {
  4. Calendar cal = Calendar.getInstance();
  5. // 設定於 3 分鐘後執行
  6. cal.add(Calendar.MINUTE, 3);
  7.  
  8. Intent intent = new Intent(this, PlayReceiver.class);
  9. intent.putExtra("msg", "play_hskay");
  10.  
  11. PendingIntent pi = PendingIntent.getBroadcast(this, 1, intent, PendingIntent.FLAG_ONE_SHOT);
  12. AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
  13. am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pi);
  14. }

AlarmManager 執行 set() 時,Android 系統會比對已註冊的其他 Intentactiondatatypeclasscategory,如果這幾個屬性完全相同,則系統會將這兩個 Intent 視為一樣,這時系統會視 PendingIntent.FLAG???? 參數以決定如何處理這個新註冊的 Intent,

PendingIntent 的 FLAG 常數
int FLAG_CANCEL_CURRENT 如果已經存在 PendingIntent, 則會取消目前的 Intent 後再產生新的 Intent.
int FLAG_NO_CREATE 如果並不存在 PendingIntent, 則傳回 null.
int FLAG_ONE_SHOT 此 PendingIntent 只能使用一次.
int FLAG_UPDATE_CURRENT 如果已存在 PendingIntent, 則更新 extra data.

反過來說,只要 actiondatatypeclasscategory 這幾個屬性其中有一個不同,則系統就會視為不同的 Intent,所以如果 AlarmManager 要一次註冊多個 Intent,我發現 category 還不錯用,如下:
  1. // 向 Android 系統註冊 AlarmManager
  2. for(int x=1;x<6;x++)
  3. {
  4. Calendar mCal = Calendar.getInstance();
  5. // 自明天起, 連續 5 天的 14:00 執行
  6. mCal.add(Calendar.DATE, x);
  7. mCal.set(Calendar.HOUR_OF_DAY, 14);
  8. mCal.set(Calendar.MINUTE, 0);
  9. mCal.set(Calendar.SECOND, 0);
  10. Intent intentAlarm = new Intent(context, PlayReceiver.class);
  11.  
  12. // 以日期字串組出不同的 category
  13. intent.addCategory("D"+String.valueOf(mCal.get(Calendar.YEAR))+String.valueOf((mCal.get(Calendar.MONTH)+1))+String.valueOf(mCal.get(Calendar.DATE)));
  14.  
  15. intentAlarm.putExtra("msg", "play_hskay");
  16. PendingIntent pi = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
  17. AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
  18. am.set(AlarmManager.RTC_WAKEUP, mCal.getTimeInMillis(), pi);
  19. }



另一種做法,則是在執行指定的工作時,順道註冊下一次的工作時刻。 當 Android 設備有關機重開的動作時,先前註冊而尚未執行的工作就會被清除,所以您可能還會需要一個在開機時執行註冊 AlarmManager 的程式。這個類別只是另一個 receiver,只不過我們加入了幾個 intent-filter,當 Android 設備發生符合 intent-filter 情況時,我們的程式會重新註冊 AlarmManager 指派的工作。 在 AndroidManifest.xml 加入:

  1. <application>
  2. ...
  3. ...
  4. <!-- 接收開機完成廣播 -->
  5. <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED">
  6. ...
  7. ...
  8. <!-- 當手機重開機,所要執行的程式。 -->
  9. <receiver android:name=".AlarmInitReceiver">
  10. <intent-filter>
  11. <action android:name="android.intent.action.BOOT_COMPLETED" />
  12. </intent-filter>
  13. </receiver>
  14. ...
  15. ...
  16. </uses-permission>
  17. </application>


再建立一個開機廣播類別:
  1. public class AlarmInitReceiver extends BroadcastReceiver
  2. {
  3. // 重開機時重新註冊 AlarmManager
  4. ....
  5. }


另外還要注意,AlarmManager 的 app 必須安裝在手機內,也就是說
  1. <manifest
  2. ...
  3. ...
  4. android:installLocation="internalOnly"
  5. android:versionCode="1"
  6. android:versionName="1.0" >
否則您的 receiver 將接收不到開機完成的廣播,receiver 那段程式也就不會執行了。

6 則留言:

  1. 拜讀了您的筆記,對於使用方法的細節介紹的非常詳細,文章的編排也容易閱讀,感謝您的分享。

    回覆刪除
  2. 新手過路...想請教一下...如果程式完全退出for的功能還會在背景繼續運作嗎?

    回覆刪除
  3. 拍謝, 不大懂您的意思...

    那段 for 迴圈做的事是向 Android 註冊 AlarmManager 的工作.

    回覆刪除
  4. 請問播放手機內建的鈴聲出來
    播放只知道使用soundpool或是 media
    想請問的是鈴聲可以從哪邊取得,不自己製作情況下
    謝謝~

    回覆刪除