Android - How do I continuously run a thread, one after another - java

So i have the following code below which basically takes the initial battery level, waits a certain amount of time, and takes the ending battery level inside of calculateHelper which then finds the difference and prints it.
// Get the initial battery level
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = this.registerReceiver(null, ifilter);
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
System.out.println("Initial battery level is: " + level);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
final float batteryPctTemp0 = level / (float) scale;
final float batteryPct0 = batteryPctTemp0 * 100;
int waitTime = 60000 * interval; // 1 minute is 60000 miliseconds
System.out.println("Wait time is " + waitTime);
Runnable r = new Runnable() {
#Override
public void run(){
calculateHelper(batteryPct0,startButton);
}
};
Handler h = new Handler();
h.postDelayed(r, waitTime);
I want to infinitely loop (until program exit) this entire process so that after each successive thread finishes, the next one begins, taking a new initial battery level each time and passing it into the calculateHelper function for calculation of a new difference. I do NOT want threads to stack up. I want one thread at a time. In other words, the loop needs to wait for the thread to finish before starting another one.
I can't for the life of me figure out how to do this! If i put the entire thing into a while, it will just repeatedly open up threads crashing the phone.
If anyone can point me in the right direction on the matter I would be greatly appreciative. Also, if any more code is needed to solve the problem, simply comment and I will reply as soon as I have added it to my question.
Thank you.
Thanks to Whooper, I've added in this method of regulating execution order in a loop. However, for some reason my postExecute() method is never being executed and nothing is happening.
private class BatteryLifeTask extends AsyncTask<Void, Void, Void> {
// Member variables
Context appContext;
float batteryPct0;
Button startButton;
public BatteryLifeTask(Context context, Button start) {
super();
appContext = context;
startButton = start;
}
protected Void doInBackground(Void... params) {
// Get the initial battery level
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = appContext.registerReceiver(null, ifilter);
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
System.out.println("Initial battery level is: " + level);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
final float batteryPctTemp0 = level / (float) scale;
batteryPct0 = batteryPctTemp0 * 100;
return null;
}
protected void onPostExecute() {
int waitTime = 60000 * interval; // 1 minute is 60000 miliseconds
System.out.println("In postExecute. waitTime is" + waitTime);
Runnable r = new Runnable() {
#Override
public void run(){
System.out.println("An interval has passed.");
calculateHelper(batteryPct0,startButton);
new BatteryLifeTask(appContext,startButton).execute();
}
};
Handler h = new Handler();
h.postDelayed(r, waitTime);
}
}
and my call to the execute method:
// Start the task loop
new BatteryLifeTask(getApplicationContext(), startButton).execute();
I've found the problem:
I forgot to set the #Override annotation, and this answer : https://stackoverflow.com/a/11127996/2247192 states:
"If your params of onPostExecute(Param param) don't match the one you defined with extends AsyncTask<...,...,Param> and you didn't use the #Override annotation, it will never be executed and you don't get a warning from Eclipse."
So I've corrected my postExecute method to:
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
int waitTime = 60000 * interval; // 1 minute is 60000 miliseconds
System.out.println("In postExecute. waitTime is " + waitTime);
Runnable r = new Runnable() {
#Override
public void run(){
System.out.println("An interval has passed.");
calculateHelper(batteryPct0,startButton);
new BatteryLifeTask(appContext,startButton).execute();
}
};
Handler h = new Handler();
h.postDelayed(r, waitTime);
}
All issues are now resolved.

Try using an AsyncTask.
http://developer.android.com/reference/android/os/AsyncTask.html
This way you can execute the task again when onPostExecute() is called.
Something like this:
private class BatteryLifeTask extends AsyncTask<Void, Void, Void> {
protected void doInBackground(Void... params) {
// Get the initial battery level
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = this.registerReceiver(null, ifilter);
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
System.out.println("Initial battery level is: " + level);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
final float batteryPctTemp0 = level / (float) scale;
final float batteryPct0 = batteryPctTemp0 * 100;
}
protected void onPostExecute() {
int waitTime = 60000 * interval; // 1 minute is 60000 miliseconds
Runnable r = new Runnable() {
#Override
public void run(){
new BatteryLifeTask.execute();
}
};
Handler h = new Handler();
h.postDelayed(r, waitTime);
}
}
Be aware that this code is untested. But I hope it gives you an idea :-)

Related

Accessing multiple sensors in non-activity classes

I am trying to use multiple sensors like Accelerator,Magnetic Field, Light and so on and for each sensor I wrote an individual class which not an activity , each of them has its SensorEventListener as well. What I want to do is when user chooses one of them I start to show the data on a fragment(on MainActivity) , when user changes the previous sensor should stop and new one should start. However, when I try to stop previous one by unregistering its listener , it doesn't unregister but it registers and works. I want to stop previous listener. What is wrong? Any ideas?
Here is the sensor class;
public class Accelerometer
{
private SensorManager sensorManager;
private Sensor sensor;
public List<ObjAccelerometer> lstData;
ObjAccelerometer currentData;
float lastX,lastY,lastZ;
String currentTime;
int numberOfSamples;
Context context;
public Accelerometer(Context _context,int _numberSample)
{
context=_context;
sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
numberOfSamples=_numberSample;
lstData=new ArrayList<>();
}
public void registerUnregister(boolean register)
{
if(register)
sensorManager.registerListener(mSensorListener, sensor, SensorManager.SENSOR_DELAY_FASTEST);
else
sensorManager.unregisterListener(mSensorListener);
}
Calendar time;
private final SensorEventListener mSensorListener = new SensorEventListener() {
public void onSensorChanged(SensorEvent event) {
if(numberOfSamples>lstData.size()) {
if (currentData != null) {
lastX = currentData.get_x();
lastY = currentData.get_y();
lastZ = currentData.get_z();
}
currentData = new ObjAccelerometer();
time = Calendar.getInstance();
currentTime = time.get(Calendar.HOUR) + ":" + time.get(Calendar.MINUTE) + ":" + time.get(Calendar.SECOND) + ":" + time.get(Calendar.MILLISECOND);
currentData.set_time(currentTime);
currentData.set_x(event.values[0]);
currentData.set_y(event.values[1]);
currentData.set_z(event.values[2]);
Float speed = Math.abs(event.values[0] + event.values[1] + event.values[2] - lastX - lastY - lastZ);
currentData.set_speed(speed);
lstData.add(currentData);
Util.createToaster(context, "X Y Z Time:" + currentData.toString());
}
else
registerUnregister(false);
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
}
Here the code for calling them ;
unRegisterAllSensors();
switch (selectedSensor) {
case Accelerometer:
accelerometer= new Accelerometer(context, DEFAULT_SAMPLE_NUMBER);
accelerometer.registerUnregister(true);
lstAccelerometer = accelerometer.lstData;
break;
case Linear_Accelerometer:
linearAccelerometer= new LinearAccelerometer(context, DEFAULT_SAMPLE_NUMBER);
linearAccelerometer.registerUnregister(true);
break;
....
Here is the unRegisterAllSensors() function code:
if(accelerometer!=null) {
accelerometer.registerUnregister(false);
accelerometer=null;
}
if(linearAccelerometer!=null) {
linearAccelerometer.registerUnregister(false);
linearAccelerometer=null;
}
Sorry about bothering people,it actually works!
I made a few changes including setting SensorManager final parameter and also removing Toast message and add A toaster only for register and unregister of each listener. It worked! I believe the problem was caused by two things;
1-Toasting the message is slower process than sensing because of that even though I unregistered messages were still on the screen for a while.
2- when I didn't define SensorManager final , every time I was getting a new instance of it so size of the listener list was 0.
Still, this can be a good sample for people who want to call sensors from non-activity classes.
Thanks!

Handler freezes UI when processing while loop inside a runnable task [android]

I want to repeat - - -... so on.
The recording (for seconds) stops when its energy reaches at some threshold value and playbacked. After playback, new recording begins. So I have to monitor the energy using buffers and begin new recording after playback time computation. My pseudocode (most simplified form) looks like this :
...
public class MainActivity extends Activity {
private Handler myHandler = new Handler();
....
private Runnable timedTask = new Runnable() {
public void run() {
audiorecorder = new AudioRecord(... initializes
audiorecorder.startRecording();
isRecording = true;
ElapsedTime = 0.0;
while(true) {
ReadByte = audiorecorder.read(buffer, 0, buffersize);
// Write ReadByte onto file (temp.pcm)
EnergyBuffer = <some averaged energy over a period (1 sec or so)>
if(EnergyBuffer > Threshold)
break;
ElapsedTime = ...
}
// audiorecorder.release, stop, and null
// play temp.pcm using AudioTrack
myHandler.postDelayed(timedTask, Elapsedtime);
};
}
and this is triggered by a start and a stop button as follows,
StartButton.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
myHandler.post(timedTask);
}
});
StopButton.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
myHandler.removeCallbacks(timedTask);
}
});
The problem is when audio is recording or playback, no button is working, I have to wait until it finishes the recording. How can I prevent freezing from recording? Is it just because of whileloop? Please help..!
If I run as follows, there is no error.
Thread t = new Thread(timedTask);
t.start();
This is only one-time ok, but after then when handler does in delayed time, it also freezes the UI.
A handler posts runnable tasks into a looper in a thread. It doesn't on it self create a new thread on which to run the runnables on asynchronously.
When you create a handler by calling the constructor new Handler(), the handler will get the looper of the current thread from which the constructor was called on by default. Since you called new Handler() on the UI thread, any runnable given to the handler's post command will just be added to the UI thread.
You need to create a new thread and get it's looper, by doing
HandlerThread readThread = new HandlerThread("");
readThread.start();
myHandler = new Handler(readThread.getLooper());
HandlerThread is a thread that automatically sets up a looper for you to use.
Of course, you will also need to quit the thread once you don't need it
readThread.quit();
You are the genius, works like a charm and it is exactly what I worked for days, thank you so much!!
Here is the answer from my problem :
public class MainActivity extends Activity {
private Handler myHandler;
....
private Runnable timedTask = new Runnable() {
public void run() {
audiorecorder = new AudioRecord(... initializes
audiorecorder.startRecording();
isRecording = true;
ElapsedTime = 0.0;
while(true) {
ReadByte = audiorecorder.read(buffer, 0, buffersize);
// Write ReadByte onto file (temp.pcm)
EnergyBuffer = <some averaged energy over a period (1 sec or so)>
if(EnergyBuffer > Threshold)
break;
ElapsedTime = ...
}
// audiorecorder.release, stop, and null
// play temp.pcm using AudioTrack
myHandler.postDelayed(timedTask, Elapsedtime);
};
....
protected void OnCreate(Bundle savedInstanceState) {
...
HandlerThread readThread = new HandlerThread("");
readThread.start();
myHandler = new Handler(readThread.getLooper());
...
}
}

Stackoverflow inside thread - stopwatch java

I would like to create a stopwatch as an android app. My problem is that I got a stackoverflow. Basically I have a class Timer and an onCreate method that instatiate it. Thats my implementation:
public abstract class Timer implements Runnable {
private boolean running;
public void start(){
running = true;
this.start();
}
#Override
public void run() {
long milliSeconds = 0;
long seconds = 0;
long minutes = 0;
long baseTime = SystemClock.elapsedRealtime();
while(running){
long time = SystemClock.elapsedRealtime() - baseTime;
long rest = time % 60000;
milliSeconds = rest % 1000;
seconds = rest - milliSeconds;
minutes = time - seconds - milliSeconds;
display(milliSeconds, seconds, minutes);
}
}
public void stop(){
running = false;
}
public abstract void display(long milliSeconds, long seconds, long minutes);
}
And my onCreate method:
public TextView time;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.opslimit);
time = (TextView)findViewById(R.id.textView3);
Timer timer = new Timer(){
#Override
public void display(long milliSeconds, long seconds, long minutes) {
time.setText(minutes + ":" + seconds + ":" + milliSeconds);
}
};
timer.start();
}
Has anyone an idea why I got a Stackoverflow. May be it deals with the display part?
You call start method recursively within itself. Fix it, and you won't get SO exception.
Note, however, that even if you change to :
public void start(){
running = true;
this.run();
}
which will fix stackoverflow, your code will run in a main thread and therefore your app won't work. You have to spawn a new thread.
I really suggest you use CountDownTimer - it will be clean code and less boilerplate threading, check example at the link.
You can, however, make your code work by doing this changes :
make Timer extends Thread instead of implementing Runnable.
change start to :
public void start(){
running = true;
super.start(); // <- note super here
}
Optionally add Override notation to start/stop methods, or rename your stop -> stopTimer

Thread reset when resuming application

Please I need help!!
I created an app that reads data from arduino through separate thread (ReadingProcessor) and fillings the values into readings[], then I created another separate thread that checks on the values. In this checking, if it's the first time that a warning occurs then the application sends message, else if there is previous warning readings, the application should wait till passing a warning interval
public class WarningProcessor extends Thread {
float readings[];
float[] min, max;
long elapsedTime;
long[] lastWarningTime;
boolean[] inWarning;
long checkInterval = Long.parseLong(Settings.Swarning) * 60000;
long currentTime;
SerialActivity sa = new SerialActivity();
WarningProcessor(float readings[]) {
this.readings = readings;
}
#Override
public void run() {
sleep_s(2);
synchronized (readings) {
lastWarningTime = new long[readings.length];
inWarning = new boolean[readings.length];
Arrays.fill(inWarning, false);
}
while (true) {
this.readings = ReadingProcessor.readings;
synchronized (readings) {
for (int i = 0; i < readings.length; i++) {
currentTime = Calendar.getInstance().getTimeInMillis();
if (readings[i] > 100) { //here to make boundaries
if (inWarning[i] == false) {
//send warning
for(String number : StartPage.phoneNumbers)
SmsManager.getDefault().sendTextMessage(number,
null,"Warning "+readings[i], null, null);
lastWarningTime[i] = currentTime;
inWarning[i] = true;
} else {
if (currentTime - lastWarningTime[i] > checkInterval) {
//send warning
for(String number : StartPage.phoneNumbers)
SmsManager.getDefault().sendTextMessage(number,
null,"Warning "+readings[i], null, null);
lastWarningTime[i] = currentTime;
}
}
} else {
inWarning[i] = false;
}
}
}
sleep_s(1);
}
}
In case of continuous warning data the program should sends message in interval, and this works well when I'm still on activity and also when I'm onpause() state, but the problem is that after the onpause() when I return to application UI , the program resends messages in case of continuous interval, discarding the waiting till passing the interval
public class SerialActivity extends Activity {
private static ArduinoSerialDriver sDriver;
private static TextView mDumpTextView;
private static ScrollView mScrollView;
String Data[]={"Temperature"};
float[] readings = new float[Data.length];
ReadingProcessor readingProcessor;
WarningProcessor warningProcessor;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.serialactivity);
mDumpTextView = (TextView) findViewById(R.id.consoleText);
mScrollView = (ScrollView) findViewById(R.id.demoScroller);}
#Override
protected void onStart() {
super.onStart();
ReadingProcessor rp = new ReadingProcessor(readings,sDriver);
readingProcessor=rp;
WarningProcessor wp = new WarningProcessor(readings);
warningProcessor=wp;
rp.start();
wp.start();
}
#SuppressWarnings("deprecation")
#Override
protected void onDestroy() {
super.onDestroy();
readingProcessor.Stop();
warningProcessor.stop();
}
So please help me, I tried too many solutions like using handler and I got the same problem
onStart is called every time you return the application to the foreground. Your problem is that you have multiple instances of each thread running. If you only want one instance of each thread running, you need to create and start the threads in onCreate instead of onStart. In general, you should only start a thread in onStart if you are going to kill it in onPause.

Android: CountDownTimer skips last onTick()!

Code:
public class SMH extends Activity {
public void onCreate(Bundle b) {
super.onCreate(b);
setContentView(R.layout.main);
TextView tv = (TextView) findViewById(R.id.tv);
new CountDownTimer(10000, 2000) {
public void onTick(long m) {
long sec = m/1000+1;
tv.append(sec+" seconds remain\n");
}
public void onFinish() {
tv.append("Done!");
}
}.start();
}
Output:
10 seconds remain
8 seconds remain
6 seconds remain
4 seconds remain
Done!
Problem:
How do I get it to show "2 seconds remain"? The time elapsed is indeed 10 seconds, but the last onTick() never happens. If I change the second parameter from 2000 to 1000, then this is the output:
10 seconds remain
9 seconds remain
8 seconds remain
7 seconds remain
6 seconds remain
5 seconds remain
4 seconds remain
3 seconds remain
2 seconds remain
Done!
So you see, it seems to be skipping that last onTick() call. And btw, the XML file is basically the default main.xml with the TextView assigned the id tv and the text set to "".
I checked the source code of CountDownTimer. The "missing tick" comes from a special feature of CountDownTimer that I have not yet seen being documented elsewhere:
At the start of every tick, before onTick() is called, the remaining time until the end of the countdown is calculated. If this time is smaller than the countdown time interval, onTick is not called anymore. Instead only the next tick (where the onFinish() method will be called) is scheduled.
Given the fact that hardware clocks are not always super precise, that there may be other processes in the background that delay the thread running CountDownTimer plus that Android itself will probably create a small delay when calling the message handler of CountDownTimer it is more than likely that the call for the last tick before the end of the count down will be at least one millisecond late and therefore onTick() will not be called.
For my application I solved this problem simply by making the tick intervals "slightly" smaller (500 ms)
myCountDownTimer = new CountDownTimer(countDownTime, intervalTime - 500) {
...
}
and I could leave my code just as it is. For applications where the length of the interval time is critical, the other solutions posted here are probably the best.
I don't know why the last tick is not working but you can create your own timer with Runable , for example.
class MyCountDownTimer {
private long millisInFuture;
private long countDownInterval;
public MyCountDownTimer(long pMillisInFuture, long pCountDownInterval) {
this.millisInFuture = pMillisInFuture;
this.countDownInterval = pCountDownInterval;
}
public void Start()
{
final Handler handler = new Handler();
Log.v("status", "starting");
final Runnable counter = new Runnable(){
public void run(){
if(millisInFuture <= 0) {
Log.v("status", "done");
} else {
long sec = millisInFuture/1000;
Log.v("status", Long.toString(sec) + " seconds remain");
millisInFuture -= countDownInterval;
handler.postDelayed(this, countDownInterval);
}
}
};
handler.postDelayed(counter, countDownInterval);
}
}
and to start it,
new MyCountDownTimer(10000, 2000).Start();
EDIT FOR GOOFY'S QUESTION
you should have a variable to hold counter status (boolean) . then you can write a Stop() method like Start().
EDIT-2 FOR GOOFY'S QUESTION
actually there is no bug on stopping counter but there is a bug on start again after stop(resume).
I'm writing a new updated full code that I had just tried and it's working. It's a basic counter that show time on screen with start and stop button.
class for counter
public class MyCountDownTimer {
private long millisInFuture;
private long countDownInterval;
private boolean status;
public MyCountDownTimer(long pMillisInFuture, long pCountDownInterval) {
this.millisInFuture = pMillisInFuture;
this.countDownInterval = pCountDownInterval;
status = false;
Initialize();
}
public void Stop() {
status = false;
}
public long getCurrentTime() {
return millisInFuture;
}
public void Start() {
status = true;
}
public void Initialize()
{
final Handler handler = new Handler();
Log.v("status", "starting");
final Runnable counter = new Runnable(){
public void run(){
long sec = millisInFuture/1000;
if(status) {
if(millisInFuture <= 0) {
Log.v("status", "done");
} else {
Log.v("status", Long.toString(sec) + " seconds remain");
millisInFuture -= countDownInterval;
handler.postDelayed(this, countDownInterval);
}
} else {
Log.v("status", Long.toString(sec) + " seconds remain and timer has stopped!");
handler.postDelayed(this, countDownInterval);
}
}
};
handler.postDelayed(counter, countDownInterval);
}
}
activity class
public class CounterActivity extends Activity {
/** Called when the activity is first created. */
TextView timeText;
Button startBut;
Button stopBut;
MyCountDownTimer mycounter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
timeText = (TextView) findViewById(R.id.time);
startBut = (Button) findViewById(R.id.start);
stopBut = (Button) findViewById(R.id.stop);
mycounter = new MyCountDownTimer(20000, 1000);
RefreshTimer();
}
public void StartTimer(View v) {
Log.v("startbutton", "saymaya basladi");
mycounter.Start();
}
public void StopTimer(View v) {
Log.v("stopbutton", "durdu");
mycounter.Stop();
}
public void RefreshTimer()
{
final Handler handler = new Handler();
final Runnable counter = new Runnable(){
public void run(){
timeText.setText(Long.toString(mycounter.getCurrentTime()));
handler.postDelayed(this, 100);
}
};
handler.postDelayed(counter, 100);
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:weightSum="1">
<TextView android:textAppearance="?android:attr/textAppearanceLarge"
android:text="TextView" android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:id="#+id/time">
</TextView>
<Button android:text="Start"
android:id="#+id/start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="StartTimer">
</Button>
<Button android:text="Stop"
android:id="#+id/stop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="StopTimer">
</Button>
</LinearLayout>
I've spent hours trying to figure out this problem, and I'm happy to show you a nice work around. Don't bother waiting for the onFinish() call, just add 1 (or whatever your interval is) to your units, then add an if statement in the onTick() calls. Just do your onFinish() task(s) on the last onTick(). Here's what I've got:
new CountDownTimer( (countDownTimerValue + 1) * 1000, 1000) { //Added 1 to the countdownvalue before turning it into miliseconds by multiplying it by 1000.
public void onTick(long millisUntilFinished) {
//We know that the last onTick() happens at 2000ms remaining (skipping the last 1000ms tick for some reason, so just throw in this if statement.
if (millisUntilFinished < 2005){
//Stuff to do when finished.
}else{
mTextField.setText("Time remaining: " + (((millisUntilFinished) / 1000) - 1)); //My textfield is obviously showing the remaining time. Note how I've had to subtrack 1 in order to display the actual time remaining.
}
}
public void onFinish() {
//This is when the timer actually finishes (which would be about 1000ms later right? Either way, now you can just ignore this entirely.
}
}.start();
The most simple solution I came up with is as follows. Note that it only works if you need a simple screen to display with a seconds countdown.
mTimer = new CountDownTimer(5000, 100){
public void onTick(long millisUntilFinished) {
mTimerView.setText(Long.toString(millisUntilFinished/1000));
}
public void onFinish() {
mTimerView.setText("Expired");
}
};
mTimer.start();
In the code above the onTick() is called every 100 milliseconds but visually only seconds are displayed.
While the solution above is valid, it can be further improved. It unnecessarily has a runnable inside another class (which can already be treated on it's own). So just create a class that extends a thread (or runnable).
class MyTimer extends Thread {
private long millisInFuture;
private long countDownInterval;
final Handler mHandler = new Handler();
public MyTimer(long pMillisInFuture, long pCountDownInterval) {
this.millisInFuture = pMillisInFuture;
this.countDownInterval = pCountDownInterval;
}
public void run() {
if(millisInFuture <= 0) {
Log.v("status", "done");
} else {
millisInFuture -= countDownInterval;
mHandler.postDelayed(this, countDownInterval);
}
}
}
I found easy solution. I need CountDown to update ProgressBar, so I did this:
new CountDownTimer(1000, 100) {
private int counter = 0;
#Override
public void onTick(long millisUntilFinished) {
Log.d(LOG_TAG, "Tick: " + millisUntilFinished);
if (++counter == 10) {
timeBar.setProgress(--lenght); // timeBar and lenght defined in calling code
counter = 0;
}
}
#Override
public void onFinish() {
Log.d(LOG_TAG, "Finish.");
timeBar.setProgress(0);
}
};
Small tick do the trick :)
So I think I went a little over board because my timer runs in its own thread instead of using postDelay handlers, though it always posts back to the thread it was created in. I also knew that I only cared about seconds so its simplified around that idea. It also lets you cancel it and restart it. I do not have pausing built in because that's not in my needs.
/**
* Created by MinceMan on 8/2/2014.
*/
public abstract class SecondCountDownTimer {
private final int seconds;
private TimerThread timer;
private final Handler handler;
/**
* #param secondsToCountDown Total time in seconds you wish this timer to count down.
*/
public SecondCountDownTimer(int secondsToCountDown) {
seconds = secondsToCountDown;
handler = new Handler();
timer = new TimerThread(secondsToCountDown);
}
/** This will cancel your current timer and start a new one.
* This call will override your timer duration only one time. **/
public SecondCountDownTimer start(int secondsToCountDown) {
if (timer.getState() != State.NEW) {
timer.interrupt();
timer = new TimerThread(secondsToCountDown);
}
timer.start();
return this;
}
/** This will cancel your current timer and start a new one. **/
public SecondCountDownTimer start() {
return start(seconds);
}
public void cancel() {
if (timer.isAlive()) timer.interrupt();
timer = new TimerThread(seconds);
}
public abstract void onTick(int secondsUntilFinished);
private Runnable getOnTickRunnable(final int second) {
return new Runnable() {
#Override
public void run() {
onTick(second);
}
};
}
public abstract void onFinish();
private Runnable getFinishedRunnable() {
return new Runnable() {
#Override
public void run() {
onFinish();
}
};
}
private class TimerThread extends Thread {
private int count;
private TimerThread(int count) {
this.count = count;
}
#Override
public void run() {
try {
while (count != 0) {
handler.post(getOnTickRunnable(count--));
sleep(1000);
}
} catch (InterruptedException e) { }
if (!isInterrupted()) {
handler.post(getFinishedRunnable());
}
}
}
}
To expand on Nantoka's answer. Here's my code to ensure the view is updated correctly:
countDownTimer = new CountDownTimer(countDownMsec, 500)
{
public void onTick(long millisUntilFinished)
{
if(millisUntilFinished!=countDownMsec)
{
completedTick+=1;
if(completedTick%2==0) // 1 second has passed
{
// UPDATE VIEW HERE based on "seconds = completedTick/2"
}
countDownMsec = millisUntilFinished; // store in case of pause
}
}
public void onFinish()
{
countDownMsec = 0;
completedTick+=2; // the final 2 ticks arrive together
countDownTimer = null;
// FINAL UPDATE TO VIEW HERE based on seconds = completedTick/2 == countDownMsec/1000
}
}
I also faced the same issue with CountDownTimer and I tried different approaches.
So one of the easiest ways is in solution provided by #Nantoca - he suggests to double the frequency from 1000ms to 500ms. But I don't like this solution because it makes more work which will consume some extra battery resource.
So I decided to use #ocanal's soultion and to write my own simple CustomCountDownTimer.
But I found couple of flaws in his code:
It's a bit inefficient (creating second handler to publish results)
It starts to publish first result with a delay. (You need to do a post() method rather than postDelayed() during first initialization)
odd looking. Methods with capital letter, status instead of classic isCanceled boolean and some other.
So I cleaned it a bit and here is the more common version of his approach:
private class CustomCountDownTimer {
private Handler mHandler;
private long millisUntilFinished;
private long countDownInterval;
private boolean isCanceled = false;
public CustomCountDownTimer(long millisUntilFinished, long countDownInterval) {
this.millisUntilFinished = millisUntilFinished;
this.countDownInterval = countDownInterval;
mHandler = new Handler();
}
public synchronized void cancel() {
isCanceled = true;
mHandler.removeCallbacksAndMessages(null);
}
public long getRemainingTime() {
return millisUntilFinished;
}
public void start() {
final Runnable counter = new Runnable() {
public void run() {
if (isCanceled) {
publishUpdate(0);
} else {
//time is out
if(millisUntilFinished <= 0){
publishUpdate(0);
return;
}
//update UI:
publishUpdate(millisUntilFinished);
millisUntilFinished -= countDownInterval;
mHandler.postDelayed(this, countDownInterval);
}
}
};
mHandler.post(counter);
}
}
if Your time Interval is more than 4 sec then every onTick() call would not be proper. So if you want precise result then keep interval less than 5 sec. The Reseaon is at the start of every tick, before onTick() is called, the remaining time until the end of the countdown is calculated and If this time is smaller than the countdown time interval, onTick() would not not called anymore. Instead only the next tick (where the onFinish() method will be called) is scheduled.
Add a few milliseconds to your timer to allow it time to process the code.
I added +100 to your timer-length, and also Math.ceil() to round up the result, rather than adding 1.
Also... the first tick is AFTER 2000 millis, so you won't get a "10 seconds left" entry unless you add it.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView tv = (TextView) findViewById(R.id.tv);
tv.setText("10 Seconds remain\n"); //displayed before the first tick.
new CountDownTimer(10000+25, 1000) { //25 to account for processing time
public void onTick(long m) {
long sec = (long) Math.ceil(m / 2000 ); //round up, don't add 1
tv.append(sec + " seconds remain\n");
}
public void onFinish() {
tv.append("Done!");
}
}.start();
}
You are calculating time remaining incorrectly. The callback gets the number of milliseconds until completion of the task.
public void onTick(long m) {
long sec = m/1000+1;
tv.append(sec+" seconds remain\n");
}
should be
public void onTick(long m) {
long sec = m/1000;
tv.append(sec+" seconds remain\n");
}
I've never used this class myself but it looks like you will not get a callback the instant it starts, which is why it appears like you're missing an entry. e.g. 10000 ms, 1000 ms per tick you'd get a total of 9 update callbacks, not 10 - 9000, 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000, finish.

Categories