Remember that in December 2013, there was a series of articles about how to develop a smart watch app that allows users to record stopwatches time in football matches. With the advent of Android Wear, it's a good idea to develop one of these apps in wearable devices, but it seems difficult to understand the architecture of the Android Wear now. So this series of articles we'll rewrite this application and lead us all into the Android Wear world.
This article will not dwell on the purpose of the app we are developing, as we have already learned about it in the previous series. Let's just say this is a timer application that starts at the start of the game and pauses during the game (stopwatches), and then 45 minutes later there will be a vibration reminder, and then the game will have a reminder after 45 minutes.
Before we begin, it is important to see why we rewrite this app instead of directly coding it. Smart watch is a modified version of the Android1.6 system, so its architecture is very much like a mobile phone running Android1.6, so our app is based on an activity, and all of our work is running on the activities. Before we begin to learn about smart watch development, we have to be very clear that our previous design didn't work on Android Wear, although it also supports activity, but it works differently on Android Wear. On a cell phone or tablet, if an activity returns from sleep to arousal, the activity is revived, but not on the wear. After a while the wear device will sleep, but after the device wakes up, the activity in the sleep state will not be awakened again.
First of all, I was very surprised by the question, I always wondered if the activity had this limitation, but also to develop a practical app? It turned out to be completely paranoid, and I've come to realize that it's easy to develop a practical app--we just need to change our software design patterns to fit the Android Wear architecture, not as a mobile phone.
The basic problem we need to consider here is that the timing application needs to record the time based on a service that has been running. But a service based on long runs is not a good solution because it consumes power. Here we mention the record time this keyword, that is, we do not need to really implement a long running service, as long as the user needs to see when we can update the message displayed on the line. For most of the time, users only need to know how long it has been, and only when the game is suspended or the end of the midfield is more detailed information is needed. So for the most part of the time, we just need to show that it's accurate to minutes, and then it's accurate to seconds when the user needs it.
The basic way to implement this method is to use Alarmmanager to trigger an update notification event every minute to update the minute display. This notification event also includes an activity that displays precision to seconds, but the entire notification is displayed only when the user slides the screen. In this way we can update the message when it has to be displayed, so for most devices, updating the message every minute is more power-saving than running a service.
The figure below shows that this is well documented, and first we need to turn on the notification so that we can get an accurate display of seconds.
However, when the information is displayed or the device is dormant, we only need to display the accuracy to the minute.
One thing to explain is that the app's name has changed. Previously in the I ' M watch version called "Footy Timer", now changed to "Match timer." Because when you use voice to start the app, Google's voice recognition is not sensitive to the word "footy", we use "OK Google,start footy Timer" This command can not start the application, and use "OK Google,start Match Timer "can be used.
Finally, I'm sorry this article has no code, but this article series will be slightly changed. In the past, I will enclose the article related code snippet at the end of each article, please be assured that the following article will still be like this, because this is a full-featured app, not a series of technical articles, so the next article will contain some code examples and comments, in the end of this series of articles will be attached to the entire project source.
Match Timer can be found on Google Play: Https://play.google.com/store/apps/details?id=com.stylingandroid.matchtimer
-->
We've explained why we need to rewrite this timer app in Android Wear (because we've already developed it in "I ' m Watch"), so let's look at the code here.
We start with a core class of this app that controls the state of the timer. This class contains 4 variables of type long : The first represents the time at which the timer starts, the second is the time when the timer stops (it is 0 in operation), and the third represents the time of the timer stopwatches (if there is no stopwatches at the moment, it is also 0), and the fourth represents the total stopwatches length. With these four variables we can maintain the state of the timer, and we can calculate the other information we need to show. The basic function of this class is to manipulate these variables, that is, to maintain these states of the timer.
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;
}
..
.
}
These are basic Java code, and it's no time to talk about it. The following function is more advanced and can manipulate the state of the timer.
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;
}
}
These are still basic Java code, you can not talk about it. Only the Save () method We haven't seen, it was written at the end of the class, and this function is worth it. Let's talk about it.
In the previous article we discussed the wake-up mechanism, we do not need to maintain a long connection or background services, only to maintain the state of these timers. We use Sharedpreference to achieve:
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 {sharedpreferences preferences = Context.getsharedpreferences (P
REFERENCES, 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); ReturnNew 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;
}
}
.
.
.
}
All we need is the newinstance () method to construct a Matchtimer instance from the Sharedpreference, and we need the Save () method to save the current timer state to the sharedpreference.
The last thing we want to say is that if a part holds a reference to a Matchtimer object, but other objects have changed the state of the timer, an exception can occur (see the next article). So we also need to provide some ways to register and unregister Matchtimer instances, and to receive changes in the state of the timer when the value of the sharedpreference changes.
Now that we've defined a basic timer, we'll explain how to keep the timer state and wake up these states when needed.
Match timer can be downloaded from Google Play: Match timer.
In the previous articles in this series, we introduced the Android Wear Timer app, and analyzed the design ideas and the structure of the app. This article will explain how to periodically wake up the program to remind users.
We've explained why it's not going to work in the way of a backend service--it's a very power-hungry way. Therefore, we have to have a timed wake-up mechanism. We can use Alarmmanager to implement this mechanism, execute a intent periodically, and then notify Broadcastreceiver. The reason to choose Broadcastreceiver instead of Intentservice is because the task we are running is lightweight and the lifecycle is very short. Using broadcastreceiver avoids the entire lifecycle of a service every time you perform a task. So it's a good fit for our lightweight task-we're doing it in milliseconds.
The core of Broadcastreceiver is the onreceiver approach, where we need to schedule a variety of event responses.
public class Matchtimerreceiver extends Broadcastreceiver {public static final int minute_millis = 60000;
Private static final long DURATION = 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.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 ()) {SE
Talarm (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_t
Ime_alarm, Elapsedend);
}
}
}
.
.
.
}
The code is very intuitive and easy to understand. First instantiate a Matchtimer object (read data from sharedpreference) and pass it to the corresponding event handling handler respectively. Then wait for the action to occur, and finally update the notification.
8 event actions are handled, of which 5 are responsible for controlling the state of the timer (START, STOP, PAUSE, RESUME, RESET); one is responsible for updating the notification, and the remaining two are responsible for the wake up to 45 minutes after the shock prompts.
Let's start with some of these control states:
public class Matchtimerreceiver extends Broadcastreceiver {public static final int minute_millis = 60000;
Private static final long DURATION = 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.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 ()) {SE
Talarm (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_t
Ime_alarm, Elapsedend);
}
}
}
.
.
.
}
These methods have two main functions: first set the state of the Matchtimer, and then set the alarm clock, change the parameters can play the alarm. This feature can also be encapsulated into a tool method called Setupdate (). This can also trigger updates to the timer.
We use the standard Alarmmanager method to set up the alarm:
public class Matchtimerreceiver extends Broadcastreceiver {.
.
.
public static final int minute_millis = 60000;
.
.
. private void Setrepeatingalarm (context context, int requestcode, Intent Intent) {Alarmmanager Alarmmanager = (alarmma
Nager) 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 = Pendin
Gintent.getbroadcast (context, Requestcode, intent, pendingintent.flag_no_create); if (pendingintent!= null) {Alarmmanager Alarmmanager = (alarmmanager) context.getsystemservice (Context.ALARM_SERVI
CE);
Alarmmanager.cancel (pendingintent);
Pendingintent.cancel ();
}
}
.
.
.
}
What is worth discussing here is the method of Setrepeatingalarm (). Because the wear is a little different in the way it is implemented. We will trigger an alarm update notification action every second in the start event, so it's time to record how many minutes have passed. Normally we would trigger this action every 60 seconds, but we can't do that on the wear. The reason for this is that the device can do this when it wakes up, but if the device goes to sleep it needs to recalculate the next minute's boundary value. This requires that the part be updated asynchronously, and then the device needs to be awakened only once per minute. Triggers the operation at the end of a minute when the timer needs to be updated.
For our timer applications, the number of minutes displayed is 1 minutes less than the actual time. But the display of minutes does not require very real time (but it is very precise to display the number of seconds), so we can do this:
The complete alarm handler is used in such a vibrating service:
public class Matchtimerreceiver extends Broadcastreceiver {
...
private static final long[] Elapsed_pattern = {0, $, g,,,, and};
private static final long[] Full_time_pattern = {0, 1000, 1000, 1000};
private void Elapsedalarm {
Vibrator vibrator = (Vibrator) Context.getsystemservice ( Context.vibrator_service);
Vibrator.vibrate (Elapsed_pattern,-1);
}
private void Fulltimealarm {
Vibrator vibrator = (Vibrator) Context.getsystemservice ( Context.vibrator_service);
Vibrator.vibrate (Full_time_pattern,-1);
}
Finally, we construct notification by this method and then present it to the user:
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 is an important part of the wear timer and requires a custom class to construct these notification notifications. In the next article, we'll talk about how to use notification in timer apps.
Match timer can be downloaded from Google Play: Match timer.