android 定時通知(永久長期的) 本篇只講AlarmManager使用

本篇使用AlarmManager達到準時執行某程式,跟鬧鐘的方式一樣,我之前用過IntentService、Service、Handler,都不能長期使用。
Servicer簡單缺點整理:
IntentService:只要關閉activity背景保存視窗,IntentService背景所執行的程式就被強迫結束,好像可以重開(我沒試過)。
Service、Handler:雖然關閉activity會繼續執行,但他是重新在開啟一次service,這到還好,但當手機螢幕關閉處於待機狀態時,service會停止運作暫停在那邊,等你喚醒再繼續執行,所以用這個加timer做定時通知會整個亂掉,timer會停下來不數了。

AlarmManager主要架構是
跟系統註冊鬧鐘甚麼時間點 → 時間到系統會發送一個鬧鈴 → 你的activity接收那個鬧鈴後執行你的程式
當你註冊好幾個鬧鐘已經晚於現在時間,他會馬上通通都執行完

先建立接收鬧鈴的程式

在Manifest.xml 中加入接收系統鬧鈴

<application>
    <activity
        android:name=".MainActivity">
    </activity>
    <!-- 當鬧鈴時間到達時要執行的程式 -->
    <receiver android:name=".AlarmReceiver">
        <intent-filter>
            <action android:name="activity_app" />
        </intent-filter>
    </receiver>

</application>

建立一個class檔,執行接收到鬧鈴後的程式


public class AlarmReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
    
        Bundle bData = intent.getExtras();
        if(bData.get("title").equals("activity_app"))
        {
            //主要執行的程式
        }
    }
}

註冊和取消鬧鐘

/***    加入(與系統註冊)鬧鐘    ***/
public static void add_alarm(Context context, Calendar cal) {
    Log.d(TAG, "alarm add time: " + String.valueOf(cal.get(Calendar.MONTH)) + "." + String.valueOf(cal.get(Calendar.DATE)) + " " + String.valueOf(cal.get(Calendar.HOUR_OF_DAY)) + ":" + cal.get(Calendar.MINUTE) + ":" + cal.get(Calendar.SECOND));

    Intent intent = new Intent(context, AlarmReceiver.class);
    // 以日期字串組出不同的 category 以添加多個鬧鐘
    intent.addCategory("ID." + String.valueOf(cal.get(Calendar.MONTH)) + "." + String.valueOf(cal.get(Calendar.DATE)) + "-" + String.valueOf((cal.get(Calendar.HOUR_OF_DAY) )) + "." + String.valueOf(cal.get(Calendar.MINUTE)) + "." + String.valueOf(cal.get(Calendar.SECOND)));
    String AlarmTimeTag = "Alarmtime " + String.valueOf(cal.get(Calendar.HOUR_OF_DAY)) + ":" + String.valueOf(cal.get(Calendar.MINUTE)) + ":" + String.valueOf(cal.get(Calendar.SECOND));

    intent.putExtra("title", "activity_app");
    intent.putExtra("time", AlarmTimeTag);

    PendingIntent pi = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    AlarmManager am = (AlarmManager) context.getSystemService(ALARM_SERVICE);
    am.set(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pi);       //註冊鬧鐘
}

/***    取消(與系統註冊的)鬧鐘    ***/
private static void cancel_alarm(Context context, Calendar cal) {
    Log.d(TAG, "alarm cancel time: " + String.valueOf(cal.get(Calendar.MONTH)) + "." + String.valueOf(cal.get(Calendar.DATE)) + " " + String.valueOf(cal.get(Calendar.HOUR_OF_DAY)) + ":" + cal.get(Calendar.MINUTE) + ":" + cal.get(Calendar.SECOND));

    Intent intent = new Intent(context, AlarmReceiver.class);
    // 以日期字串組出不同的 category 以添加多個鬧鐘
    intent.addCategory("ID." + String.valueOf(cal.get(Calendar.MONTH)) + "." + String.valueOf(cal.get(Calendar.DATE)) + "-" + String.valueOf((cal.get(Calendar.HOUR_OF_DAY) )) + "." + String.valueOf(cal.get(Calendar.MINUTE)) + "." + String.valueOf(cal.get(Calendar.SECOND)));
    String AlarmTimeTag = "Alarmtime " + String.valueOf(cal.get(Calendar.HOUR_OF_DAY)) + ":" + String.valueOf(cal.get(Calendar.MINUTE)) + ":" + String.valueOf(cal.get(Calendar.SECOND));

    intent.putExtra("title", "activity_app");
    intent.putExtra("time", AlarmTimeTag);

    PendingIntent pi = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    AlarmManager am = (AlarmManager) context.getSystemService(ALARM_SERVICE);
    am.cancel(pi);    //取消鬧鐘,只差在這裡
}

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

如何呼叫此函式

//下一分鐘0秒時
Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT+8:00")); //取得時間
cal.add(Calendar.MINUTE, 1);    //加一分鐘
cal.set(Calendar.SECOND, 0);    //設定秒數為0
add_alarm(context, cal);        //註冊鬧鐘
//在MainActivity中 context = this
//在service中      context = context(service的)
//註冊多個,每分鐘響共10次
Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT+8:00")); //取得時間
for(int i = 0; i < 10; i++){
    cal.add(Calendar.MINUTE, 1);    //加一分鐘
    cal.set(Calendar.SECOND, 0);    //設定秒數為0
    add_alarm(context, cal);        //註冊鬧鐘
}
//在MainActivity中 context = this
//在service中      context = context(service的)
也可以在AlarmReceiver 中註冊下一個鬧鐘,也就是說當第一個響起,註冊下一個

開機註冊

但是當手機重新開機過後,所有的鬧鐘都會消失,要重新註冊

要在manifest.xml中加入接收開機系統廣播的通知

<manifest>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <application>
        <receiver android:name=".BootUpReceiver"
            android:enabled="false">   //官方建議先設false要用時再true
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"></action>
            </intent-filter>
        </receiver>
    </application>        
</manifest>
建立BootUpReceiver 的 class檔


public class BootUpReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO: This method is called when the BroadcastReceiver is receiving
        // an Intent broadcast.
//        throw new UnsupportedOperationException("Not yet implemented");

           /* 同一個接收者可以收多個不同行為的廣播,所以可以判斷收進來的行為為何,再做不同的動作 */
        if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
            /* 收到廣播後要做的事 */

            //建立通知發布鬧鐘
            Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("GMT+8:00")); //取得時間
            for(int i = 0; i < 10; i++){
                cal.add(Calendar.MINUTE, 1);    //加一分鐘
                cal.set(Calendar.SECOND, 0);    //設定秒數為0
                MainActivity.add_alarm(context, cal);        //註冊鬧鐘
            } 
        }
    }
}

開啟與關閉BootUpReceiver 接收功能

//開機執行
ComponentName receiver = new ComponentName(this, BootUpReceiver.class);
PackageManager pm = this.getPackageManager();
pm.setComponentEnabledSetting(receiver,
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
        PackageManager.DONT_KILL_APP);

//取消開機執行
ComponentName receiver = new ComponentName(this, BootUpReceiver.class);
PackageManager pm = this.getPackageManager();
pm.setComponentEnabledSetting(receiver,
        PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
        PackageManager.DONT_KILL_APP);


參考自:
官方:https://developer.android.com/training/scheduling/alarms.html
其他:http://oldgrayduck.blogspot.tw/2012/10/androidalarmmanager.html
http://blog.tonycube.com/2012/10/alarmnotification.html
http://yinlamdevelop.blogspot.tw/2015/01/alarmmanager.html

留言

這個網誌中的熱門文章

C# 模擬鍵盤滑鼠控制電腦

python pyautogui 簡介

raspberrypi 開機自動執行程式 與 在terminal開啟第二個terminal執行python

python nn 聲音辨識 -1 傅立葉轉換