Android時鐘應用的定時框架分析

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


1、基本結構


Alarm

代表一條定時數據

AlarmInstance

代表一個定時項目的實例,一個AlarmInstance對應到一個Alarm,相比Alarm多存儲了一些狀態信息

AlarmStateManager

狀態管理器,對定時項目進行調度,添加、刪除、更改狀態,是一個BroadcastReciever,定時到點後發廣播到這裏進行下一步處理

AlarmService

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

ClockDataHelper

裏面創建了三個表,ALARMS_TABLE,INSTANCE_TABLE,CITIES_TABLE,前兩個分別對應到上面的Alarm和AlarmInstance。

    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");
    }
    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中管理。

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


2、具體的類分析


Alarm


從上面也可以看出,Alarm類作爲定時的基礎數據結構,主要是封裝了一些數據庫操作,完成增刪改查功能。額外有一個方法createInstanceAfter,根據自身來創建一個AlarmInstance實例。代碼如下

    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

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

    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

鬧鐘定時的核心邏輯就在這裏,AlarmStateManager就是管理所有定時項目狀態的調度器。



可以看到上面大多是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:

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()規劃下一個狀態:

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():

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方法中,

    @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);
                return;
            }

            if (alarmState >= 0) {
                setAlarmState(context, instance, alarmState);
            } else {
                registerInstance(context, instance, true);
            }
        } else if (SHOW_AND_DISMISS_ALARM_ACTION.equals(action)) {
            Uri uri = intent.getData();
            AlarmInstance instance = AlarmInstance.getInstance(context.getContentResolver(),
                    AlarmInstance.getId(uri));

            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中:

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);
                break;
            case AlarmInstance.HIDE_NOTIFICATION_STATE:
                setHideNotificationState(context, instance);
                break;
            case AlarmInstance.HIGH_NOTIFICATION_STATE:
                setHighNotificationState(context, instance);
                break;
            case AlarmInstance.FIRED_STATE:
                setFiredState(context, instance);
                break;
            case AlarmInstance.SNOOZE_STATE:
                setSnoozeState(context, instance);
                break;
            case AlarmInstance.MISSED_STATE:
                setMissedState(context, instance);
                break;
            case AlarmInstance.DISMISSED_STATE:
                setDismissState(context, instance);
                break;
            default:
                Log.e("Trying to change to unknown alarm state: " + state);
        }
    }

對沒一個state又轉移相應的setXXXState方法中,完成下一次狀態的轉換,形成一個定時的循環,直到在DISMISSED_STATE裏停用或者刪除定時項目,如果需要重複則獲取下一次定時的時間。

整體的框架就是這樣,在AlarmStateManager裏使用AlarmManager形成了一個定時的狀態機,不斷轉移到下一個狀態處理。

源碼在這裏https://android.googlesource.com/platform/packages/apps/DeskClock/+/android-4.4.4_r2.0.1

發佈了54 篇原創文章 · 獲贊 12 · 訪問量 72萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章