How to display timer in activity? - android

I need to display a time duration on a few of my Activities within the application. The timer starts when one of the Activity starts.
Should I use service for the timer ?
Is this the best way ?
Or should I start thread from one of the Activity ?

I think in the use case you're describing it would be best to store time stamps (see Data Storage) and calculate the deltas for GUI use. If you need to display a real-time clock in one of your activities you can create a separate thread in that activity just to update the clock.

Well, depending on how much interface work you need to display your progress, I would start a thread within the activity and then create a timer that checks the status of the thread progress and updates the interface as needed. Services are good for background tasks that don't require a lot of interface notification/updates.
Here's an example from a project I'm currently working on (UpdateListRunnable just calls "notifyDataSetChanged()" on my list adapter. I do it multiple times in the code so I encapsulated it in a class. Also, updateHandler is just a regular Handler instance):
#Override
public void run() {
Timer updateProgressTimer = null;
UpdateItem currentItem = null;
for(int i = 0; i < items.size(); i++) {
currentItemIndex = i;
currentItem = items.get(i);
if (currentItem.isSelected() == true) {
updateProgressTimer = new Timer();
updateProgressTimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
updateHandler.post(new UpdateListRunnable());
}
}, 0, 2000); // check every 2 seconds
lookupDb.downloadUpdate(currentItem);
currentItem.setUpToDate(true);
currentItem.setStatusCode(UpdateItem.UP_TO_DATE);
currentItem.setProgress(0);
updateProgressTimer.cancel();
updateHandler.post(new UpdateListRunnable());
} // end if its the database we are hosting on our internal server
} // end for loop through update items
currentItemIndex = -1;
} // end updateThread run

Related

Internal AsyncTask class to poll URL periodically

Problem:
What I am trying to do is have an AsyncTask poll a URL in the background periodically (every 5 seconds or so), and if specific data is received, disable an element on the activity. I have tried creating a separate class and using onPostExecute to run the method from the activity class to disable an ImageView, but I run into issues. I cannot call that method from the other class unless I make it static, and if I do declare it static, I cannot access the ImageView using findViewById.
Current status:
I have a TimerTask running my RetrieveColorTask that extends AsyncTask periodically every 5 seconds. But this causes exceptions as it tries to execute the same thread multiple times.
Timer timer = new Timer();
final RetrieveColorTask task = new RetrieveColorTask();
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, true);
AsyncTask.Status status = task.getStatus();
TimerTask doTask = new TimerTask() {
#Override
public void run() {
if(status.compareTo(AsyncTask.Status.FINISHED) == 0)
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, true);
}
};
timer.schedule(doTask, 5000, 5000);
Another method I tried was to create a new RetrieveColorTask in the TimerTask but then I don't know if the old thread has finished running yet, and ends up creating multiple AsyncTasks, most of them in the WAIT state. The most progress I have had is with this code:
Timer timer = new Timer();
final RetrieveColorTask[] task = new RetrieveColorTask[1];
final AsyncTask.Status[] status = new AsyncTask.Status[1];
final boolean[] isRun = {false};
TimerTask doTask = new TimerTask() {
#Override
public void run() {
if(task[0] == null) {
task[0] = new RetrieveColorTask();
status[0] = task[0].getStatus();
task[0].executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, true);
} else {
if(status[0].compareTo(AsyncTask.Status.FINISHED) == 0) {
task[0] = new RetrieveColorTask();
task[0].executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, true);
}
}
}
};
timer.schedule(doTask, 5000, 5000);
But once the task executes the first time, it stays in PENDING state after that, so it is only run once.
I am fairly new to Android programming so I would appreciate any advice
Put the line
status[0] = task[0].getStatus();
at the beginning of the run()-method so that it updates the local status variable every time the method is executed by the TimerTask, because right now you are storing the status of the task when it is first created, but you don't update it when the method is ran again after 5 seconds and task[0] is not null anymore.
Also, I would suggest that you to create a regular variable to put the task and status in just to make it cleaner, instead of using arrays because you are initializing them with only one element anyways, so in other words declare the task and the status variables like this instead:
RetrieveColorTask task = null;
RetrieveColorTask.Status status = null;
And also, personally I would do the status check like this
if(status == RetrieveColorTask.Status.FINISHED) ...
instead of using the compareTo() methods, because you never make use of the 1 or -1 values that it could also possibly return.
Hope this helps!

How to use android handler in a loop

I am building my first android application and I am trying to make a memory game. Anyhow, I need to make an array of buttons change color for 1 second and then return to its original color in order, for example: button1 changes to yellow, stays like that for 1 second then returns to gray, then button2 changes to yellow for 1 second then returns, and so on. I tried using the handler but it always works only after the last iteration, this is my code:
for (i = 0; i < 9; i++) {
buttonList.get(i).setBackgroundColor(Color.YELLOW);
runnable =new Runnable(){
#Override
public void run() {
buttonList.get(i).setBackgroundColor(Color.GRAY);
}
};
handler.postDelayed(runnable,1000);}
what am I doing wrong?
EDIT
Found How to do it. First I need to make a runnable class that takes paramaters ex MyRunnable implements Runnable (using Runnable interface), then writing a method that uses this paramater, I can't do it with the regular one because it depends on i and i changes with the iteration.
You need to create a new Runnable inside each loop because all 9 delayed posts are running the same runnable that you create on the 9th and final loop since the loop no doubt takes less than a second to complete. So try something like this:
for (i = 0; i < 9; i++) {
buttonList.get(i).setBackgroundColor(Color.YELLOW);
Runnable runnable = new Runnable(){
#Override
public void run() {
buttonList.get(i).setBackgroundColor(Color.GRAY);
}};
handler.postDelayed(runnable,1000);
}
You're synchronously (at the same time) setting all buttons' colors to yellow, and also creating 9 asynchronous tasks (one for each button) to change color to gray after one second. It means all buttons will change colors back to gray after around 1 second, (more or less) at the same time.
Think of the handler as a queue that you add tasks to. The call postDelayed() is scheduling your tasks to be executed in the future, but all of them are scheduled at the same time, so all of them will be executed at the same time in the future.
I haven't run it, but I think this approach is more of what you are looking for:
// Those are fields
private int buttonIndex = 0;
private boolean yellow = false;
private final Handler handler = new Handler(new Handler.Callback() {
#Override
public void handleMessage(Message msg) {
if (!yellow) {
buttonList.get(buttonIndex).setBackgroundColor(Color.YELLOW);
handler.sendEmptyMessageDelayed(0, 1000);
} else {
buttonList.get(buttonIndex).setBackgroundColor(Color.GRAY);
if (++buttonIndex < 9) handler.sendEmptyMessage(0);
}
yellow = !yellow;
}});
// Call this to start the sequence.
handler.sendEmptyMessage(0);
Note that I'm using sendEmptyMessage*() instead of post*(), but either approach could be used. Additionally, handler's messages (tasks) can have input parameters, so it'd be nice to use them.

Activity is shown after a strange delay

I'm suffering from a delay when starting an activity in my android application project.
Whenever a menu item or a clickable view is clicked, the onClickListener just creates a new Intent and starts specified activity. That's OK so far. But then, view is frozen for a noticeable time(around 1 sec) until user see the new activity.
That time may be caused by progress inside onCreate, but I measured time by System.currentTimeMillis() and printed it in logcat at the end. So it seems it takes only 20-30 ms and the log is printed long before user sees the activity.
Here is my code:
public class ExampleActivity extends MyActivity {
MyModel myModel;
long startTime;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startTime = System.currentTimeMillis();
setContentView(R.layout.activity_ders_programi);
setToolbar();
myModel = Controller.getMyModel();
if(myModel == null) { //If we don't have the object previously
//this fills the object, takes some time
myModel = new MyModel(this);
//myModel is observable and this activity is observer.
//Whenever it's done, it notifies this activity
}
else //If we got the object previously
createCardViews();
Controller.setMyModel(myModel);
setParentActivity();
}
//If Observable object notifies this activity, update() is called.
#Override
public void update(Observable observable, Object o) {
createCardViews();
}
//Creating a list of cardViews, this also takes some time
private void createCardViews() {
ArrayList<Card> cards = new ArrayList<Card>();
for(int i = 0; i < 5; i++) {
MyModelCardModel card = new MyModelCardModel(this, i);
card.init();
cards.add(card);
}
CardArrayAdapter mCardArrayAdapter = new CardArrayAdapter(this,cards);
CardListView listView = (CardListView) findViewById(R.id.my_card_list_view);
if (listView!=null)
listView.setAdapter(mCardArrayAdapter);
//I think here is the last point.
//After this point there will be no significant process.
long stopTime = System.currentTimeMillis();
Log.d("modelflow", "card create takes: " + (stopTime - startTime) + "ms");
}
So what am I doing wrong? If the delay is because the progress is heavy, then why the measured time seems little. Let's say progress causes delay, why don't application wait after showing the activity? And how to avoid this?
There is a project to test native apps performance in comparison to polymer+xwalk. You can find sources here https://github.com/collabora/xw-perf
this project proves that native apps perform much better but the delay for start activity and GC is noticeable. Also it is interesting to see how GC causes fps drops.
Your issue is either with Controller.SetMyModel() or setParentActivity().
Why do I narrow it down to those?
You time everything but those two calls.
Everything timed seems to run quickly.
There is no source code for either of those un-timed calls.
Those calls don't appear to be to Android methods with known behavior.
Given your description of Controller.SetMyModel() as being
Actually nothing more than a basic setter
the most likely candidate is setParentActivity(). You should post its source code.

slowing down a user's ability to button mash in Android

I have an activity that runs some ASCII control over a network port to a remote device.
Every single button push on the interface will trigger an AsyncTask to handle the communication, and (finally) works great.
However, if a user starts button mashing like a chimp on crack, the system will crash with way too many calls on the same socket, so I've come up with a little timer function to slow down the reaction to their excitement.
I'm wondering if somebody has come up with a better way to do this?
First off, inside the onCreate:
btn_pwrtoggle = (Button)findViewById(R.id.pwr_btn);
btn_pwrtoggle.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(!buttonMasher){
if(powerstat.equals("OFF")){
String[] commandToSend = {"POWER","ON"}
}else{
String[] commandToSend = {"POWER","OFF"};
}
deviceControl(commandToSend);
}
startButtonMashTimer();
}else{
Log.w("button masher","slow down there, monkey.");
}
}
});
Then, in the actual Activity:
Timer buttonTimer;
TimerTask buttonMonitorThread;
int chimpCrackCounter;
protected void startButtonMashTimer() {
chimpCrackCounter = 0;
buttonTimer = new Timer();
buttonMonitorThread = new TimerTask(){
#Override
public void run(){
buttonMasher = true;
if(chimpCrackCounter == 1){
buttonMasher = false;
buttonTimer.cancel();
}
chimpCrackCounter++;
}
};
buttonTimer.schedule(buttonMonitorThread, 0, 500);
}
It seems to be working just fine, (and may help somebody having the same difficulty) but I'm open to suggestions.
An easy way to prevent a user from pushing a button too often is to save the time when a button was pushed, and then next time compare the last time with the current time and if the difference is too small, ignore the action.
final static long minTimeBetweenClicks = 1000;
long lastTime;
onClick(View v){
if( System.currentTimeMillis() < lastTime + minTimeBetweenClicks ) return;
//Handle the click
lastTime = System.currentTimeMillis();
}
The beauty of this is that it doesn't require any new threads or timers, and your AsyncTasks won't have to know about the buttons.
Disable the Button after a click (setEnabled(false), perhaps in onPreExecute, and enable after the task is done, in onPostExecute.
Also, be sure to pay attention to lifecycle changes. Your AsyncTask may be killed if the Activity is paused, so be sure to check the state in onResume.

Android Chronometer, retain time state (and keep counting in background)

I have a timer that counts up from the time a user encounters that activity
I am currently using a Chronometer set during onCreate (initially started only when certain conditions are met). But I need the chronometer to keep counting upward until the app and all its views are closed (I have an "Exit" function to do that).
The problem is that the Chronometer gets reset to zero on every time I look at another tab and come back to its activity. (This has to do with the oncreate, but I dont know the way around it)
I didn't find an intuitive way to save the chronometer's state or countup in the background on its own (or to perhaps keep track of the time on my own and update the chronometer visually at a different point in time)
One idea I had was to start the Chronometer with a service and let the service keep counting , while having a textview in the existing activity update using the chronometer's current time tally as a string
any insight on a known approach to this problem be appreciated!
This is further complicated because this is an activity in a tabhost, and tabhosts call both onPause and onResume every time you load a view, so this breaks lifecycle functions.
There are a number of ways to persist the time. The easiest one I have found is to store the time in the Intent that was used to create the original activity via getIntent().putExtra("START_TIME", floatvalue). You may retrieve the value with getIntent().getFloatExtra("START_TIME", 0f). Doing it this way has a number of benefits:
It doesn't break the Activity LifeCycle and does not require a Context.
It can be passed easily between other Activities and Applicaitons.
It persists among Pauses and Stops.
It doesn't require special listeners.
It doesn't create any new objects (the Intent is the one used to create the Activity the first time).
This solution is great for persisting in a Tabbed Activity, or across Dialogs, etc. It has some limitations if leaving the Application to a more memory intensive one, but only if your Activity is destroyed (due to memory).
Because of my Tabhost, the lifecycle functions could not be relied on.
What I did was make the chronometer a static global in a central class, and added a ontabchangedlistener within my tabhost that checked to see if the tab being changed to was the tab with the chronometer. If this was true then it stores the Long value of the chronometer's current time.
tabHost.setOnTabChangedListener(new OnTabChangeListener(){
#Override
public void onTabChanged(String arg0) {
// TODO Auto-generated method stub
if(arg0.contentEquals("homeGroup"))
{
//store time in centralhelper.java
//stopWatch is of type Chronometer
//stopWatchLastTime is of type Long and is initially set to zero. Chronometer uses milliseconds to determine time, will never be zero after set
CentralHelper.stopWatchLastTime = CentralHelper.stopWatch.getBase();
}
}
});
When my homeGroup view loads, the onResume() function is called, there is a condition here to retrieve the time for the chronometer to resume counting from. Despite the fact that a tabhost will call both onPause() and onResume() in EVERY load outside of normal lifecycle functions, they still get called before onCreate()
public void onResume(){
super.onResume();
//update Chronometer with time stored in tabchangelistener
if(CentralHelper.stopWatchLastTime!=0)
CentralHelper.stopWatch.setBase(CentralHelper.stopWatchLastTime);
}
this allowed me to do a similar check in onCreate()
if(CentralHelper.stopWatchLastTime!=0)
{
CentralHelper.stopWatch.start(); //this is where it resumes counting from the base set in onResume()
}
else
{
CentralHelper.stopWatch.start();
CentralHelper.stopWatch.setBase(SystemClock.elapsedRealtime());
}
When you switch to a different activity the previous one is paused (onPause, asand so on, in attached image) when you came back to the activity it is resumed, but occasionaly when dalvik runs out of memory your Activity object can be deleted when ton showing.
If you keep your application data in the Activity instance you might loose it accidentally, please read this Activity Lifecycle http://developer.android.com/reference/android/app/Activity.html
This approach is tested and it works really well.
Try this:
Take a boolean volatile variable which will control your thread(start/stop). Take three text views, hour, min and sec text views, and remove chronometer completely. Update your UI using a Handler Write the following code.
public void timeUpdate()
{
timerThread = new Thread(new Runnable() {
#Override
public void run() {
while(continueThread){
Date newDate = new Date();
if(((newDate.getTime()) - date.getTime()) > 1000){
secondCounter = secondCounter+1;
mHandlerUpdateSec.post(mUpdateSec);
System.out.println("Inside the Theread ..."+secondCounter);
if(secondCounter > 59){
minuteCounter = minuteCounter + 1;
mHandlerUpdateMinute.post(mUpdateMinute);
secondCounter = 0;
if(minuteCounter > 59){
hourCounter = hourCounter + 1;
mHandlerUpdateHour.post(mUpdateHour);
minuteCounter = 0;
}
}
}
try{
timerThread.sleep(1000);
}catch (Exception e) {
// TODO: handle exception
}
}
}
});
timerThread.start();
}
The continueThread is a boolean volatile variable. Setting it to false will stop the thread. The timerThread is an instance of thread. There are three counters, hour, min and sec counters which will give you the latest time values. The handlers are updated as follows.
final Handler mHandlerUpdateSec = new Handler();
final Runnable mUpdateSec = new Runnable() {
public void run() {
String temp = "" + secondCounter;
System.out.println("Temp second counter length: " + temp.length());
if(temp.length() == 1)
secTextView.setText("0" + secondCounter);
else
secTextView.setText("" + secondCounter);
}
};
final Handler mHandlerUpdateMinute = new Handler();
final Runnable mUpdateMinute= new Runnable() {
public void run() {
String temp = "" + minuteCounter;
System.out.println("Temp second counter length: " + temp.length());
if(temp.length() == 1)
minTextView.setText("0" + minuteCounter);
else
minTextView.setText("" + minuteCounter);
}
};
final Handler mHandlerUpdateHour = new Handler();
final Runnable mUpdateHour = new Runnable() {
public void run() {
String temp = "" + hourCounter;
System.out.println("Temp second counter length: " + temp.length());
if(temp.length() == 1)
hourTextView.setText("0" + hourCounter);
else
hourTextView.setText("" + hourCounter);
}
};
Now, whenever you want to start the timer, set continueThread to true and call timeUpdate(). To stop it, just do continueThread = false. To start the thread again, set continueThread to true and call timeUpdate() again. Make sure you update the counters accordingly while you start/stop the timer.
You could save the start time in a sharedpreferences (or file, etc.) and establish your count-up from that (rather than starting at 0) in onResume().
Your UI may need some changes to handle the fact that you will have to reset the start time, since it could theoretically count forever.

Categories

Resources