The purpose of the service is to keep track of time since a button was pressed. If the menuactivity is showing, it updates some values on the menuactivity every minute using a timer, otherwise, it just updates itself.
It seems to work fine when the application is open or closed, but when the phone is off it slows down to less than half of what it should be (only showing like 10 minutes have passed after 21 real minutes have passed).
int startTime; //time at which the button is pressed
int time; //the current time, relative to startTime
#Override
public int onStartCommand(Intent intent, int flags, int startId){
startTime = (int)(System.nanoTime()/1000000000.0);
UpdateTimeTask updateTimeTask = new UpdateTimeTask();
timer = new Timer();
timer.schedule(updateTimeTask, 0, UPDATE_PERIOD); //update period is 60,000 (60 seconds)
return START_STICKY;
}
public class UpdateTimeTask extends TimerTask {
public void run() {
updateMenu();
}
}
public void updateMenu(){
time = (int) Math.round((System.nanoTime() / 1000000000.0) - startTime);
if(serviceCallbacks != null){ //it wont be null if its on menuactivity
serviceCallbacks.updateTimeElapsed(time/3600, time/60 - (time/3600) * 60);
}
}
If I have my phone off for a while and then go back in to menuactivity, it doesn't "fix itself" after a few cycles. I thought onStartCommand might be called more than once, but the only time the service can possibly be started is when the button is pressed.
but the only time the service can possibly be started is when the
button is pressed
That is not true. When your app go into the background (is no longer visible) it becomes a candidate to be killed if the system needs memory for other higher ranked apps. Also, the user can kill your app by swiping it from the task list. Because the service returns START_STICKY from onStartCommand(), the system will restart the service after some period of time, calling onStartCommand() with a null intent. This behavior makes a service unsuitable as the home for a data item you want to retain, such as startTime.
An alternative is to persist the value in SharedPreferences. A service is not needed and the periodic update processing can be done in your activity using the postDelayed() method of any view.
This sample activity outlines the basic logic:
public class ButtonActivity extends Activity {
private static final String TIME_KEY = "time";
private static final long PERIOD = 60*1000; // 60 seconds
private SharedPreferences mPrefs;
private Button mButton;
private TextView mTimeView;
private Runnable mDisplayTask = new Runnable() {
#Override
public void run() {
// show the time elapsed since button press in milliseconds
long elapsed = System.currentTimeMillis() - mPrefs.getLong(TIME_KEY, 0);
mTimeView.setText(""+elapsed);
// schedule next display update
mTimeView.postDelayed(mDisplayTask, PERIOD);
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPrefs = getPreferences(MODE_PRIVATE);
// clear the button press time
setPressTime(0);
setContentView(R.layout.activity_demo);
mButton = (Button)findViewById(R.id.button);
mTimeView = (TextView)findViewById(R.id.time);
mButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// save the button press time
setPressTime(System.currentTimeMillis());
// start the display updates
mDisplayTask.run();
}
});
}
#Override
protected void onPause() {
super.onPause();
// activity no longer visible; stop the updates
mTimeView.removeCallbacks(mDisplayTask);
}
#Override
protected void onResume() {
super.onResume();
// activity is visible
// if the button has been pressed, start the display updates
if (mPrefs.getLong(TIME_KEY, 0) > 0) {
mDisplayTask.run();
}
}
private void setPressTime(long time) {
// persist the button press time
SharedPreferences.Editor ed = mPrefs.edit();
ed.putLong(TIME_KEY, time);
ed.commit();
}
}
The accepted answer is correct, but only solved part of the problem. The other problem was that I was using System.nanoTime() instead of .currentTimeMillis(); .nanoTime() stops when the screen is off. Putting this here for possible future googlers.
Related
the program that i am making includes two different count down.
first, user has to type in the password within the time limit and press a button. If user can't input the password before the time limit, then it is over. However, if the user was able to input the password correctly before the time limit, a new timer should start.
public class MalibuCountDownTimer extends CountDownTimer
{
public MalibuCountDownTimer(long startTime, long interval)
{
super(startTime, interval);
}
#Override
public void onFinish()
{
if(hasPlanted == false) {
text.setText("Time's up!");
mTextView.setText("cya");
btnEnable(false);
startB.setEnabled(false);
}
}
#Override
public void onTick(long millisUntilFinished)
{
text.setText(""+String.format("%02d:%02d:%03d",
TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millisUntilFinished)),
TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished)),
TimeUnit.MILLISECONDS.toMillis(millisUntilFinished) - TimeUnit.SECONDS.toMillis(TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished))
));
}
}
this is the first timer, and it is working just fine. However, i am having trouble trying to stop this timer when user entered the correct password.
I have created another class of CountDownTimer, and when the user input the correct password within the time limit, the second timer starts. I put a boolean to stop the first timer to do anything when it ends, but I have no idea how I can fully stop that counter.
if(myTimer != null) {
myTimer.cancel();
myTimer = null;
}
This is in order to cancel the timer early. First check if it is null, and if it isn't, apply the cancel method on it. Let me know if this was helpful.
:)
First you have to create service and call that service in main class where timer is running and call the onReceive method
BroadcastReceiver br = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
//Here you check the timer is running or not
timer.cancel();
}
};
In my onCreate method in my activity i call a method from an object and pass the methods value as 1 which means to start a timer in the objects class. However I want to stop the timer whenever the app closes, loses focus or someone pressed the back button on their device and exited the app. I tried doing this below my onCreate method with an onPause, onStop, onDestroy and entered the methods value as 2 for the object which means to cancel the timer. However my problem is that whenever someone presses the back button on their device and then goes back in to the app the same timer is running twice because the app did not cancel the timer in the onStop, onPause or onDestroy. Why didn't the onStop, onPause and onDestroy stop the timer and how do i make it stop the timer so two arent running when the app is reopened?
Activity below
Ship mShip = new Ship(0,0,0);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
mShip.timerStart(1);
}
#Override
public void onPause()
{
super.onPause();
mShip.timerStart(2);
}
#Override
public void onStop()
{
super.onStop();
mShip.timerStart(2);
}
#Override
public void onDestroy()
{
super.onDestroy();
mShip.timerStart(2);
}
Ship Class below
public static int counter = 0;
public static int counterPerSec = 5;
TimerClass startTimer = (TimerClass) new TimerClass(2000,1000)
{
#Override
public void onFinish() {
counter += counterPerSec;
this.start();
}
};
public void timerStart(int x) {
if(x == 1)
{
startTimer.start();
}
if(x == 2)
{
startTimer.cancel();
}
}
Timer Class
public class TimerClass extends CountDownTimer {
public TimerClass(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
#Override // when timer is finished
public void onFinish() {
this.start();
}
#Override // on every tick of the timer
public void onTick(long millisUntilFinished) {
}
}
I can not see, why your timer is not canceled. But there is another bug in your code: You can not pause and resume a countdown timer by calling resume and start.
If your time gets canceled, you should save the old timer vaules. And if your timer has to be resumed, you can create a new timer with the old timer values. See: Android: How to pause and resume a Count Down Timer?
To your question: Can you debug and check if onPause, onStop, onDestroy is called? Is there any exception thrown? Do you have any compile warnings?
Last important question: How do you know that two timers are running?
Well, I think I can correctly assume that onPause, onStop, and onDestroy are executing, so I would venture to guess that there is a bug in your TimerClass class.
Before I start I have looked at lots of threads including:
How to add time to countdown timer?
Android game countdown timer
But I just cant get my timer to work in the way I require. I want the timer to be counting down from say 30 and when and image is pressed (named imageview1 in this case) the timer adds 3 seconds to the timer to give it more time. I know you cannot essentially add the time while its running and you need to cancel and then start a new timer, The code I have so far is :
public void onClick(View v) {
// TODO Auto-generated method stub
//GlobalClass global = new GlobalClass();
Random rand = new Random();
CountDownTimer thetimer = new myTimer(millisInFuture, 1000);
switch(v.getId()) {
case R.id.buttonstart:
btnstart.setVisibility(View.INVISIBLE);
thetimer.start();
break;
case R.id.imageView1:
if (thetimer != null){
thetimer.cancel();
thetimer = new myTimer(countdownPeriod + 3000, 1000).start();
}
break;
with lots of other case references then :
public class myTimer extends CountDownTimer {
public myTimer(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
#Override
public void onTick(long millisUntilFinished) {
timedisplay.setText("Time Left: " + millisUntilFinished / 1000);
countdownPeriod=millisUntilFinished;
}
#Override
public void onFinish() {
timedisplay.setText("Timer Finished");
started = false;
btnstart.setVisibility(View.VISIBLE);
}
}
I think the problem is its not cancelling the original timer so the label that shows the timer does some crazy things, like jumping around on different numbers both up and down as there would appear more than 1 class of thetimer. That is even though I have included the line thetimer.cancel(); The timer works fine if I just let it run to 0.
Any help would be great
You should not create your timer as a local in onClick. Instead create it as a global and start it somewhere else (in onCreate perhaps).
What happens with your current code is that whenever onClick is called a new timer is created and you then cancel the new timer - which has no effect on any previously created timer(s).
Try something like this:
public class MyActivity extends Activity {
CountDownTimer thetimer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
thetimer = new myTimer(millisInFuture, 1000);
}
public void onClick(View v) {
Random rand = new Random();
switch(v.getId()) {
case R.id.buttonstart:
btnstart.setVisibility(View.INVISIBLE);
thetimer.start();
break;
case R.id.imageView1:
if (thetimer != null) {
thetimer.cancel();
thetimer = new myTimer(countdownPeriod + 3000, 1000).start();
}
break;
}
}
}
You will still have to keep track of the global time somewhere - i.e. the countDonwPeriod used to re-create the timer instance when an image is touched - it should probably be extracted from the timer before canceling it.
I have a CountdownTimer that counts down from 60 seconds. This CountdownTimer works by setting a textView to the remaining milliseconds, but whenever i rotate my device, the CountdownTimer gets reset.
I know this happens because the Activity gets restarted on rotation. So i tried saving the time remaining in a bundle and then restoring it, after the Activity was restarted.
long transferValue;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_playtimemode);
Log.d("Debug", "onCreate: " + transferValue);
long setTime = 60000;
long difference = setTime - transferValue;
new CountDownTimer(difference, 1000) {
public void onTick(long millisUntilFinished) {
millisUntilFinishedToSave = millisUntilFinished;
tvCountdown.setText("" + millisUntilFinished / 1000);
}
public void onFinish() {
tvCountdown.setText("done!");
}
}.start();
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putLong("millisKey", millisUntilFinishedToSave);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
transferValue = savedInstanceState.getLong("millisKey");
Log.d("Debug", "onRestoreInstanceState(): " + transferValue);
}
This however doesn't work. I am intializing transferValue at the top of this code (hence it returning 0), but how can i else save the data from the savedInstanceState to the CountdownTimer?
07-06 20:21:30.038: D/Debug(28995): onCreate: 0
07-06 20:21:30.043: D/Debug(28995): onRestoreInstanceState(): 55994
I would give your timer it's own thread. Your timer is being stopped because it's on the UI thread (as you stated) and when the UI redraws the Activity is re-initialized. All long running processes should have their own thread. Rule of thumb: get out of the UI thread as soon as possible.
Here is an example of using a Service. This service will start when called and stay in memory regardless of screen orientation changes or even activity focus changes.
Here is the service:
public class Timer extends Service {
#Override
public IBinder onBind(Intent i) {
return null;
}
#Override
public int onStartCommand(Intent i, int flags, int startId) {
// Put your timer code here
}
}
You will need to add this line to your manifest (somewhere between the application open/close tags):
<service android:name=".Timer" />
Then you can start the service at any time by using this (it's important to note that you can start it over and over again, the recursive calls do not make a new service.):
startService(new Intent(this, Timer.class));
Use System.currentTimeMillis to get the current system time, then add 60000 milliseconds to the time and store that as an end time. Then any time you have to change anything, just compare System.currentTimeMillis to the EndTime.
Endtime = System.currentTimeMillis + 60000;
then on every instance
TimeRemaining = Endtime - System.currentTimeMillis
The accepted answer makes the thing very complex. You can do it in a simpler way. The problem in your code is that Activity gets created on rotation (see Activity Lifecycle), so does the CountdownTimer instance.
I could write the whole example code but there is a nice answer from #Drew here: Android Innerclass TextView reference when activity resumes
I'm feeling very, very stupid right now... I feel like I must be missing something really obvious.
I've encountered variations of this problem on multiple occasions now, however here is my current example -
When the activity is created, there will be a button marked Start and text set to --:-- next to it. I would like to have it so that when the button is pressed, a timer starts from one minute and displays the seconds remaining in the --:-- text as 00:59 etc. etc., and the text on the button changes to Pause. If the button is pressed when the timer is running, it pauses the timer and changes the text on the button to Start.
So I was using a boolean, timerRunning, in order to keep track of whether the timer was running or not. But if I try to change timerRunning within the onClickListener it gives an error and tells me to change timerRunning to final, and then once I do that it says "The final local variable timerRunning cannot be assigned, since it is defined in an enclosing type."
I'm sorry if this is unclear - I'm just really confused with where I should be declaring variables / how to access them etc. in Android, I don't really understand why I keep getting weird errors all the time.
Thanks.
public class Timing extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.timing);
Button bStartJammer = (Button) findViewById(R.id.buttonStartJammer);
CountDownTimer cdtJammerTimer;
long lJammerTotalMS = 60000;
final boolean timerRunning = false;
bStartJammer.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
timerRunning = true;
}
});
}
}
Without source context, it's tough to visualize what you're doing.
How are you defining your click handler? If it's an anonymous class, you'll run into the final issues--is your activity or handler so complex that it makes a separate class completely necessary?
In the previous question my click handler was implemented by the activity, so it has access to that instance's variables. A much-abbreviated skeleton of what I had been doing before not using the CountDownTimer:
public class FooTimer extends Activity implements View.OnClickListener {
private CountDownTimer timer;
private TextView timerDisplay;
private Button pauseButton;
private boolean timerRunning = false;
private boolean timerDone = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
pauseButton = (Button) findViewById(R.id.pause_button);
pauseButton.setOnClickListener(this);
timerDisplay = (TextView) findViewById(R.id.timer_display);
timerDisplay.setText(String.format("%d:%02d:%02d", hours, minutes, seconds));
timer = newTimer();
timerRunning = true;
timer.start();
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.pause_button:
toggleTimer();
break;
}
}
private void toggleTimer() {
if (timerRunning) {
timer.cancel();
pauseButton.setText(getResources().getString(R.string.resume_label));
timerRunning = false;
} else if (timerDone) {
finishActivity(0);
} else {
seconds += 1;
timer = newTimer();
timer.start();
pauseButton.setText(getResources().getString(R.string.pause_label));
timerRunning = true;
}
}
private CountDownTimer newTimer() {
millis = (minutes * 60 * 1000) + ((seconds + 1) * 1000);
return new CountDownTimer(millis, 1000) {
public void onTick(long millisUntilFinished) {
timerDisplay.setText(String.format("%d:%02d:%02d", hours, minutes, seconds));
}
public void onFinish() {
timerDisplay.setText("Finished");
timerRunning = false;
timerDone = true;
pauseButton.setText(getResources().getString(R.string.back_label));
}
};
}
}
(I took a bunch of stuff out, and added some really early code back in, so it's a bit of a mish-mosh, but the ideas are there.)
Your new OnClickListener(){..} is actually an anonymous class: http://mindprod.com/jgloss/anonymousclasses.html
Anonymous classes have access to class (static) and instance fields of enclosing class. So a solution is to have timerRunning defined as a field, i.e. define it outside of onCreate() method.
its not starting the code anywhere, your just setting it to true that its a timer. watch the tutorials here and they should really help you out. cornboyzandroid
Some of his earlier videos really describe how to do a timer pretty clearly and step by step. And he helps with global and local variables. around episode 6 or 7 just check his page out.