【後臺任務】安排重複報警(19)

警報(基於AlarmManager類)爲您提供了一種在應用程序生命週期之外執行基於時間的操作的方法。例如,您可以使用鬧鐘啓動長時間運行的操作,例如每天啓動一次服務以下載天氣預報。

警報具有以下特徵:

  • 他們讓你在設定的時間和/或時間間隔觸發意圖。
  • 您可以將它們與廣播接收器一起使用以啓動服務並執行其他操作。
  • 它們在應用程序之外運行,所以即使應用程序未運行,即使設備本身處於睡眠狀態,也可以使用它們觸發事件或操作。
  • 它們可以幫助您最大限度地減少應用程序的資源需求 您可以安排操作而不依賴定時器或連續運行後臺服務。

注意:對於在應用程序生命週期中保證發生的定時操作,請考慮將此類 Handler與Timerand 結合使用 Thread。這種方法可以讓Android更好地控制系統資源

瞭解權衡


重複報警是一種相對簡單的機制,靈活性有限。它可能不是您的應用程序的最佳選擇,特別是如果您需要觸發網絡操作。設計不當的警報可能會導致電池耗盡並給服務器帶來重大負擔。

觸發應用程序生命週期之外的操作的常見方案是將數據與服務器同步。這種情況下,您可能會試圖使用重複鬧鈴。但是,如果您擁有託管應用數據的服務器,則將Google雲消息傳遞(GCM)與同步適配器一起使用 是一種更好的解決方案AlarmManager。同步適配器爲您AlarmManager提供與之相同的計劃選項,但它爲您提供了更大的靈活性。例如,同步可能基於來自服務器/設備的“新數據”消息(請參閱 運行同步適配器有關詳細信息),用戶的活動(或不活動),一天中的時間等。有關何時以及如何使用GCM和同步適配器的詳細信息,請參閱本頁頂部的鏈接視頻。

設備處於打盹模式下時,警報不會觸發 。任何計劃的警報將被推遲到設備退出打盹。如果您需要確保您的工作即使在設備空閒時也能完成,有幾個選項可用。您可以使用 setAndAllowWhileIdle()或 setExactAndAllowWhileIdle()保證報警將執行。另一種選擇是使用新的WorkManager API,該API可以一次或定期執行後臺工作。有關更多信息,請參閱 使用WorkManager安排任務。

最佳實踐


您在設計重複警報時所做的每一項選擇都會對您的應用程序使用(或濫用)系統資源的方式產生影響。例如,想象一個與服務器同步的流行應用程序。如果同步操作基於時鐘時間,並且每個應用程序實例在晚上11:00同步,則服務器上的負載可能導致高延遲甚至“拒絕服務”。遵循以下使用警報的最佳做法:

將隨機性(抖動)添加到由於重複警報而觸發的任何網絡請求中:
當警報觸發時做任何本地工作。“本地工作”是指任何不會觸及服務器或從服務器請求數據的東西。
同時,安排包含網絡請求的警報在某個隨機時間段內觸發。
保持警報頻率最低。
請勿不必要地喚醒設備(此行爲由報警類型決定,如選擇報警類型中所述)。
不要讓鬧鐘的觸發時間更加準確。
使用,setInexactRepeating()而不是setRepeating()。當您使用時setInexactRepeating(),Android會同步來自多個應用程序的重複警報並同時觸發它們。這樣可以減少系統必須喚醒設備的總次數,從而減少電池消耗。從Android 4.4(API Level 19)開始,所有重複警報都是不精確的。請注意,儘管 setInexactRepeating()有所改進setRepeating(),但如果應用程序的每個實例都在同一時間點擊服務器,它仍然可能會淹沒服務器。因此,對於網絡請求,添加一些隨機性到您的警報,如上所述。

儘可能避免在鬧鐘時間內使用鬧鐘。
重複基於精確觸發時間的警報不能很好地擴展。ELAPSED_REALTIME如果可以,請使用。下一節將詳細介紹不同的報警類型。

設置重複鬧鐘


如上所述,重複報警對於安排常規事件或數據查找是一個很好的選擇。重複報警具有以下特徵:

  • 警報類型。有關更多討論,請參閱選擇報警類型。
  • 觸發時間。如果您指定的觸發時間過去,則會立即觸發警報。
  • 警報的間隔。例如,每天一次,每小時一次,每五分鐘一次等等。
  • 觸發警報時觸發的未決意圖。當您設置使用相同待定意圖的第二個警報時,它會替換原始警報。

選擇一種報警類型

使用重複報警的首要考慮因素之一是其類型應該是什麼。

有兩種通用的鬧鐘類型:“實時實時”和“實時時鐘”(RTC)。經過實時使用“系統啓動後的時間”作爲參考,實時時鐘使用UTC(掛鐘)時間。這意味着已過去的實時時間適合於根據時間的推移設置鬧鐘(例如,每30秒觸發一次鬧鐘),因爲它不受時區/區域設置的影響。實時時鐘類型更適合依賴當前語言環境的警報。

這兩種類型都有一個“喚醒”版本,即在屏幕關閉時喚醒設備的CPU。這確保了警報將在預定的時間觸發。如果您的應用程序具有時間依賴性,例如,如果它具有執行特定操作的有限窗口,這非常有用。如果您不使用鬧鈴類型的喚醒版本,則當您的設備下次醒來時,所有重複鬧鈴都會觸發。

如果您只需要在特定時間間隔(例如,每半小時)觸發一次鬧鐘,請使用其中一種實時類型。一般來說,這是更好的選擇。

如果您需要在一天中的特定時間觸發鬧鐘,請選擇其中一種基於時鐘的實時時鐘類型。但請注意,此方法可能有一些缺點 - 應用程序可能無法很好地轉換到其他語言環境,並且如果用戶更改設備的時間設置,則可能會導致應用程序出現意外行爲。如上所述,使用實時時鐘報警類型也不能很好地擴展。如果可以,我們建議您使用“經過實時”警報。

這裏是類型列表:

  • ELAPSED_REALTIME - 根據自設備啓動以來的時間量引發待定意圖,但不喚醒設備。經過的時間包括設備睡着的任何時間。
  • ELAPSED_REALTIME_WAKEUP - 設備啓動後經過指定的時間長度後,喚醒設備並觸發掛起的意圖。
  • RTC - 在指定的時間爲待定意圖啓動,但不會喚醒設備。
  • RTC_WAKEUP - 喚醒設備在指定的時間觸發掛起的意圖。

已用實時警報的示例

這裏有一些使用的例子ELAPSED_REALTIME_WAKEUP。

在30分鐘內將設備喚醒,然後每30分鐘發出一次:

// Hopefully your alarm will have a lower frequency than this!
alarmMgr.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
        SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR,
        AlarmManager.INTERVAL_HALF_HOUR, alarmIntent);

喚醒設備以在一分鐘內觸發一次性(非重複)警報:

private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
...
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
        SystemClock.elapsedRealtime() +
        60 * 1000, alarmIntent);

實時時鐘報警示例
這裏有一些使用的例子RTC_WAKEUP。

在大約下午2:00喚醒設備以發出警報,並且每天重複一次:

// Set the alarm to start at approximately 2:00 p.m.
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 14);

// With setInexactRepeating(), you have to use one of the AlarmManager interval
// constants--in this case, AlarmManager.INTERVAL_DAY.
alarmMgr.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
        AlarmManager.INTERVAL_DAY, alarmIntent);

在上午8點30分喚醒設備,以便每隔20分鐘啓動鬧鐘:

private AlarmManager alarmMgr;
private PendingIntent alarmIntent;
...
alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReceiver.class);
alarmIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

// Set the alarm to start at 8:30 a.m.
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(System.currentTimeMillis());
calendar.set(Calendar.HOUR_OF_DAY, 8);
calendar.set(Calendar.MINUTE, 30);

// setRepeating() lets you specify a precise custom interval--in this case,
// 20 minutes.
alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
        1000 * 60 * 20, alarmIntent);

決定您的警報需要的精確程度

如上所述,選擇警報類型通常是創建警報的第一步。另一個區別是你需要你的鬧鐘有多精確。對於大多數應用程序, setInexactRepeating()是正確的選擇。當您使用此方法時,Android會同步多個不精確的重複警報並同時觸發它們。這可以減少電池的消耗。

對於具有嚴格時間要求的罕見應用程序,例如,警報需要在上午8:30準確着火,然後每小時使用一次setRepeating()。但是如果可能的話,你應該避免使用精確的警報

有了setInexactRepeating(),你不能像你一樣指定一個自定義時間間隔 setRepeating()。你必須使用間隔常量之一,比如INTERVAL_FIFTEEN_MINUTES, INTERVAL_DAY等。請參閱AlarmManager 完整列表。

取消鬧鐘


根據您的應用程序,您可能希望包含取消鬧鐘的功能。要取消鬧鐘,請cancel()打開鬧鐘管理器,傳遞PendingIntent您不再想要的鬧鐘。例如:

// If the alarm has been set, cancel it.
if (alarmMgr!= null) {
    alarmMgr.cancel(alarmIntent);
}

設備重新啓動時發出警報


默認情況下,當設備關閉時,所有報警都會被取消。爲防止這種情況發生,您可以設計應用程序,以便在用戶重新啓動設備時自動重新啓動重複警報。這確保了AlarmManager將繼續執行其任務,而用戶不需要手動重啓警報。

這裏是步驟:
在應用程序的清單中設置權限。這允許您的應用程序在系統完成引導後接收 廣播的廣播(這只有在應用程序已經由用戶至少啓動一次時纔可用): RECEIVE_BOOT_COMPLETEDACTION_BOOT_COMPLETED

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

實現一個BroadcastReceiver接收廣播:

public class SampleBootReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
            // Set the alarm here.
        }
    }
}

使用意向過濾器將接收器添加到應用的清單文件中,該過濾器對ACTION_BOOT_COMPLETED動作進行過濾:

<receiver android:name=".SampleBootReceiver"
        android:enabled="false">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"></action>
    </intent-filter>
</receiver>

請注意,在清單中,引導接收器已設置爲 android:enabled="false"。這意味着除非應用程序明確啓用接收器,否則不會調用接收器。這可以防止引導接收器被不必要地調用。您可以啓用接收器(例如,如果用戶設置了警報),如下所示:

ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
PackageManager pm = context.getPackageManager();

pm.setComponentEnabledSetting(receiver,
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
        PackageManager.DONT_KILL_APP);

一旦以這種方式啓用接收器,即使用戶重新啓動設備,它也將保持啓用狀態。換句話說,即使在重新啓動時,以編程方式啓用接收器也會覆蓋清單設置。接收器將保持啓用狀態,直到您的應用停用。您可以禁用接收器(例如,如果用戶取消報警),如下所示:

ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
PackageManager pm = context.getPackageManager();

pm.setComponentEnabledSetting(receiver,
        PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
        PackageManager.DONT_KILL_APP);

打盹和應用待機的影響


在Android 6.0(API級別23)中引入了Doze和App Standby,以延長設備電池壽命。當設備處於打盹模式時,任何標準報警將被推遲,直到設備退出打盹模式或打開維護窗口。如果即使在打盹模式下也必須有警報火警,您可以使用 或。閒置時,您的應用將進入App Standby模式,這意味着用戶在一段時間內沒有使用它,並且該應用沒有前臺進程。當應用程序處於應用程序待機狀態時,警報延遲就像打盹模式一樣。當應用程序不再閒置或設備插入電源時,此限制即被解除。有關您的應用如何受到這些模式影響的更多信息,請閱讀 Optimize for Doze和App Standby。 setAndAllowWhileIdle() setExactAndAllowWhileIdle()

示例應用


要嘗試本指南中的概念,請下載示例應用程序

Lastest Update:2018.05.24

聯繫我

QQ:94297366
微信打賞:https://pan.baidu.com/s/1dSBXk3eFZu3mAMkw3xu9KQ

公衆號推薦:

【後臺任務】安排重複報警(19)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章