I'm building a simple timer that will display the milliseconds as well. Something like
Hours:Minutes:Seconds:Milliseconds
I'm updating the TextView that displays the timer every millisecond, inside a Handler that calls itself every 1 millisecond:
Handler timerHandler = new Handler();
Runnable timerRunnable = new Runnable() {
#Override
public void run() {
//calculate new time
...
//update TextView
...
timerHandler.postDelayed(this, 1);
}
};
My question is, will calling a Handler every 1 millisecond significantly affect the battery usage of the app?
Is there any better approach (performance-wise) other than using a Handler?
I've checked multiple posts of timers and stopwatches implementations but they will either not display the milliseconds or they won't discuss about performance issues.
Related
I'm building app for making tests. I have a scene made in SceneBuilder. There is a ImageView under image Label with Question and 3 buttons "A", "B", "C", Texts to buttons and question's Label are taken from DataBase, if you click answer new Image, question and asnwers are loading, everything works, but i want to add a Timer in the corner. When image and question show on the screen there will be "Time to read the question" and countdown from 10 to 0, and then "Time to answer" and again countdown from 10 to 0. If timer ends and there is no answer question and image will change automaticly. But the problem is that, i can do the timer, its counting down, and after this time it change the question but I dont know how to put it into Label. If inside Timer I do something like seconds-- label.setText(seconds) there is no error but when I start app there is a lot of exceptions. Can you help me how I can put this variable which is decrementing in timer after each second to Label ?
public void setTimer() {
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
if(interval > 0)
{
timeElapsed.setText("Time to read the question: "+interval);
System.out.println(interval);
interval--;
}
else
timer.cancel();
}
}, 1000,1000);
}
Now i have something like this, in console everything is working there is a countdown from 10 to 0 but no effect in scene.
And errors:
Exception in thread "Timer-0" java.lang.IllegalStateException: Not on FX application thread; currentThread = Timer-0
at javafx.graphics/com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:291)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:423)
at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:493)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:113)
at javafx.base/com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:108)
at javafx.controls/javafx.scene.control.skin.LabeledSkinBase.updateChildren(LabeledSkinBase.java:271)
at javafx.controls/javafx.scene.control.skin.LabeledSkinBase.lambda$new$11(LabeledSkinBase.java:219)
at javafx.controls/com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler.lambda$new$1(LambdaMultiplePropertyChangeListenerHandler.java:49)
at javafx.base/javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:89)
at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181)
at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80)
at javafx.base/javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:104)
at javafx.base/javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:111)
at javafx.base/javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:145)
at javafx.base/javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:50)
at javafx.base/javafx.beans.property.StringProperty.setValue(StringProperty.java:65)
at javafx.controls/javafx.scene.control.Labeled.setText(Labeled.java:147)
at gui.controller.StudentTestYesNoController$1.run(StudentTestYesNoController.java:40)
at java.base/java.util.TimerThread.mainLoop(Timer.java:556)
at java.base/java.util.TimerThread.run(Timer.java:506)
The problem is that you are trying to change the UI from a thread other than the application.
This should solve the problems with your current implementation
public void setTimer() {
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
if(interval > 0)
{
Platform.runLater(() -> timeElapsed.setText("Time to read the question: "+interval));
System.out.println(interval);
interval--;
}
else
timer.cancel();
}
}, 1000,1000);
}
Also, you can take a look at something specific about the javafx - Timeline
JavaFX periodic background task
Adding on to what mr mcwolf said above, i think you need to set
Platform.setImplicitExit(false);
before Platform.runLater(); so that it will run every time the task is called.
I am trying to write a simple little thing that stops an audio file from being played when the user "interrupts" it by talking to the phone. I'm using the SoundMeter class that this person wrote to get the maxAmplitude of the microphone. I've set a threshold to 800 and want to test the return of the microphone amplitude against it constantly (every 50ms).
For some reason, it's hit or miss when I get the "interrupt" to show up and when the audio stops. I'd like for the interrupt to be showing the entire time the amplitude is above 800.
handler = new Handler();
final Runnable r = new Runnable() {
#Override
public void run() {
mic_amplitude.setText(""+mSoundMeter.getAmplitude());
if(mSoundMeter.getAmplitude() > threshold) {
Log.d("INTERRUPT", "INTERRUPT");
interrupt.setText("Interrupt");
mBackgroundAudio.pause(mp);
audioButton.setText("Resume Audio");
} else {
interrupt.setText("");
}
handler.postDelayed(this, 50);
}
};
handler.postDelayed(r, 50);
While viewing this, I'm able to see the amplitude at a steady 40-50 while there is no background noise and peaks of 1200-1500 when talking quietly. I'd like the interrupt to show anytime above 800, but it's currently only showing up intermittently.
Ok, I figured it out. I tested what my amplitude was by logging the amp along with the interrupt and I saw I was getting 0. I realized I had been testing on a new amplitude (other than the amp I was showing), so I assigned the amplitude to a variable and used the variable everywhere else.
here's my outcome:
handler = new Handler();
final Runnable r = new Runnable() {
#Override
public void run() {
mAmplitude = mSoundMeter.getAmplitude();
mic_amplitude.setText(""+mAmplitude);
if(mAmplitude > threshold) {
Log.d("INTERRUPT", "INTERRUPT " + mAmplitude);
interrupt.setText("Interrupt");
mBackgroundAudio.pause(mp);
audioButton.setText("Resume Audio");
} else {
interrupt.setText("");
}
handler.postDelayed(this, 100);
}
};
handler.postDelayed(r, 100);
Im programming a Music Player with a Seekbar.To manage it im using an Handler with a Runnable which does it update. Somehow it is lagging my UI . How can I stop this lag ?
OnCreate:
mHandler = new Handler();
When Im playing a Song:
public static void updateProgressBar() {
mHandler.postDelayed(mUpdateTimeTask, 100);
}
My Runnable:
private static Runnable mUpdateTimeTask = new Runnable() {
public void run() {
try {
long totalDuration = songService.getTotalDuration();
int currentDuration = songService.getCurrentDuration();
// Displaying Total Duration time
player_time_length.setText(""+utils.milliSecondsToTimer(totalDuration));
// Displaying time completed playing
player_time_current.setText(""+utils.milliSecondsToTimer(currentDuration));
// Updating progress bar
int progress = (int)(utils.getProgressPercentage(currentDuration, totalDuration));
SB_song.setProgress(currentDuration);
// Running this thread after 100 milliseconds
mHandler.postDelayed(this, 100);
}catch(Exception e){}
}
};
How can I prevent this lagg in my App ?
The lag is because the Runnable is executing in the UI Thread. To reduce or remove the lag, you have to reduce the amount of work you do inside the Runnable.
One thing that you can do is to remove long totalDuration = songService.getTotalDuration(); from the Runnable and instead, put it outside, like I do in my music player.
I could add more to this answer if you included the "utils" methods that you're using to convert milliseconds to human readable time.
I would say its because its running on the UI Thread which laggs. But you have to use UI elements so another Thread is not possible .. Im right ?
Make sure the text view is not set to wrap_content. That will trigger a layout pass each time you call setText.
So I have seven panels of different colors that need to be rotated in order. My code is working well for the most part but the first and last panels always have same color. How do I fix this?
I have already checked that each of my panels have a different color upon start.
Code:
public void run()
{
TimerTask colorAction = new TimerTask(){
public void run()
{
redPanel.setBackground(orangePanel.getBackground());
orangePanel.setBackground(yellowPanel.getBackground());
yellowPanel.setBackground(greenPanel.getBackground());
greenPanel.setBackground(bluePanel.getBackground());
bluePanel.setBackground(indigoPanel.getBackground());
indigoPanel.setBackground(violetPanel.getBackground());
violetPanel.setBackground(redPanel.getBackground());
}
};
java.util.Timer utilTimer = new java.util.Timer();
utilTimer.scheduleAtFixedRate(colorAction, START_AFTER, DELAY );
}
Snapshot (before change):
Snapshot (after change)
The basic problem, other then the fact that you are violating the single thread rules of Swing, is you are relying on a value from component whose background has already changed...
violetPanel.setBackground(redPanel.getBackground());
redPanel's background is now set to orangePanel background by the time you call this.
Instead, first grab redPanel's background color before you change anything, then apply it to violetPanel
Color redBackground = redPanel.getBackground();
redPanel.setBackground(orangePanel.getBackground());
//...
violetPanel.setBackground(redBackground);
Take a look (and get your teacher to do the same) at Concurrency in Swing and How to Use Swing Timers for more details...
If you MUST use a java.util.Timer, you should be wrapping your changes to UI in an invokeLater call, for example...
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Color redBackground = redPanel.getBackground();
redPanel.setBackground(orangePanel.getBackground());
//...
violetPanel.setBackground(redBackground);
}
});
I'm trying to set the background color of an ImageView over a specified amount of time (pauses in between each switch of color) based on a certain criteria (such as string = "a" or "b"). My problem is I can't seem to make the entire application wait for one change until it moves on to the next, so to the user it just looks like the color starts then is immediately the last color.
I've tried CountDownTimers (which just continue to execute while the other timer is running), handlers (postDelayed(null, 5000)), Thread.Sleeps etc.
Here's an example of what I'm trying to do:
Set color on ImageView to red
sleep for 500ms
for(int i = 0; i < stringArray.length; i++){
if(stringCompare = "a") {
Set color on ImageView to blue
sleep for 500ms }
else if(stringCompare = "b") {
Set color on ImageView to blue
sleep for 1000ms
}
Set color on ImageView to red
sleep for 500ms
}
I know that's kind of crazy, but I've tried all the above methods I could think of with no success on actually making the program wait, but not completely stopping it.
Thanks.
For the past two decades, most GUIs have been based on event-driven models. Some, such as Android, use a single-threaded event-driven model. Your request to change a color does not take immediate effect; rather, it puts a message on a work queue, to be processed by the main application thread. That main application thread is also what is running your code, unless you specifically move that code to a background thread.
As a result, never sleep on the main application thread. Otherwise, that thread is tied up and cannot process any sort of GUI events, let alone your request to change the color.
postDelayed() (on View or Handler) will allow you to schedule code to be run in the future on the main application thread, after a delay, without tying up the main application thread during that delay.
Put this in your activity (make it an inner class):
private class ColorChanger extends Thread
{
final ImageView imageView;
public ColorChanger(ImageView imageView)
{
this.imageView = imageView;
}
private void changeColor(final int color)
{
YourActivity.this.runOnUiThread(new Runnable() {
public void run() {
// Set imageView to color here
}
});
}
#Override
public void run() {
try {
Thread.sleep(500);
changeColor(0xffff0000);
Thread.sleep(500);
changeColor(0xff0000ff);
// Etc.
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Then you can just do:
new ColorChanger(/*Your imageview*/).start();
Anywhere in your activity.
Create a separate thread that does your pause and run. From that thread post messages to the UI thread to change the color of its imageview.