天天看點

android AlarmManager講解

android系統鬧鐘定時功能架構,總體來說就是用資料庫存儲定時資料,有一個狀态管理器來統一管理這些定時狀态的觸發和更新。在andriod系統中實作定時功能,最終還是要用到系統提供的alarmmanager,隻是當一個定時完成後怎麼繼續處理,或者中間怎麼更新定時的時間或者狀态,像鬧鐘這種應用程式,每天重複定時,或者一周選擇其中的幾天,鬧鐘響了延遲5分鐘再次響鈴,這時候就需要想一種好的辦法來讓管理這些資料和狀态,下面就分析一下android系統鬧鐘的實作。

代表一條定時資料

代表一個定時項目的執行個體,一個alarminstance對應到一個alarm,相比alarm多存儲了一些狀态資訊

狀态管理器,對定時項目進行排程,添加、删除、更改狀态,是一個broadcastreciever,定時到點後發廣播到這裡進行下一步處理

響應結果,也就是定時到達後要做的事,響鈴,停止響鈴

裡面建立了三個表,alarms_table,instance_table,cities_table,前兩個分别對應到上面的alarm和alarminstance。

android AlarmManager講解

private static void createalarmstable(sqlitedatabase db) {  

    db.execsql("create table " + alarms_table_name + " (" +  

            clockcontract.alarmscolumns._id + " integer primary key," +  

            clockcontract.alarmscolumns.hour + " integer not null, " +  

            clockcontract.alarmscolumns.minutes + " integer not null, " +  

            clockcontract.alarmscolumns.days_of_week + " integer not null, " +  

            clockcontract.alarmscolumns.enabled + " integer not null, " +  

            clockcontract.alarmscolumns.vibrate + " integer not null, " +  

            clockcontract.alarmscolumns.label + " text not null, " +  

            clockcontract.alarmscolumns.ringtone + " text, " +  

            clockcontract.alarmscolumns.delete_after_use + " integer not null default 0);");  

    log.i("alarms table created");  

}  

android AlarmManager講解

private static void createinstancetable(sqlitedatabase db) {  

    db.execsql("create table " + instances_table_name + " (" +  

            clockcontract.instancescolumns._id + " integer primary key," +  

            clockcontract.instancescolumns.year + " integer not null, " +  

            clockcontract.instancescolumns.month + " integer not null, " +  

            clockcontract.instancescolumns.day + " integer not null, " +  

            clockcontract.instancescolumns.hour + " integer not null, " +  

            clockcontract.instancescolumns.minutes + " integer not null, " +  

            clockcontract.instancescolumns.vibrate + " integer not null, " +  

            clockcontract.instancescolumns.label + " text not null, " +  

            clockcontract.instancescolumns.ringtone + " text, " +  

            clockcontract.instancescolumns.alarm_state + " integer not null, " +  

            clockcontract.instancescolumns.alarm_id + " integer references " +  

                alarms_table_name + "(" + clockcontract.alarmscolumns._id + ") " +  

                "on update cascade on delete cascade" +  

            ");");  

    log.i("instance table created");  

這裡說一下幾個特殊的字段,對于alarm表,days_of_week表示一周内需要定時的天(鬧鐘有個功能是選擇一周中的幾天),這裡是個int值,用位來表示設定的天數,源碼中有個專門的類daysofweek來存儲和處理。

alarminstance表中有一個alarm_id,關聯到一個alarm,可以看到在alarminstance表裡也有時間,為什麼不和alarm表合成一個表?應該是這樣的,alarm表示原始的定時項,是一個基礎資料,而alarminstance則代表了一個使用中的定時項目,或者是一個已經激活的定時項目,它的時間是可以變化的,比如鬧鐘響了以後延時5分鐘再響,就需要改變這裡的時間,而基礎資料不能變,還需要顯示在那裡。alarm_state代表了目前定時項目的狀态,具體排程都在alarmstatemanager中管理。

忘了在哪裡看到的,“程式設計最重要的是設計資料結構,接下來是分解各種代碼塊”。資料結構是基礎,就像建築裡的鋼筋水泥磚瓦,有了基礎的材料後,剩下的工作就是對這些材料處理,也就是設計具體的處理邏輯。

android AlarmManager講解

從上面也可以看出,alarm類作為定時的基礎資料結構,主要是封裝了一些資料庫操作,完成增删改查功能。額外有一個方法createinstanceafter,根據自身來建立一個alarminstance執行個體。代碼如下

android AlarmManager講解

public alarminstance createinstanceafter(calendar time) {  

    calendar nextinstancetime = calendar.getinstance();  

    nextinstancetime.set(calendar.year, time.get(calendar.year));  

    nextinstancetime.set(calendar.month, time.get(calendar.month));  

    nextinstancetime.set(calendar.day_of_month, time.get(calendar.day_of_month));  

    nextinstancetime.set(calendar.hour_of_day, hour);  

    nextinstancetime.set(calendar.minute, minutes);  

    nextinstancetime.set(calendar.second, 0);  

    nextinstancetime.set(calendar.millisecond, 0);  

    // if we are still behind the passed in time, then add a day  

    if (nextinstancetime.gettimeinmillis() <= time.gettimeinmillis()) {  

        nextinstancetime.add(calendar.day_of_year, 1);  

    }  

    // the day of the week might be invalid, so find next valid one  

    int adddays = daysofweek.calculatedaystonextalarm(nextinstancetime);  

    if (adddays > 0) {  

        nextinstancetime.add(calendar.day_of_week, adddays);  

    alarminstance result = new alarminstance(nextinstancetime, id);  

    result.mvibrate = vibrate;  

    result.mlabel = label;  

    result.mringtone = alert;  

    return result;  

alarminstance與alarm很相似,像alarm中的增删改查操作在alarminstance中都有相似的方法。那有什麼不同呢,就是上面說的alarminstance的時間是可以根據目前狀态改變的,也就多了時間的set和get方法。

android AlarmManager講解

public void setalarmtime(calendar calendar) {  

    myear = calendar.get(calendar.year);  

    mmonth = calendar.get(calendar.month);  

    mday = calendar.get(calendar.day_of_month);  

    mhour = calendar.get(calendar.hour_of_day);  

    mminute = calendar.get(calendar.minute);  

/** 

 * return the time when a alarm should fire. 

 * 

 * @return the time 

 */  

public calendar getalarmtime() {  

    calendar calendar = calendar.getinstance();  

    calendar.set(calendar.year, myear);  

    calendar.set(calendar.month, mmonth);  

    calendar.set(calendar.day_of_month, mday);  

    calendar.set(calendar.hour_of_day, mhour);  

    calendar.set(calendar.minute, mminute);  

    calendar.set(calendar.second, 0);  

    calendar.set(calendar.millisecond, 0);  

    return calendar;  

鬧鐘定時的核心邏輯就在這裡,alarmstatemanager就是管理所有定時項目狀态的排程器。

android AlarmManager講解

可以看到上面大多是static類型的方法,用于設定各種狀态值。

先看一下定時的幾種狀态:

silent_state,alarm被激活,但是不需要顯示任何東西,下一個狀态是low_notification_state;

low_notification_state,這個狀态表示alarm離觸發的時間不遠了,時間差是alarminstance.low_notification_hour_offset=-2,也就是2個小時。下一個狀态會進入high_notification_state,hide_notification_state,dismiss_state;

hide_notification_state,這是一個暫時态,表示使用者想隐藏掉通知,這個狀态會一直持續到high_notification_state;

high_notification_state,這個狀态和low_notification_state相似,但不允許使用者隐藏通知,負責觸發fired_state或者dismiss_state;

snoozed_state,像high_notification_state,但是會增加一點定時的時間來完成延遲功能;

fired_state,表示響鈴狀态,會啟動alarmservice直到使用者将其變為snoozed_state或者dismiss_state,如果使用者放任不管,會之後進入missed_state;

missed_state,這個狀态在fired_state之後,會在通知欄給出一個提醒剛才響鈴了;

dismiss_state,這個狀态表示定時結束了,會根據定時項目的設定判斷是否需要重複,進而決定要删除這個項目還是繼續設定一個新的定時。

上面的 setxxxstate 方法就是對這些狀态的處理,同時會規劃一個定時轉換到下一個狀态。比如setsilentstate:

android AlarmManager講解

public static void setsilentstate(context context, alarminstance instance) {  

        log.v("setting silent state to instance " + instance.mid);  

        // update alarm in db  

        contentresolver contentresolver = context.getcontentresolver();  

        instance.malarmstate = alarminstance.silent_state;  

        alarminstance.updateinstance(contentresolver, instance);  

        // setup instance notification and scheduling timers  

        alarmnotifications.clearnotification(context, instance);  

        scheduleinstancestatechange(context, instance.getlownotificationtime(),  

                instance, alarminstance.low_notification_state);  

更新alarminstance的資訊,同時通過scheduleinstancestatechange()規劃下一個狀态:

android AlarmManager講解

private static void scheduleinstancestatechange(context context, calendar time,  

            alarminstance instance, int newstate) {  

        long timeinmillis = time.gettimeinmillis();  

        log.v("scheduling state change " + newstate + " to instance " + instance.mid +  

                " at " + alarmutils.getformattedtime(context, time) + " (" + timeinmillis + ")");  

        intent statechangeintent = createstatechangeintent(context, alarm_manager_tag, instance,  

                newstate);  

        pendingintent pendingintent = pendingintent.getbroadcast(context, instance.hashcode(),  

                statechangeintent, pendingintent.flag_update_current);  

        alarmmanager am = (alarmmanager) context.getsystemservice(context.alarm_service);  

        if (utils.iskitkatorlater()) {  

            am.setexact(alarmmanager.rtc_wakeup, timeinmillis, pendingintent);  

        } else {  

            am.set(alarmmanager.rtc_wakeup, timeinmillis, pendingintent);  

        }  

通過alarmmanager發起一個定時,定時的時間從調用處可以看到是有alarminstance得到的,比如在setsilentstate()中的定時時間是instance.getlownotificationtime():

android AlarmManager講解

public calendar getlownotificationtime() {  

        calendar calendar = getalarmtime();  

        calendar.add(calendar.hour_of_day, low_notification_hour_offset);  

        return calendar;  

low_notification_hour_offset值為-2,也就是在鬧鈴響之前的兩小時那一刻會發這個low_notification_state的廣播出來,alarmstatemanager接收到這個廣播處理再轉移到下一個。廣播的接收在onreciever方法中,

android AlarmManager講解

    @override  

    public void onreceive(final context context, final intent intent) {  

        final pendingresult result = goasync();  

        final powermanager.wakelock wl = alarmalertwakelock.createpartialwakelock(context);  

        wl.acquire();  

        asynchandler.post(new runnable() {  

            @override  

            public void run() {  

                handleintent(context, intent);  

                result.finish();  

                wl.release();  

            }  

        });  

    private void handleintent(context context, intent intent) {  

        final string action = intent.getaction();  

        log.v("alarmstatemanager received intent " + intent);  

        if (change_state_action.equals(action)) {  

            uri uri = intent.getdata();  

            alarminstance instance = alarminstance.getinstance(context.getcontentresolver(),  

                    alarminstance.getid(uri));  

            if (instance == null) {  

                // not a big deal, but it shouldn't happen  

                log.e("can not change state for unknown instance: " + uri);  

                return;  

            int globalid = getglobalintentid(context);  

            int intentid = intent.getintextra(alarm_global_id_extra, -1);  

            int alarmstate = intent.getintextra(alarm_state_extra, -1);  

            if (intentid != globalid) {  

                log.i("ignoring old intent. intentid: " + intentid + " globalid: " + globalid +  

                        " alarmstate: " + alarmstate);  

            if (alarmstate >= 0) {  

                setalarmstate(context, instance, alarmstate);  

            } else {  

                registerinstance(context, instance, true);  

        } else if (show_and_dismiss_alarm_action.equals(action)) {  

            long alarmid = instance.malarmid == null ? alarm.invalid_id : instance.malarmid;  

            intent viewalarmintent = alarm.createintent(context, deskclock.class, alarmid);  

            viewalarmintent.putextra(deskclock.select_tab_intent_extra, deskclock.alarm_tab_index);  

            viewalarmintent.putextra(alarmclockfragment.scroll_to_alarm_intent_extra, alarmid);  

            viewalarmintent.addflags(intent.flag_activity_new_task);  

            context.startactivity(viewalarmintent);  

            setdismissstate(context, instance);  

在handleintent方法中統一處理,狀态的分發在setalarmstate中:

android AlarmManager講解

public void setalarmstate(context context, alarminstance instance, int state) {  

        switch(state) {  

            case alarminstance.silent_state:  

                setsilentstate(context, instance);  

                break;  

            case alarminstance.low_notification_state:  

                setlownotificationstate(context, instance);  

            case alarminstance.hide_notification_state:  

                sethidenotificationstate(context, instance);  

            case alarminstance.high_notification_state:  

                sethighnotificationstate(context, instance);  

            case alarminstance.fired_state:  

                setfiredstate(context, instance);  

            case alarminstance.snooze_state:  

                setsnoozestate(context, instance);  

            case alarminstance.missed_state:  

                setmissedstate(context, instance);  

            case alarminstance.dismissed_state:  

                setdismissstate(context, instance);  

            default:  

                log.e("trying to change to unknown alarm state: " + state);  

對沒一個state又轉移相應的setxxxstate方法中,完成下一次狀态的轉換,形成一個定時的循環,直到在dismissed_state裡停用或者删除定時項目,如果需要重複則擷取下一次定時的時間。

整體的架構就是這樣,在alarmstatemanager裡使用alarmmanager形成了一個定時的狀态機,不斷轉移到下一個狀态處理。