⬆️ ⬇️

Simple Time Manager for Android. Part 2

In this part we will finish the application “Time Manager” from the first part .



Immediately express my deep gratitude to Belkin and to all those who pushed the first part, you helped me with the invite)



I want to draw your attention to the fact that I refused to use the service, as originally intended. Judge for yourself, to start the process only to increase the timer every second - stupidity. The solution is simple: before each program stop, we save time, and after starting, we subtract it from the current time, getting the number of seconds.

')

Action plan:





I just want to apologize and praise) I apologize for making you wait a long time, and to praise that the other day Android brought me the first well, ooooochen good money and I bought myself xbox myself and lost it all weekend, so I was delayed)



So let's go!





First, either read the first part , and

so that it was clear we write everything under my story in it, or

if everything is clear, then download the source code of the first part, so that was something

work.

Sources (134 Kb)



Opened, launched, just in case the fireman collected and launched.



 [2010-03-12 09:12:15 - TimeManager] Android Launch!
 [2010-03-12 09:12:15 - TimeManager] adb is running normally.
 [2010-03-12 09:12:15 - TimeManager] Performing
 com.nixan.timemanager.Main activity launch
 [2010-03-12 09:12:15 - TimeManager] Automatic Target Mode: using device
 'HT91MKV01100'
 [2010-03-12 09:12:15 - TimeManager] Uploading TimeManager.apk onto
 device 'HT91MKV01100'
 [2010-03-12 09:12:15 - TimeManager] Installing TimeManager.apk ...
 [2010-03-12 09:12:19 - TimeManager] Re-installation failed due to
 different application signatures.
 [2010-03-12 09:12:19 - TimeManager] You must perform a full uninstall
 of the application.  WARNING: This will remove the application data!
 [2010-03-12 09:12:19 - TimeManager] Please execute 'adb uninstall
 com.nixan.timemanager 'in a shell.
 [2010-03-12 09:12:19 - TimeManager] Launch canceled!


In my case, Eclipse cursed what the applet could not install,

due to the fact that it is already installed.

A little explanation: collecting the applet in Eclipse followed by

installation, the application is signed by debug keys, and when installing from

Market - normal. So the Eclipse does not have the ability to remove

application installed through the market, so you have to delete it

in manual. It's all very simple - go to the market and delete), or

We act as Eclipse told us - we do adb in the terminal

uninstall <package name>.

Another digression: in general, adb is a very useful thing. TO

example, you can even steer through the database through it, through it

screenshots are taken, and even the screencast, which I am in the first part on

youtube laid out.



So, removed, reassembled and installed. Everything should work if

No, look for errors, or download my sources.



Bug fixes and improvements



Having used the day, I realized that turning the screen, though not

turns off the counter, but resets it to the moment it starts up and that

the program lacks a reset of counters, plus Riot reported about the same bug. Well go, rule.



Bugfix

For reference, open the activity lifecycle. When turning the screen, the applet

kills the activity and then restarts it. Last time we are in onCreate ()

added podgruzka values ​​whether the timer is running and how many seconds on them

got stuck. Things are easy - you only need to save when killing an activity

timer values.

 @Override
    public void onStop ()
    {
   	 super.onStop ();
		 stats_editor.putInt ("key_rest_time", rest_time);
		 stats_editor.putInt ("key_work_time", work_time);
		 stats_editor.commit ();
   	
    }


super.onStop (); - the line calls the parent's onStop () method. If a

it says nothing to you, then it would be good for you to read something about

OOP in java.

stats_editor.putInt (); - last time I already talked about this,

but again, these methods are put in the application settings file, which I

I use to store data, our variables with the key.

stats_editor.commit (); - the method saves the variables that we

previously written with putInt, putBoolean, putString ... etc.

It is important to remember that in a good way at the same time open to

A configuration file can only be written once, otherwise having two

variables with settings, after commit () we risk getting later on

the output is what the last variable recorded, which

The commit () method was called;



Chew a little:

Suppose in the settings are already two integer'a integer_one and

integer_two, both are equal to 1.

 SharedPreferences.Editor stats_editor1 =
 PreferenceManager.getDefaultSharedPreferences (this) .edit ();
 SharedPreferences.Editor stats_editor2 =
 PreferenceManager.getDefaultSharedPreferences (this) .edit ();
 stats_editor1.putInt ("integer_one", 2);
 stats_editor2.putInt ("integer_two", 2);
 stats_editor1.commit ();
 stats_editor2.commit ();


We risk getting output integer_one == 1 and integer_two == 2.



We collect, run, everything worked for me.



Add the ability to reset the timers.

You need to create a menu called up by pressing the <> button on the device, and

there should be a button that will reset the timers.

For starters, the button must be called. Remember how we last time

edit the res / values ​​/ strings.xml file, and add there and, if

did localization, in the file with localization.

 <string name = "button_reset"> Reset timers </ string>


My line will be called button_reset.

Retreat: now you have witnessed evidence

that localization is most conveniently done at the end. Simply

think for what you have not 5 lines, and more than 200.



Then in the main, and, while the only class, you need to add two methods:

 public boolean onCreateOptionsMenu (Menu menu)


and

 public boolean onOptionsItemSelected (MenuItem item)


The first generates a menu, the second handles clicks on its elements.



Then we write the code to generate the menu:

 MenuItem menuItem;
 menuItem = menu.add (Menu.NONE, Menu.NONE, Menu.NONE, R.string.button_reset);
 menuItem.setIcon (android.R.drawable.ic_menu_delete);
 return super.onCreateOptionsMenu (menu);


menuItem - the variable contains the button itself in the menu. First

line we only denote it.

menu.add - first, it is taken from the method call - look,

it is there as an input parameter, secondly this method

returns the type MenuItem, which we shove into the menuItem variable.

The following values ​​are passed in the parameters:

1. A group of buttons. I have Menu.NONE, I don’t use it.

2. Item ID. In general, it is necessary to put it everywhere

different, the easiest way is to put this section of the code in a loop and transmit

there is a loop counter. It is needed in order to understand the click handler,

which button we clicked.

3. Order.

4. Button name. Refers to button_reset in res / values ​​/ strings.xml.

setIcon - sets an icon for the menu item.

Please note that I did not upload ic_menu_delete.png to the folder

drawable, and the fact that we're not using as usual - R.drawable, but

android.R.drawable. This means that the system will search for this picture.

in pictures of the system itself, and not specifically our application.

As always, a retreat. Why use pictures from the most

systems? Firstly, there is no need to draw or vyderat what is already

long drawn Secondly it is the most ideal option to make

system by guideline. Third, for example, take a look at my

Rozhozhurnal - client for LJ (available in the market, again search by

phrase pub: "Nixan"). In 1.5 and 1.6, the animation in the pop-up window was one,

and in 2.0 it was updated. As a result, on different systems we have different

Pictures.



Let's collect this thing, and see what happened.



Here is my option.



It remains for complete joy to just write a handler for clicking on the menu.



 if (resting)
 {
	 rest_timer.startAnimation (shrink_rest);
	 resting = false;
 }
 if (working)
 {
	 work_timer.startAnimation (shrink_work);
	 working = false;
 }
 rest_timer.setText ("0:00:00");
 work_timer.setText ("0:00:00");
 rest_time = 0;
 work_time = 0;
 stats_editor.putInt ("key_rest_time", rest_time);
 stats_editor.putInt ("key_work_time", work_time);
 stats_editor.commit ();
 Return super.onOptionsItemSelected (item);


1. We stop the counters.

2. Zero the text on the timers.

3. We reset the counters themselves.

4. Zero settings where they are contained.



Small optimization

I repent, but it just flew out of my head that the string with the counter can be set via String.format (); which I did.

To do this, we go to TimerTask, delete all Strings from the list of variables, delete portions of the code where, before seconds and minutes, zeroes are added, and

 rest_timer.setText (hours_ind_r + ":" + minutes_ind_r + ":" + seconds_ind_r);


change to

 rest_timer.setText (String.format ("% d:% 02d:% 02d", hours_r, minutes_r, seconds_r));




We collect, we try. Everything is working!



We remove the need to keep the application running.



As I said earlier, it’s not advisable to fence the service in this case. I do not argue, I really wanted to write something about it, but the fact that I did not do it, to some extent, howto). In addition, on Habré there is a wonderful article "Good night" 3fonov 'a, in which it is perfectly told about services, namely about the fact that there are two types of them:

1. Created, it worked, stopped.

2. Connected, executed the code under the command of the connecting activity, stopped.

3fonov described in detail the second part, there are very subtle points with the interface and the aidl file, on which I once spent a day of working time. In the first type, everything is quite simple. Take as a basis, for example, our class, namely its methods onCreate () and onDestroy. All work in this service takes place in them.

A little ad-libbing. And where should I use what type of service?

The second type of service is very convenient for applications that imply quite active user interaction with the server, for example, the same Google Talk, the service is constantly hanging in memory. By calling the onBind command, the Activity connects to this service and asks, say, who is online, to which the service responds with the data loaded at the time of the last update. In the same team, for sure there are methods that allow you to send messages, set statuses. The advantage is that we have one socket, one connection, which is implemented just in this service, for background work and for a user.

The first type, in my opinion, is much more convenient for applications where the user has much less impact on the server, I mean, let's say, the content search function. At the moment I am writing an application, whose service has just TimerTask for content updates, when updating, it stores all the information in a local database, and sends a broadcast message (popularly Broadcast) about updating the interface. At the same time, if the Activity is started, then it will pick up this message and re-read the database, displaying all new elements, otherwise Broadcast will simply not do anything. The plus of this type of service is, according to my observations, the time for its development and implementation, well ... I think there are 3 times less than the services in the ability to connect to them Activity.



So, after the retreat, let's continue our re-coding)

In order for the application to continue to count the time, even when it is not active, I came up with such an algorithm.

When an application is closed, it saves the current time in settings, two work and rest timers and two boolean values ​​whether these timers are running. Then, when we start, we read these parameters, and we subtract the current time value from the settings from the actual current time (It was more correct to call this parameter the date of the last run, or something like that).



All this is done in a few lines of code.

Add this line to the onStop () method (remember, the method called when an activity is killed and once again refer to Activity lifecycle ) before the method that saves the settings.

 stats_editor.putLong ("key_last_used_date", System.currentTimeMillis ());


I will refresh in memory. stats_editor is a variable of the SharedPreferences.Editor class that saves the settings of the application, but we use it as data storage. There is also the SharedPreferences class, but it is only for reading these settings. The putLong method is reported with two String variables, a settings key and a long value. There are also methods putString, putInt and so on, they, respectively, the second parameter will be String, int ... They are read by the getString method, getInt, etc., also with two parameters: key and default value, for example, if the settings are If the system does not find it, it will return the default value.

System.currentTimeMillis () - returns the long value of the current time in milliseconds.



To all the variables that we created in the first part, we add the int last_time variable; to get the difference between the current time and the time of the last launch (or rather stop) of the application.



Then go to the onCreate () method and add it there:

 last_time = (int) ((System.currentTimeMillis () - saved_stats.getLong ("key_last_used_date", System.currentTimeMillis ())) / 1000);


Attention! IMHO is worth mentioning, although I hope everyone understands that you need to add this line after we initialize the saved_stats variable, otherwise we will catch a NullPointerException.

Chew: The current time is the last run time (more precisely, the stop) and all this is divided into 1000, to get a millisecond second, the whole structure has a return type int.

Division? This is an integer variable! - you say, in Java, when you divide an integer into an integer, the whole part of the division will return.



It remains to add these seconds to the active counter. We are looking for the same onCreate () construction, which, depending on the Boolean variables, resting and working increases the size of the text of the counters and appends this increase there.

 if (resting)
 {
       	 rest_timer.startAnimation (magnify_rest);
       	 rest_time + = last_time;
 }
 if (working)
 {
       	 work_timer.startAnimation (magnify_work);
       	 work_time + = last_time;
 }




We start, we try, everything works for me, so it should be with you.



Notifications



The algorithm is as follows: if the counter is on, then we display a notification when the applet is stopped, and send the command to hide notifications in any case when the program starts.



We declare and initialize the NotificationManager class variable, which just controls the notifications.

 notificationManager = (NotificationManager) getSystemService (NOTIFICATION_SERVICE);




With this variable, we will need two methods cancel () and notify (). The first, as you may have guessed, removes the notification. One parameter is passed to it - id, in case the application has several notifications, in order to remove some concrete one, we use this identifier. Notify () has two parameters — id and a variable of the Notification class.

Since my application has only one notification, feel free to put 0 or any other number instead of id. It is important that they have been created and canceled the same.



After initializing the variable notificationManager, you can immediately use the cancel () method to kill the notification when the application starts.

 notificationManager.cancel (0);




Now go to onStop () and add there:

 Notification notification = null;
 PendingIntent intent = PendingIntent.getActivity (this, 0, new Intent (this, Main.class), PendingIntent.FLAG_UPDATE_CURRENT);
 if (working)
 {
	 notification = new Notification (R.drawable.notification, getResources (). getString (R.string.notification_work), System.currentTimeMillis ());
	 notification.setLatestEventInfo (this, getResources (). getString (R.string.notification_work), getResources (). getString (R.string.show_app), intent);
 }
 if (resting)
 {
	 notification = new Notification (R.drawable.notification, getResources (). getString (R.string.notification_rest), System.currentTimeMillis ());
	 notification.setLatestEventInfo (this, getResources (). getString (R.string.notification_rest), getResources (). getString (R.string.show_app), intent);
 }
 if (notification! = null)
 {
	 notification.flags = Notification.FLAG_ONGOING_EVENT |  Notification.FLAG_AUTO_CANCEL;
	 notificationManager.notify (0, notification);
 }


1. Create a Notification class variable.

2. We describe it - which lines and image to use, as well as what should be launched when clicking on this notification.

3. Make notify ();

intent - variable of the PendingIntent class - just here it is indicated which class should be launched when pressed. Also pay attention to the last parameter, the flags for the running intent are sent there, I have a flag set for the new intent to update the old one.

The constructor of the notification is the picture, the text that will be shown when creating the notification, when it is the full width of the notification bar, the notification time.

setLatestEventInfo is two lines: the first is what is displayed in large print, the second is the signature in small print and Intent to run.

notification.flags - as you know, android has two types of notifications. A vivid example of this is the calendar and the mounting of a flash drive. One is forthcoming, the other is current. Notification.FLAG_ONGOING_EVENT - sets the current status to the notification.

By the way, you can use another flag - Notification.FLAG_AUTO_CANCEL, and remove the line that removes the notification from the onCreate () method. In general, read the explanations, there are a lot of interesting things)

notify - actually apply this notification.



Here is how it all looks.





We collect applet, we try to start. Everything should turn out wonderfully, but for every fireman I share source codes.

Archive zip (158 Kb)



The image that I used in the notifications is in the updated archive with all the pictures.

Archive zip (295 Kb)



Fill the market



Suppose you went through the developer registration procedure, paid a $ 25 fee, and now it’s time to upload the project. Well, let's go!



First of all, the application must set the correct version.

Open AndroidManifest.xml and find

 android: versionCode = "1"
 android: versionName = "1.0"


versionCode - needed for the market. It compares the current installed code with the code in the market and if it offers less update.

versionName is a “readable” version number.



Since after writing the first part of the application was available in the market, it's time to update it. I increment versionCode by 1, and in versionName I put 1.1, although this is all up to you, even though you have been collecting all your life, 0.cht and a little little more than one and onenew.cht and a bit more.



Save the project and right-click on it. Android tools - Export Signed Application Package. Enter the name of the project for export, usually everything is already stamped and just click next. Then you have to choose an existing one or create a new key.

Enter the path to the key and two times the password.



Fill something like this. Regarding the period of validity, Google recommends setting it up almost until 2030, so we unscrew it to the maximum)



Click next and sign, getting the apk file with the application.

Apk application (46 Kb)



Just in case, I recommend to execute the adb install TimeManager_2.apk command, which will install the already signed version on the phone and check the functionality again.



Go to the admin area of ​​the market and fill in our application. In principle, everything is also clear there, you just have to explain how to take screenshots.



Need an installed SDK.

In the terminal we do ddms, select our receiver or emulator on the left, Device - Screen Capture.



And do not forget to write a description of the program and its name in Russian)



Especially for MiXeR about folding and so on, I’ll probably send you back to android.com, namely to the article from the blog of the development team , there are a lot of interesting things.



And about runOnUiThread ().

Creating an activity without threads, we mean that all code will be executed in one thread, so-called. UI Thread, because it draws the entire interface. Why does the interface slow down when performing long calculations? That's right, because everything works in one thread, and until the calculations end, the interface will not continue to be drawn. For this purpose, they use threads, although this is far from their main purpose. But not in the UI Thread interface can not draw. For these tasks, there are Handlers that are called from the thread. In them there is a drawing. In general, they provide a lot of utilities, as for example, you can make one thread at the end call another, well, this is an example. But they are very cumbersome, and it is much easier to make the runOnUiThread () method, which has the Runnable class in its parameters, where the run () method describes everything that needs to be done exactly in the interface stream. I apologize in advance, yes I am a shitty speaker, but something like this in general)

Source: https://habr.com/ru/post/87657/



All Articles