Analysis of the timing framework of the Android clock application and the android clock framework
In general, the Android system uses a database to store scheduled data. A status manager is provided to centrally manage the trigger and update of these scheduled states. To implement the timing function in the Andriod system, we still need to use the AlarmManager provided by the system. It is just how to continue processing after a scheduled task is completed, or how to update the scheduled time or status in the middle, an application like an alarm clock repeats its timing every day, or selects a few days in a week. The alarm clock rings again after a delay of 5 minutes, at this time, we need to think of a good way to manage the data and status. Next we will analyze the implementation of the Android system alarm clock.
1. Basic Structure
Alarm
Indicates a scheduled data entry.
AlarmInstance
Represents an instance of a scheduled project. An AlarmInstance corresponds to an Alarm, which stores more status information than Alarm.
AlarmStateManager
Status manager, which schedules scheduled projects, adds, deletes, and changes the status. It is a BroadcastReciever, Which is broadcasted here after the scheduled point for further processing.
AlarmService
Response results, that is, what needs to be done after scheduled arrival.
ClockDataHelper
Three tables are created: ALARMS_TABLE, INSTANCE_TABLE, and CITIES_TABLE. The first two correspond to the Alarm and AlarmInstance above respectively.
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"); }
Here we will talk about several special fields. For the Alarm table, DAYS_OF_WEEK indicates the days that need to be scheduled in a week (the Alarm clock has a function to select a day in a week). Here it is an int value, the number of days set is expressed in bits. The source code contains a special class DaysOfWeek for storage and processing.
The ALARM_ID in the AlarmInstance table is associated with an Alarm. You can see that there is time in the AlarmInstance table. Why not synthesize a table from the Alarm table? This should be the case. Alarm indicates the original scheduled item and basic data, while AlarmInstance indicates a scheduled item in use or an activated scheduled item, its time can be changed. For example, if the alarm is delayed for 5 minutes, the time here needs to be changed. The basic data cannot be changed and needs to be displayed there. ALARM_STATE indicates the status of the current scheduled project. Specific scheduling is managed in AlarmStateManager.
I forgot where to see it. "The most important thing about programming is to design the data structure. Next, we will break down various code blocks ". The data structure is the foundation, just like the reinforced cement bricks and tiles in the building. With the basic materials, the rest of the work is to process these materials, that is, to design specific processing logic.
2. Specific Class Analysis
Alarm
As shown in the preceding figure, the Alarm class is the basic regular data structure, which encapsulates some database operations to complete addition, deletion, modification, and query functions. There is an additional method createInstanceAfter to create an AlarmInstance instance based on itself. The Code is as follows:
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 is similar to Alarm. For example, the addition, deletion, modification, and query operations in Alarm have similar methods in AlarmInstance. What is the difference? The AlarmInstance time mentioned above can be changed according to the current state, and the set and get methods with more time are used.
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
The core logic of scheduled alarm is here. AlarmStateManager is the scheduler that manages the status of all scheduled projects.
We can see that most of the above methods are static, which is used to set various status values.
Let's take a look at several scheduled statuses:
SILENT_STATE, alarm is activated, but nothing needs to be displayed. The next state is LOW_NOTIFICATION_STATE;
LOW_NOTIFICATION_STATE indicates that alarm is not far from the trigger time. The time difference is AlarmInstance. LOW_NOTIFICATION_HOUR_OFFSET =-2, that is, 2 hours. The next status will go to HIGH_NOTIFICATION_STATE, HIDE_NOTIFICATION_STATE, DISMISS_STATE;
Hide_icationication_state: this is a temporary state, indicating that the user wants to hide the notification, which will continue until HIGH_NOTIFICATION_STATE;
HIGH_NOTIFICATION_STATE, which is similar to LOW_NOTIFICATION_STATE, but does not allow users to hide notifications and trigger FIRED_STATE or DISMISS_STATE;
SNOOZED_STATE, such as HIGH_NOTIFICATION_STATE, but it will increase the scheduled time to complete the delay function;
FIRED_STATE, which indicates the alarm status. AlarmService is started until the user changes it to SNOOZED_STATE or DISMISS_STATE. If the user does not care about it, the system enters MISSED_STATE;
MISSED_STATE. After FIRED_STATE, a notification is sent in the notification bar, indicating that the alarm is triggered;
DISMISS_STATE. This State indicates that the scheduled task is finished. The system checks whether the task needs to be repeated based on the scheduled Project Settings, and determines whether to delete the project or set a new scheduled task.
The preceding setXXXState method is used to process these statuses. At the same time, a scheduled switch is planned to the next state. For example, 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); }
Update AlarmInstance information and plan the next state through 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); } }
When AlarmManager initiates a scheduled time, AlarmInstance is obtained from the call. For example, the scheduled time in setSilentState () is instance. getLowNotificationTime ():
public Calendar getLowNotificationTime() { Calendar calendar = getAlarmTime(); calendar.add(Calendar.HOUR_OF_DAY, LOW_NOTIFICATION_HOUR_OFFSET); return calendar; }
The low_icationication_hour_offset value is-2, that is, the LOW_NOTIFICATION_STATE broadcast will be sent two hours before the alarm is triggered, and AlarmStateManager receives the broadcast processing and transfers it to the next one. The broadcast is received in the onReciever method,
@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); } }}
In the handleIntent method, the status is distributed in 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); } }
The corresponding setXXXState method is transferred for no state to complete the next state conversion and form a scheduled cycle until the scheduled project is disabled or deleted in DISMISSED_STATE, if you need to repeat the time, obtain the next scheduled time.
The overall framework is like this. Using AlarmManager in AlarmStateManager forms a timed state machine, which is constantly transferred to the next state for processing.
Https://android.googlesource.com/platform/packages/apps/DeskClock/+/android-4.4.4_r2.0.1 source code here
Design of scheduled electronic clock
I assigned a high point.
If you want to set the scheduled time of the clock control, you can use the following attributes to set the vb
Dim a As Integer
Private Sub commandementclick ()
If Val (Text1.Text) = 0 Then
MsgBox "Enter the countdown! ", 16," prompt"
Else
Timer1.Enabled = True
A = Text1.Text
End If
End Sub
Private Sub Form_Load ()
Timer1.Enabled = False
End Sub
Private Sub Timer1_Timer ()
Label1.Caption =
A = a-1
If a = 0 Then
MsgBox "Time! ", VbOKOnly," prompt"
Timer1.Enabled = False
End If
End Sub
The code is like this.
This is the control