public final class MatchTimer {
.
.
.
public static final int MINUTE_MILLIS = 60000;
private long start;
private long currentStoppage;
private long totalStoppages;
private long end;
.
.
.
public long getElapsed() {
if (isRunning()) {
return System.currentTimeMillis() - start;
}
if (end > 0) {
return end - start;
}
return 0;
}
public boolean isRunning() {
return start > 0 && end == 0;
}
public boolean isPaused() {
return currentStoppage > 0;
}
public int getElapsedMinutes() {
return (int) ((System.currentTimeMillis() - start) / MINUTE_MILLIS);
}
public long getTotalStoppages() {
long now = System.currentTimeMillis();
if (isPaused()) {
return totalStoppages + (now - currentStoppage);
}
return totalStoppages;
}
public long getPlayed() {
return getElapsed() - getTotalStoppages();
}
public long getStartTime() {
return start;
}
.
.
.
}
这些都是基本的java代码,就不费时间讲了。下面的函数更高级一些,可以操作计时器的状态。
public final class MatchTimer {
.
.
.
public void start() {
if (end > 0) {
start = System.currentTimeMillis() - (end - start);
end = 0;
} else {
start = System.currentTimeMillis();
}
save();
}
public void stop() {
if (isPaused()) {
resume();
}
end = System.currentTimeMillis();
save();
}
public void pause() {
currentStoppage = System.currentTimeMillis();
save();
}
public void resume() {
totalStoppages += System.currentTimeMillis() - currentStoppage;
currentStoppage = 0L;
save();
}
public void reset() {
resetWithoutSave();
save();
}
private void resetWithoutSave() {
start = 0L;
currentStoppage = 0L;
totalStoppages = 0L;
end = 0L;
}
}
这些还是基本的Java代码,也可以不用讲了。只有save()方法我们还没有见到,这是在类的最后写的,这个函数才值得的我们讲讲。
前一篇文章我们讨论了关于唤醒机制的问题,我们不需要去维持一个长连接或者后台服务,只需要维持这几个计时器的状态就可以了。我们使用SharedPreference来实现:
public final class MatchTimer implements SharedPreferences.OnSharedPreferenceChangeListener {
private static final String KEY_START = "com.stylingandroid.matchtimer.KEY_START";
private static final String KEY_CURRENT_STOPPAGE = "com.stylingandroid.matchtimer.KEY_CURRENT_STOPPAGE";
private static final String KEY_TOTAL_STOPPAGES = "com.stylingandroid.matchtimer.KEY_TOTAL_STOPPAGES";
private static final String KEY_END = "com.stylingandroid.matchtimer.KEY_END";
private static final String PREFERENCES = "MatchTimer";
private final SharedPreferences preferences;
public static MatchTimer newInstance(Context context) {
SharedPreferences preferences = context.getSharedPreferences(PREFERENCES, Context.MODE_PRIVATE);
long start = preferences.getLong(KEY_START, 0);
long currentStoppage = preferences.getLong(KEY_CURRENT_STOPPAGE, 0);
long totalStoppages = preferences.getLong(KEY_TOTAL_STOPPAGES, 0);
long end = preferences.getLong(KEY_END, 0);
return new MatchTimer(preferences, start, currentStoppage, totalStoppages, end);
}
private MatchTimer(SharedPreferences preferences, long start, long currentStoppage, long totalStoppages, long end) {
this.preferences = preferences;
this.start = start;
this.currentStoppage = currentStoppage;
this.totalStoppages = totalStoppages;
this.end = end;
}
public void save() {
preferences.edit()
.putLong(KEY_START, start)
.putLong(KEY_CURRENT_STOPPAGE, currentStoppage)
.putLong(KEY_TOTAL_STOPPAGES, totalStoppages)
.putLong(KEY_END, end)
.apply();
}
public void registerForUpdates() {
preferences.registerOnSharedPreferenceChangeListener(this);
}
public void unregisterForUpdates() {
preferences.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
long value = sharedPreferences.getLong(key, 0L);
if (key.equals(KEY_START)) {
start = value;
} else if (key.equals(KEY_END)) {
end = value;
} else if (key.equals(KEY_CURRENT_STOPPAGE)) {
currentStoppage = value;
} else if (key.equals(KEY_TOTAL_STOPPAGES)) {
totalStoppages = value;
}
}
.
.
.
}
我们需要的就是newInstance()方法从SharedPreference中构造一个MatchTimer实例,我们还需要save()方法,可以帮我们把当前的计时器状态保存到SharedPreference中。
最后我们要说明的是,如果某一部分持有MatchTimer对象的引用,但是其他对象已经改变了计时器的状态,就可能会发生异常(见下一篇文章)。所以我们还需要提供一些方法去注册和注销MatchTImer的实例,在Sharedpreference的值改变时去接收计时器状态的变化。
现在我们已经定义了一个基本的计时器了,下一篇文章我们会介绍怎么保持计时器的状态以及在需要的时候去唤醒这些状态。
Match Timer 可以在Google Play上下载:[url=https://play.google.com/store/apps/details?id=com.stylingandroid.matchtimer]Match Timer[/url].
在本系列前几篇文章中,我们介绍了Android Wear计时器app,对设计思路和app的结构进行了分析。本文将讲解如何定时唤醒程序提醒用户。
对于为什么不用后台服务的方式一直运行,我们已经进行了解释——这种方式非常耗电。因此,我们必须要有一个定时唤醒机制。我们可以使用AlarmManager来实现这个机制,定时执行一个Intent,然后通知BroadcastReceiver。之所以选择BroadcastReceiver而不用IntentService,是因为我们要运行的任务是轻量级的而且生命周期非常短暂。使用BroadcastReceiver可以避免每次执行任务的时候都经历Service的整个生命周期。因此,对于我们这种轻量级的任务来说非常合适——我们执行的任务都在毫秒级。
BroadcastReceiver的核心在于onReceiver方法,我们需要在这里安排各种事件响应。
public class MatchTimerReceiver extends BroadcastReceiver {
public static final int MINUTE_MILLIS = 60000;
private static final long DURATION = 45 * MINUTE_MILLIS;
private static final Intent UPDATE_INTENT = new Intent(ACTION_UPDATE);
private static final Intent ELAPSED_ALARM = new Intent(ACTION_ELAPSED_ALARM);
private static final Intent FULL_TIME_ALARM = new Intent(ACTION_FULL_TIME_ALARM);
private static final int REQUEST_UPDATE = 1;
private static final int REQUEST_ELAPSED = 2;
private static final int REQUEST_FULL_TIME = 3;
public static void setUpdate(Context context) {
context.sendBroadcast(UPDATE_INTENT);
}
.
.
.
private void reset(MatchTimer timer) {
timer.reset();
}
private void resume(Context context, MatchTimer timer) {
timer.resume();
long playedEnd = timer.getStartTime() + timer.getTotalStoppages() + DURATION;
if (playedEnd > System.currentTimeMillis()) {
setAlarm(context, REQUEST_FULL_TIME, FULL_TIME_ALARM, playedEnd);
}
}
private void pause(Context context, MatchTimer timer) {
timer.pause();
cancelAlarm(context, REQUEST_FULL_TIME, FULL_TIME_ALARM);
long elapsedEnd = timer.getStartTime() + DURATION;
if (!isAlarmSet(context, REQUEST_ELAPSED, ELAPSED_ALARM) && elapsedEnd > System.currentTimeMillis()) {
setAlarm(context, REQUEST_ELAPSED, ELAPSED_ALARM, elapsedEnd);
}
}
private void stop(Context context, MatchTimer timer) {
timer.stop();
cancelAlarm(context, REQUEST_UPDATE, UPDATE_INTENT);
cancelAlarm(context, REQUEST_ELAPSED, ELAPSED_ALARM);
cancelAlarm(context, REQUEST_FULL_TIME, FULL_TIME_ALARM);
}
private void start(Context context, MatchTimer timer) {
timer.start();
long elapsedEnd = timer.getStartTime() + DURATION;
setRepeatingAlarm(context, REQUEST_UPDATE, UPDATE_INTENT);
if (timer.getTotalStoppages() > 0 && !timer.isPaused()) {
long playedEnd = timer.getStartTime() + timer.getTotalStoppages() + DURATION;
if (playedEnd > System.currentTimeMillis()) {
setAlarm(context, REQUEST_FULL_TIME, FULL_TIME_ALARM, playedEnd);
}
if (elapsedEnd > System.currentTimeMillis()) {
setAlarm(context, REQUEST_ELAPSED, ELAPSED_ALARM, elapsedEnd);
}
} else {
if (elapsedEnd > System.currentTimeMillis()) {
setAlarm(context, REQUEST_FULL_TIME, FULL_TIME_ALARM, elapsedEnd);
}
}
}
.
.
.
}
代码还是非常直观易于理解的。首先实例化一个MatchTimer对象(从SharedPreference中读取数据),然后分别传给对应的事件处理Handler。之后等待动作发生,最后更新Notification。
这里会处理8个事件动作,其中5个负责控制计时器的状态(START、STOP、PAUSE、RESUME、RESET);一个负责更新Notification,剩下两个负责到45分钟唤醒后震动提示。
我们先从这几个控制状态开始:
public class MatchTimerReceiver extends BroadcastReceiver {
public static final int MINUTE_MILLIS = 60000;
private static final long DURATION = 45 * MINUTE_MILLIS;
private static final Intent UPDATE_INTENT = new Intent(ACTION_UPDATE);
private static final Intent ELAPSED_ALARM = new Intent(ACTION_ELAPSED_ALARM);
private static final Intent FULL_TIME_ALARM = new Intent(ACTION_FULL_TIME_ALARM);
private static final int REQUEST_UPDATE = 1;
private static final int REQUEST_ELAPSED = 2;
private static final int REQUEST_FULL_TIME = 3;
public static void setUpdate(Context context) {
context.sendBroadcast(UPDATE_INTENT);
}
.
.
.
private void reset(MatchTimer timer) {
timer.reset();
}
private void resume(Context context, MatchTimer timer) {
timer.resume();
long playedEnd = timer.getStartTime() + timer.getTotalStoppages() + DURATION;
if (playedEnd > System.currentTimeMillis()) {
setAlarm(context, REQUEST_FULL_TIME, FULL_TIME_ALARM, playedEnd);
}
}
private void pause(Context context, MatchTimer timer) {
timer.pause();
cancelAlarm(context, REQUEST_FULL_TIME, FULL_TIME_ALARM);
long elapsedEnd = timer.getStartTime() + DURATION;
if (!isAlarmSet(context, REQUEST_ELAPSED, ELAPSED_ALARM) && elapsedEnd > System.currentTimeMillis()) {
setAlarm(context, REQUEST_ELAPSED, ELAPSED_ALARM, elapsedEnd);
}
}
private void stop(Context context, MatchTimer timer) {
timer.stop();
cancelAlarm(context, REQUEST_UPDATE, UPDATE_INTENT);
cancelAlarm(context, REQUEST_ELAPSED, ELAPSED_ALARM);
cancelAlarm(context, REQUEST_FULL_TIME, FULL_TIME_ALARM);
}
private void start(Context context, MatchTimer timer) {
timer.start();
long elapsedEnd = timer.getStartTime() + DURATION;
setRepeatingAlarm(context, REQUEST_UPDATE, UPDATE_INTENT);
if (timer.getTotalStoppages() > 0 && !timer.isPaused()) {
long playedEnd = timer.getStartTime() + timer.getTotalStoppages() + DURATION;
if (playedEnd > System.currentTimeMillis()) {
setAlarm(context, REQUEST_FULL_TIME, FULL_TIME_ALARM, playedEnd);
}
if (elapsedEnd > System.currentTimeMillis()) {
setAlarm(context, REQUEST_ELAPSED, ELAPSED_ALARM, elapsedEnd);
}
} else {
if (elapsedEnd > System.currentTimeMillis()) {
setAlarm(context, REQUEST_FULL_TIME, FULL_TIME_ALARM, elapsedEnd);
}
}
}
.
.
.
}
这些方法主要有两个功能:首先设置MatchTimer的状态,然后设置时间提醒的闹铃,改变参数就可以播放闹铃。这个功能还可以封装成一个工具方法,叫setUpdate()。这样外部也可以触发计时器的更新。
我们使用标准AlarmManager的方法来设置闹铃:
public class MatchTimerReceiver extends BroadcastReceiver {
.
.
.
public static final int MINUTE_MILLIS = 60000;
.
.
.
private void setRepeatingAlarm(Context context, int requestCode, Intent intent) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), MINUTE_MILLIS, pendingIntent);
}
private boolean isAlarmSet(Context context, int requestCode, Intent intent) {
return PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_NO_CREATE) != null;
}
private void setAlarm(Context context, int requestCode, Intent intent, long time) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.setExact(AlarmManager.RTC_WAKEUP, time, pendingIntent);
}
private void cancelAlarm(Context context, int requestCode, Intent intent) {
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_NO_CREATE);
if (pendingIntent != null) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(pendingIntent);
pendingIntent.cancel();
}
}
.
.
.
}
这里值得讨论的是setRepeatingAlarm()这个方法。因为在Wear在实现方式上有点不一样。我们会在Start事件中每秒钟触发一次闹铃更新Notification动作,所以这里需要记录具体已经过去了多少分钟。正常来说我们会每隔60秒触发一次这个动作,但是在Wear上不能这么做。原因是——当设备在唤醒着的时候可以这样做,但是如果设备进入睡眠状态就需要重新计算下一分钟的边界值。这就需要异步更新部件,然后设备只需要每分钟唤醒一次。一分钟结束后在计时器需要更新状态的时候触发操作。
对于我们的计时器应用来说,显示的分钟数会比实际时间少1分钟。但是显示分钟并不要求非常实时(但显示秒数时需要非常精确),所以我们可以这样操作:
完整的alarm Handler是这样使用振动服务的:
public class MatchTimerReceiver extends BroadcastReceiver {
.
.
.
private static final long[] ELAPSED_PATTERN = {0, 500, 250, 500, 250, 500};
private static final long[] FULL_TIME_PATTERN = {0, 1000, 500, 1000, 500, 1000};
private void elapsedAlarm(Context context) {
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(ELAPSED_PATTERN, -1);
}
private void fullTimeAlarm(Context context) {
Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(FULL_TIME_PATTERN, -1);
}
.
.
.
}
最后,我们通过这个方法来构造Notification然后呈现给用户:
public class MatchTimerReceiver extends BroadcastReceiver {
public static final int NOTIFICATION_ID = 1;
.
.
.
private void updateNotification(Context context, MatchTimer timer) {
NotificationBuilder builder = new NotificationBuilder(context, timer);
Notification notification = builder.buildNotification();
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.notify(NOTIFICATION_ID, notification);
}
}
Notification是Wear计时器的一个重要的部分,这里还需要一个自定义类来构造这些Notification通知。下一篇文章我们会讲如何在计时器app中使用Notification。
Match Timer可以在Google Play上下载:[url=https://play.google.com/store/apps/details?id=com.stylingandroid.matchtimer]Match Timer[/url]。
机械节能产品生产企业官网模板...
大气智能家居家具装修装饰类企业通用网站模板...
礼品公司网站模板
宽屏简约大气婚纱摄影影楼模板...
蓝白WAP手机综合医院类整站源码(独立后台)...苏ICP备2024110244号-2 苏公网安备32050702011978号 增值电信业务经营许可证编号:苏B2-20251499 | Copyright 2018 - 2025 源码网商城 (www.ymwmall.com) 版权所有