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 區段寫入要接收廣播的程式
<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 系統會比對已註冊的其他 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 還不錯用,如下:
// 向 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 那段程式也就不會執行了。

6 則留言:

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

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

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

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

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

    回覆刪除