AlarmManager-系統推薦的定時任務

近期leader提了很多這樣的需求:每隔幾個小時拉取服務器的配置信息存在本地、每隔一段時間跟服務端校對一下本地時間、每隔一段時間上傳一下本地日誌等等。其實這些本質都是定時任務,隔一段時間去幹xxx,那麼在安卓中定時任務無非幾種實現方式,Handler(CountDownTimer)、Timer、while循環、AlarmManager。(如果有遺漏還望留言告知O(∩_∩)O謝謝)前三種大家基本都用過,也就不多贅言,本篇blog專注介紹AlarmManager。

AlarmManager介紹


見名知意鬧鐘管理者,當然不代表AlarmManager只是用來做鬧鐘應用的,作爲一個系統級別的提示服務,其實它的作用和Timer有點相似

  1. 在指定時長後執行某項操作
  2. 週期性的執行某項操作

並且AlarmManager對象可以配合Intent使用,定時的開啓一個Activity,發送一個BroadCast,或者開啓一個Service.那麼用它實現定時任務再好不過了。

使用


AlarmManager初體驗

先來一發簡單的Demo體驗一下

AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

Intent intent = new Intent(this, AlarmService.class);
intent.setAction(AlarmService.ACTION_ALARM);
PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
if(Build.VERSION.SDK_INT < 19){
    am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5000, pendingIntent);
}else{
    am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 5000, pendingIntent);
}

這個例子就是5秒鐘後發送一個Action爲AlarmService.ACTION_ALARM的Intent到AlarmService。


AlarmManager的常用API

set(int type, long triggerAtMillis, PendingIntent operation)

該方法用於設置一次性鬧鐘,第一個參數表示鬧鐘類型,第二個參數表示鬧鐘執行時間,第三個參數表示鬧鐘響應動作。

setRepeating(int type, long triggerAtMillis,long intervalMillis, PendingIntent operation)

該方法用於設置重複鬧鐘,第一個參數表示鬧鐘類型,第二個參數表示鬧鐘首次執行時間,第三個參數表示鬧鐘兩次執行的間隔時間,第四個參數表示鬧鐘響應動作。

setInexactRepeating(int type, long triggerAtMillis,long intervalMillis, PendingIntent operation)

該方法也用於設置重複鬧鐘,與第二個方法相似,不過鬧鐘時間不精確。

setExact(int type, long triggerAtMillis, PendingIntent operation)
setWindow(int type, long windowStartMillis, long windowLengthMillis,PendingIntent operation)

方法1和方法2在SDK_INT 19以前是精確的鬧鐘,19以後爲了節能省電(減少系統喚醒和電池使用)。使用Alarm.set()和Alarm.setRepeating()已經不能保證精確性,不過還好Google又提供了兩個精確的Alarm方法setWindow()和setExact(),所以19以後需要精確的鬧鐘就需要上面兩個方法,具體原因後面再說

cancel(PendingIntent operation)

取消Intent相同的鬧鐘,這裏是根據Intent中filterEquals(Intent other)方法來判斷是否相同

public boolean filterEquals(Intent other) {
        if (other == null) {
            return false;
        }
        if (!Objects.equals(this.mAction, other.mAction)) return false;
        if (!Objects.equals(this.mData, other.mData)) return false;
        if (!Objects.equals(this.mType, other.mType)) return false;
        if (!Objects.equals(this.mPackage, other.mPackage)) return false;
        if (!Objects.equals(this.mComponent, other.mComponent)) return false;
        if (!Objects.equals(this.mCategories, other.mCategories)) return false;

        return true;
    }

從方法體可以看出mAction、mData、mType、mPackage、mComponent、mCategories這幾個完全一樣就認定爲同一Intent


鬧鐘類型

這個鬧鐘類型就是前面setxxx()方法第一個參數int type.

  • AlarmManager.ELAPSED_REALTIME:使用相對時間,可以通過SystemClock.elapsedRealtime() 獲取(從開機到現在的毫秒數,包括手機的睡眠時間),設備休眠時並不會喚醒設備。
  • AlarmManager.ELAPSED_REALTIME_WAKEUP:與ELAPSED_REALTIME基本功能一樣,只是會在設備休眠時喚醒設備。
  • AlarmManager.RTC:使用絕對時間,可以通過 System.currentTimeMillis()獲取,設備休眠時並不會喚醒設備。
  • AlarmManager.RTC_WAKEUP: 與RTC基本功能一樣,只是會在設備休眠時喚醒設備。


舉個栗子

1.點擊按鈕,AlarmManager3秒後發送intent到service彈出toast提示.

activity代碼

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

    }

    public void alarm(View v){
        AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

        Intent intent = new Intent(this, AlarmService.class);
        intent.setAction(AlarmService.ACTION_ALARM);
        PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        if(Build.VERSION.SDK_INT < 19){
            am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 3000, pendingIntent);
        }else{
            am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 3000, pendingIntent);
        }
    }

}

service代碼

public class AlarmService extends Service {

    public static String ACTION_ALARM = "action_alarm";
    private Handler mHanler = new Handler(Looper.getMainLooper());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mHanler.post(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(AlarmService.this, "鬧鐘來啦", Toast.LENGTH_SHORT).show();
            }
        });
        return super.onStartCommand(intent, flags, startId);
    }
}

效果如下




2.具體年月日啓動鬧鐘

核心代碼如下

Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR,2016);
        calendar.set(Calendar.MONTH,Calendar.DECEMBER);
        calendar.set(Calendar.DAY_OF_MONTH,16);
        calendar.set(Calendar.HOUR_OF_DAY,11);
        calendar.set(Calendar.MINUTE,50);
        calendar.set(Calendar.SECOND,0);
        //設定時間爲 2016年12月16日11點50分0秒

        AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

        Intent intent = new Intent(this, AlarmService.class);
        intent.setAction(AlarmService.ACTION_ALARM);
        PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        if(Build.VERSION.SDK_INT < 19){
            am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
        }else{
            am.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
        }

效果跟上面沒區別


AlarmManager鬧鐘不準原因

前面介紹的所有的set方法其實都是調用內部的一個private的方法setImpl(),只是不同的set方法傳入的值不同而已

private void setImpl(int type, long triggerAtMillis, long windowMillis, long intervalMillis,
            int flags, PendingIntent operation, final OnAlarmListener listener, String listenerTag,
            Handler targetHandler, WorkSource workSource, AlarmClockInfo alarmClock) {
        if (triggerAtMillis < 0) {
            /* NOTYET
            if (mAlwaysExact) {
                // Fatal error for KLP+ apps to use negative trigger times
                throw new IllegalArgumentException("Invalid alarm trigger time "
                        + triggerAtMillis);
            }
            */
            triggerAtMillis = 0;
        }

        ......

        try {
            mService.set(mPackageName, type, triggerAtMillis, windowMillis, intervalMillis, flags,
                    operation, recipientWrapper, listenerTag, workSource, alarmClock);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
    }

這裏只展示了相關代碼,而具體控是否精確是靠windowMillis這個參數

在看看普通的set()與setRepeating()方法如何傳遞windowMillis參數

public void set(int type, long triggerAtMillis, PendingIntent operation) {
        setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null,
                null, null, null);
    }

public void setRepeating(int type, long triggerAtMillis,
        long intervalMillis, PendingIntent operation) {
    setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation,
            null, null, null, null, null);
}

可以發現windowMillis參數爲legacyExactLength()方法返回值的,那麼我們接着在看legacyExactLength方法

可以看出mAlwaysExact這個變量控制着該方法的返回值,如果是小於API19的版本會使用
WINDOW_EXACT參數,這個參數是0(意思就是區間設置爲0,那麼就會按照triggerAtMillis這個時間準時觸發,也就是精準觸發)另一個參數WINDOW_HEURISTIC的值是-1,這個值具體的用法就要看AlarmManagerService具體的實現了,反正只要知道這個值是不精準就可以。而setExact()這個值爲WINDOW_EXACT,setWindow()的話這個值你可以自己傳所以19以後他們是精準的.


相關知識


本篇blog只以getService()方式舉了栗子,還可通過getBroadCast()發送廣播或getActivity()啓動Activity來執行某項固定任務。其中各方法的最後一個參數含有以下常量分別代表不同含義的任務執行效果:

  • FLAG_CANCEL_CURRENT:如果當前系統中已經存在一個相同的PendingIntent對象,那麼就將先將已有的PendingIntent取消,然後重新生成一個PendingIntent對象。

  • FLAG_NO_CREATE:如果當前系統中不存在相同的PendingIntent對象,系統將不會創建該PendingIntent對象而是直接返回null。

  • FLAG_ONE_SHOT:該PendingIntent只作用一次。在該PendingIntent對象通過send()方法觸發過後,PendingIntent將自動調用cancel()進行銷燬,那麼如果你再調用send()方法的話,系統將會返回一個SendIntentException。

  • FLAG_UPDATE_CURRENT:如果系統中有一個和你描述的PendingIntent對等的PendingInent,那麼系統將使用該PendingIntent對象,但是會使用新的Intent來更新之前PendingIntent中的Intent對象數據,例如更新Intent中的Extras。



總結


AlarmManager非常適合Android中定時任務.並且因爲他具有喚醒CPU的功能,可以保證每次需要執行特定任務時CPU都能正常工作,
或者說當CPU處於休眠時註冊的鬧鐘會被保留(可以喚醒CPU),(老司機們請注意此處有彎道減速慢行)但是國內Rom衆多.有的可能休眠時候無法喚醒..但是我還是推薦用AlarmManager….233333

ps:對對對,差點忘了趕緊加上…是諮詢了錘哥(錘廠CTO)才知道AlarmManager也可以做定時任務的..

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