如果您程式指定的時刻是在現在 (now) 之前,則當 AlarmManager 一完成註冊時,因為系統認為已經錯過了,所以指定的工作會立即補執行。例如:現在是 2013/09/25 21:30:00,而您指定的執行時間是 2012/09/24 09:30:00,則 AlarmManager 一完成註冊時,您指定的工作便會立即執行。
當執行的時刻一到,即使設備(手機或平板) 狀態處於休眠中,系統仍然會執行指定的工作,但若設備關機了,已註冊但尚未執行的工作也會一併被清除;所以如果您要使用 AlarmManager,您的 app 應該要有一段對應 Android 設備重開機後,重新註冊要執行指定工作的程式碼。
由上述可知,您的 app 需要至少一個接收系統廣播的程式
先在專案的 AndroidManifest.xml 的
<application> ... ... <!-- 當鬧鈴時間到達時要執行的程式 --> <receiver android:name=".PlayReceiver" android:exported="true"> ← Android12 起要再加這個 <intent-filter> <action android:name="play_hskay" /> </intent-filter> </receiver> ... ... </application>intent-filter 可用來讓接收廣播的程式過濾判別要處理的情況及要執行的工作。
接下來建立 接收廣播 的類別:
public class PlayReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Bundle bData = intent.getExtras(); if(bData.get("msg").equals("play_hskay")) { ... ... 要執行的工作 ... ... } } }
我們用了一個 Bundle 類別接收廣播通知 intent,並判別傳進來的是否是要執行的通知,若是,則執行指定的工作。
接下來就在主程式註冊 AlarmManager 執行時間
// 按下設定鈕 public void onClickSetup(View view) { Calendar cal = Calendar.getInstance(); // 設定於 3 分鐘後執行 cal.add(Calendar.MINUTE, 3); Intent intent = new Intent(this, PlayReceiver.class); intent.putExtra("msg", "play_hskay"); PendingIntent pi = PendingIntent.getBroadcast(this, 1, intent, PendingIntent.FLAG_ONE_SHOT); AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE); am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pi); }
當 AlarmManager 執行 set() 時,Android 系統會比對已註冊的其他 Intent 的 action、data、type、class、category,如果這幾個屬性完全相同,則系統會將這兩個 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. |
反過來說,只要 action、data、type、class、category 這幾個屬性其中有一個不同,則系統就會視為不同的 Intent,所以如果 AlarmManager 要一次註冊多個 Intent,我發現 category 還不錯用,如下:
// 向 Android 系統註冊 AlarmManager for(int x=1;x<6;x++) { Calendar mCal = Calendar.getInstance(); // 自明天起, 連續 5 天的 14:00 執行 mCal.add(Calendar.DATE, x); mCal.set(Calendar.HOUR_OF_DAY, 14); mCal.set(Calendar.MINUTE, 0); mCal.set(Calendar.SECOND, 0); Intent intentAlarm = new Intent(context, PlayReceiver.class); // 以日期字串組出不同的 category intent.addCategory("D"+String.valueOf(mCal.get(Calendar.YEAR))+String.valueOf((mCal.get(Calendar.MONTH)+1))+String.valueOf(mCal.get(Calendar.DATE))); intentAlarm.putExtra("msg", "play_hskay"); PendingIntent pi = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT); AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); am.set(AlarmManager.RTC_WAKEUP, mCal.getTimeInMillis(), pi); }
另一種做法,則是在執行指定的工作時,順道註冊下一次的工作時刻。 當 Android 設備有關機重開的動作時,先前註冊而尚未執行的工作就會被清除,所以您可能還會需要一個在開機時執行註冊 AlarmManager 的程式。這個類別只是另一個 receiver,只不過我們加入了幾個 intent-filter,當 Android 設備發生符合 intent-filter 情況時,我們的程式會重新註冊 AlarmManager 指派的工作。 在 AndroidManifest.xml 加入:
<application> ... ... <!-- 接收開機完成廣播 --> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"> ... ... <!-- 當手機重開機,所要執行的程式。 --> <receiver android:name=".AlarmInitReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver> ... ... </uses-permission> </application>
再建立一個開機廣播類別:
public class AlarmInitReceiver extends BroadcastReceiver { // 重開機時重新註冊 AlarmManager .... }
另外還要注意,AlarmManager 的 app 必須安裝在手機內,也就是說
<manifest ... ... android:installLocation="internalOnly" android:versionCode="1" android:versionName="1.0" >否則您的 receiver 將接收不到開機完成的廣播,receiver 那段程式也就不會執行了。
拜讀了您的筆記,對於使用方法的細節介紹的非常詳細,文章的編排也容易閱讀,感謝您的分享。
回覆刪除淺顯易懂很受用, thx ^^
回覆刪除新手過路...想請教一下...如果程式完全退出for的功能還會在背景繼續運作嗎?
回覆刪除拍謝, 不大懂您的意思...
回覆刪除那段 for 迴圈做的事是向 Android 註冊 AlarmManager 的工作.
請問播放手機內建的鈴聲出來
回覆刪除播放只知道使用soundpool或是 media
想請問的是鈴聲可以從哪邊取得,不自己製作情況下
謝謝~
拍謝,我也不知道耶 :-p
回覆刪除