I think there is something that I'm just not yet getting about how these Views are to be coded. This is the third time I have had to come and post a question rearding them in the last 3 days! :S
Anyway, my problem is as follows.
edit:
This is the code inside a method that gets executed on a button press:
(Points of importance are noted)
>> setContentView(R.layout.stop);
>> timer = (TextView) findViewById(R.id.timer2);
GPSMain.button = (Button) findViewById(R.id.stopbutton);
startService(new Intent(context, Timer.class));
}
This is the Timer class that is executed in the "startService" call:
(Again points of importance are noted)
public class Timer extends Service {
static int totalSeconds = 0;
private int hour = 0;
private int min = 0;
private int sec = 0;
String mTimeFormat = "%02d:%02d:%02d";
final private Handler mHandler = new Handler();
static String timeTaken;
Context context = this;
Runnable mUpdateTime = new Runnable() {
public void run() { updateTimeView(); }
};
#Override
public void onCreate() {
Toast.makeText(context, "Timer Started", Toast.LENGTH_LONG).show();
mHandler.postDelayed(mUpdateTime, 1000);
}
public void updateTimeView() {
totalSeconds += 1;
sec += 1;
if(sec >= 60) {
sec = 0;
min += 1;
if (min >= 60) {
min = 0;
hour += 1;
}
}
>> timeTaken = String.format(mTimeFormat, hour, min, sec);
>> GPSMain.timer.setText("Time Taken: "+timeTaken);
mHandler.postDelayed(mUpdateTime, 1000);
}
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
}
The logcat returns a NullPointerException on the following line of code:
GPSMain.timer.setText("Time Taken: "+timeTaken);
If I remove that line of code the code executes properly*, well I say properly because it executes all the way to the end of the application code, but the reason that I want to print the timer to the screen as it is counting is because I need to make sure that it is functioning correctly.
*not only does it run properly but it displays the timer text view with it's default defined string from the xml. It's only when I try to update it from the java that it crashes.
Here is the full xml file that the code is referencing at this point:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:id="#+id/stopbutton"
android:layout_width="100px"
android:layout_height="100px"
android:text="Stop"
android:layout_centerInParent="true"
/>
<TextView
android:id="#+id/timer2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Time Taken: unknown"
android:layout_below="#+id/timer"
/>
</RelativeLayout>
UPDATE!
The MyTimer Class:
#SuppressWarnings("unchecked")
public class MyTimer extends AsyncTask {
Timer _timerTask = new Timer();
static int totalSeconds = 0, hour = 0, min = 0, sec = 0;
static String mTimeFormat = "%02d:%02d:%02d";
static String timeTakenString;
#Override
protected Object doInBackground(Object... params) {
TimerTask timer = new TimerTask() {
#Override
public void run() {
totalSeconds += 1;
sec += 1;
if(sec >= 60) {
sec = 0;
min += 1;
if (min >= 60) {
min = 0;
hour += 1;
}
}
timeTakenString = String.format(mTimeFormat, hour, min, sec);
GPSMain.timer.setText("Time Taken: "+GPSMain.timeTaken);
}
};
(_timerTask).scheduleAtFixedRate(timer,1000,1000);
return null;
}
}
The method to start the timer thread:
void startService(){
setContentView(R.layout.stop);
timer = (TextView) findViewById(R.id.timer2);
GPSMain.button = (Button) findViewById(R.id.stopbutton);
new MyTimer().execute();
}
Why do you need a Service here? Why don't you just use AsyncTask? A Service is usually used when you need to run something in background, with no layout shown to user. Your application shows data to user, so the best way for you to solve your problem is to run a separate thread, not a service. Hope this helps.
You can use TimerTask instead of Service . Here is the code ..
TimerTask timer = new TimerTask() {
#Override
public void run() {
// Here you can update your UI
}
}
Timer _timerTask = new Timer();
_timerTask.scheduleAtFixedRate(timer,60000,60000);
Well, I seriously don't know much about services, but in all cases, you should be accessing UI only from the UI thread.
I your case, I feel the Activity that has the button and the Service are two different classes, with the service not possessing any object of the activity. I think the way you're setting the text view from a service may be an issue. but again, i don't know much about services.
On the other note, I agree with Egor, unless you have a lot of activities and each keeps doing a lot of network activity, AsyncTask is the way to go.
Related
What I've built is a timer with multiple timer styles, using a spinner so the user can switch quickly between the timer they want to use.
My problem is with the switch statement - each timer works fine on its own but when going (example) from the Basic Timer to the Countdown Timer via the spinner, the first timer (Basic Timer) continues being run alongside the newly selected timer. In this example, the bug displays as 1 second down 1 second up 1 second down 1 second up etc...
My questions is: Is there a command that can be used to "kill" a function that I no longer want to continue running? If not, is there a better way I could have organized the code so the switch statements will keep each timer process separate? I have tried a Boolean check with no luck and breaking up the timer running-specific code differently has not worked for me so far.
Please see the relevant code below:
package com.mtag.app.muaythaiathletesguide;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import java.util.Locale;
public class TimerActivity extends Activity implements AdapterView.OnItemSelectedListener {
private int seconds = 0; // Number of seconds passed
private boolean running; // Check whether timer is running
private boolean wasRunning;
private int timeCap = 0; // Custom max time, stop timer when reached and reset here for countdown
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_timer);
// Timer Selection Spinner
Spinner spinner = (Spinner) findViewById(R.id.timer_spinner);
// Create an ArrayAdapter using the string array and a default spinner layout
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
R.array.timer_spinner, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// Apply the adapter to the spinner
spinner.setAdapter(adapter);
// Spinner click listener
spinner.setOnItemSelectedListener(this);
// Restore activity's state by getting values from Bundle
if (savedInstanceState != null && running) {
seconds = savedInstanceState.getInt("seconds");
running = savedInstanceState.getBoolean("running");
wasRunning = savedInstanceState.getBoolean("wasRunning");
}
}
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id){
String selection = parent.getItemAtPosition(pos).toString();
// TODO: Remove Toast outputs after testing
// Call Timer types when corresponding position is chosen
switch(pos) {
case 0: // Basic Stopwatch: Count from 0:00:00 to 99:59:59 (or cap)
onDestroy();
running = false; // Stop clock
seconds = 0; // Reset seconds to zero
timeCap = seconds; // Set time cap to match seconds on the clock, for reset point
runBasicTimer();
break;
case 1: // Countdown: Count from 99:59:59 (or cap) to 0:00:00
Toast.makeText(parent.getContext(), "Selected: " + selection, Toast.LENGTH_LONG).show();
onDestroy();
running = false;
seconds = 1200; // Default cap 20:00:00
timeCap = seconds;
runCountdownTimer();
break;
case 2: // Tabata: Beep every 20th and 30th second. Reset to 0:00:00 on each 30th second
Toast.makeText(parent.getContext(), "Selected: " + selection, Toast.LENGTH_LONG).show();
running = false;
seconds = 0;
runTabataTimer();
break;
case 3: // Fight Gone Bad: 17min cap, beep on each minute
Toast.makeText(parent.getContext(), "Selected: " + selection, Toast.LENGTH_LONG).show();
running = false;
seconds = 0;
runFGBTimer();
break;
case 4: // "3 On 1 Off": Beep every 3rd and 4th minute
Toast.makeText(parent.getContext(), "Selected: " + selection, Toast.LENGTH_LONG).show();
running = false;
seconds = 0;
runThreeOneTimer();
break;
case 5: // "5 On 1 Off": Beep every 5th and 6th minute
Toast.makeText(parent.getContext(), "Selected: " + selection, Toast.LENGTH_LONG).show();
running = false;
seconds = 0;
runFiveOneTimer();
break;
default:
running = false;
seconds = 0;
Toast.makeText(parent.getContext(), "Error", Toast.LENGTH_LONG).show();
break;
}
}
public void onNothingSelected(AdapterView<?> parent){
// Another interface callback
}
#Override
// Save the state of variables
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putInt("seconds", seconds);
savedInstanceState.putBoolean("running", running);
savedInstanceState.putBoolean("wasRunning", wasRunning);
}
#Override
protected void onResume() {
super.onResume();
// If the stopwatch was running at stop, set it running again
if (wasRunning)
running = true;
}
#Override
protected void onPause() {
super.onPause();
// Record state of stopwatch, running or not running
wasRunning = running;
running = false;
}
public void onClickStart(View view) {
running = true; // Start stopwatch
}
public void onClickStop(View view) {
running = false; // Stop stopwatch
}
public void onClickReset(View view) {
seconds = timeCap; // Reset seconds to zero
}
private void runBasicTimer() {
final TextView timeView = (TextView)findViewById(R.id.time_view);
final Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
int secs = seconds % 60;
// Format time to hours, minutes, and seconds
String time = String.format(Locale.getDefault(), "%d:%02d:%02d", hours, minutes, secs);
timeView.setText(time);
if (running) {
seconds++;
}
// Don't allow timer to go over 99:59:59
if (seconds >= 359999) {
running = false;
Toast.makeText(getApplicationContext(), "Maximum time reached", Toast.LENGTH_LONG).show();
}
// Post code again with delay of one second
handler.postDelayed(this, 1000);
}
});
}
private void runCountdownTimer() {
final TextView timeView = (TextView)findViewById(R.id.time_view);
final Handler handler = new Handler();
handler.post(new Runnable() {
#Override
public void run() {
int hours = seconds / 3600;
int minutes = (seconds % 3600) / 60;
int secs = seconds % 60;
// Format time to hours, minutes, and seconds
String time = String.format(Locale.getDefault(), "%d:%02d:%02d", hours, minutes, secs);
timeView.setText(time);
if (running) {
seconds--;
}
// Don't allow timer to go under 0:00:00
if (seconds <= 1) {
running = false;
Toast.makeText(getApplicationContext(), "Maximum time reached", Toast.LENGTH_LONG).show();
}
// Post code again with delay of one second
handler.postDelayed(this, 1000);
}
});
}
private void runTabataTimer() {
}
private void runFGBTimer() {
}
private void runThreeOneTimer() {
}
private void runFiveOneTimer() {
}
}
And here's the .xml file:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:background="#color/colorWhite"
tools:context="com.mtag.app.muaythaiathletesguide.TimerActivity">
<TextView
android:id="#+id/timer_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="#string/timer_style"/>
<Spinner
android:id="#+id/timer_spinner"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="20dp"
android:background="#color/colorLightGrey"
android:minHeight="40dp" />
<TextView
android:id="#+id/time_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textAppearance="#android:style/TextAppearance.Large"
android:textSize="90sp" />
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="horizontal"
android:padding="16dp">
<Button
android:id="#+id/start_button"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="#color/colorTimerGreen"
android:onClick="onClickStart"
android:text="#string/start" />
<Button
android:id="#+id/stop_button"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="8dp"
android:background="#color/colorTimerRed"
android:onClick="onClickStop"
android:text="#string/stop" />
<Button
android:id="#+id/reset_button"
android:layout_width="90dp"
android:layout_height="50dp"
android:layout_marginTop="8dp"
android:onClick="onClickReset"
android:text="#string/reset" />
</LinearLayout>
</GridLayout>
Your timer/countdown is running on another thread. That is okay by itself but comes with provisions that you have missed. For example, you are trying to set running and seconds when a new item is selected. Those two variables exist in the context of your UI thread. When they are used in the Runnable they are simply put, copied as final variables and used by the new thread. This happens when you reference a variable inside an anonymous class. This causes the Runnable to not see the variables you are changing because they are not the same anymore.
Now how to fix this?
Instead of passing an anonymous Runnable, you should hold a reference to it. Right now, you are creating a timer and just letting it go. That makes it very hard to control it as you can see.
Runnable activeTimer;
private void runBasicTimer() {
//... first remove any other activeTimer if exists and make sure it is cancelled properly (see below)
activeTimer = new Runnable() {//Your code}
handler.post(activeTimer);
}
This way you can always remove the activeTimer from the Handler like this.
This is still not complete. Because you also repost the runnable regularly. We also want to cancel that as well so that it is not reposted to the handler. To do this you should extend the Runnable class so that you can change running in it.
public class TimerRunnable extends Runnable {
public boolean mRunning = true;
#Override
public void run() {
//your code
if (mRunning) {
handler.postDelayed(this, 1000);
}
}
}
Now when you want to create a new timer:
activeTimer = new TimerRunnable(); //instead of just new Runnable()
Perfect! Now you can easily cancel it because you have access to its thread and you have access to the thread's mRunning variable. In your onItemSelected instead of just changing running you can do this:
if (activeTimer != null) {
activeTimer.mRunning = false;
}
How can i optimize my stopwatch in my app to use less cpu?
I am running a stopwatch using an asynctask class in the mainActivity. The doInBackground() method increments the values for the hours, minutes, seconds and centiseconds(10th of a second). The onProgressUpdate() method is responsible for updating 4 imageViews that display the hrs, mins, sec, centisec.
The problem i have is that the stopwatch uses about on average 50%+ cpu usage according to android studio(50% user and 30% kernel usage) and a cpu monitoring app that i installed on the device (2013 HTC one m7). The default android operating system stopwatch uses only about 10% cpu usage. If i use textViews instead of image views the cpu usage drop to half (less than 25%). But it is still more than 10% and i also i want to keep the style of digits im using.
Would caching the images help in anyway? source
I have also considered using XML drawables for the digits instead of bitmaps, but i don't know how effective this will be or if its even possible to create xml drawables of the digits
Lend me your knowledge stackoverflow
main XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#763768"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:context="com.example.aroboius.stopwatch.MainActivity"
tools:showIn="#layout/activity_main">
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="#+id/hoursImage"
android:layout_marginRight="20dp"
android:src="#drawable/digit00" />
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="#+id/minutesImage"
android:layout_marginRight="20dp"
android:src="#drawable/digit00" />
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="#+id/secondsImage"
android:layout_marginRight="20dp"
android:src="#drawable/digit00" />
<ImageView
android:layout_width="50dp"
android:layout_height="50dp"
android:id="#+id/centiSecondsImage"
android:src="#drawable/digit00" />
</LinearLayout>
MainActivity
public class MainActivity extends AppCompatActivity {
ImageView hoursIMG, minutesIMG, secondsIMG, centiSecondsIMG;
TextView hoursText, minutesText, secondsText, centicsecondsText;
int centiseconds, seconds, minutes, hours ;
long startMS , endMS , elapsed ;
boolean timerRunning;
String [] digit = {"digit00","digit01","digit02","digit03","digit04","digit05","digit06","digit07","digit08","digit09", "digit10", "digit11","digit12","digit13","digit14","digit15","digit16","digit17","digit18","digit19","digit20", "digit21","digit22","digit23","digit24","digit25","digit26","digit27","digit28","digit29","digit30","digit31",
"digit32","digit33","digit34","digit35","digit36","digit37","digit38","digit39","digit40","digit41","digit42","digit43","digit44","digit45","digit46","digit47","digit48","digit49","digit50","digit51","digit52","digit53",
"digit54","digit55","digit56","digit57","digit58","digit59","digit60","digit61","digit62","digit63","digit64","digit65","digit66","digit67","digit68","digit69","digit70","digit71","digit72","digit73","digit74","digit75",
"digit76","digit77","digit78","digit79","digit80","digit81","digit82","digit83","digit84","digit85","digit86","digit87","digit88","digit89", "digit90","digit91","digit92","digit93","digit94","digit95","digit96","digit97","digit98","digit99"} ;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
//initializing values
centiseconds = 0; seconds = 0; minutes = 0; hours = 0;
startMS = 0; endMS = 0; elapsed = 0;
hoursIMG = (ImageView) findViewById(R.id.hoursImage);
minutesIMG = (ImageView) findViewById(R.id.minutesImage);
secondsIMG = (ImageView) findViewById(R.id.secondsImage);
centiSecondsIMG = (ImageView) findViewById(R.id.centiSecondsImage);
//start asynctask/stopwatch
timerRunning = true; new asyncTask().execute();
}
class asyncTask extends AsyncTask<Void, Void, Void> {
//initialize a variable to the current system time
#Override
protected void onPreExecute() {
startMS = System.currentTimeMillis();
}
#Override
protected Void doInBackground(Void... params) {
//timerRunning a varible to stop/start the timer
while (timerRunning) {
//initialize a 2nd variable to the current system time
endMS = System.currentTimeMillis();
//get the difference between the 2 time variables
elapsed = endMS - startMS;
//once it is greater than or equal to 100ms increment the centis, mins, secs, hrs
if (elapsed >= 100) {
//reset the starting variable to repeat the process. it also compensating if elapses is greater than 100ms
startMS = endMS - (elapsed - 100);
centiseconds++;
if (centiseconds > 9) {
centiseconds = 0;
seconds++;
if (seconds > 59) {
seconds = 0;
minutes++;
if (minutes > 59) {
minutes = 0;
hours++;
}
}
}
//call method to update the images
publishProgress();
}
}
return null;
}
#Override
protected void onProgressUpdate(Void... values) {
//get resource IDs for images that represent the values of hrs, mins, secs using the string array created earlier
int hourResID = getResources().getIdentifier(digit[hours], "drawable", getPackageName());
int minResID= getResources().getIdentifier(digit[minutes], "drawable", getPackageName());
int secResID= getResources().getIdentifier(digit [seconds], "drawable", getPackageName());
int csecResID= getResources().getIdentifier(digit[centiseconds], "drawable", getPackageName());
//set images of imageViews
centiSecondsIMG.setImageResource(csecResID);
secondsIMG.setImageResource(secResID);
minutesIMG.setImageResource(minResID);
hoursIMG.setImageResource(hourResID);
}
}
}
1:
Instead of burning your CPU and battery with a loop, you should use a CountDownTimer
new CountDownTimer(30000, 1000) { // 30sec, tick each second
public void onTick(long millisUntilFinished) {
publishProgress();
// mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
}
public void onFinish() {
mTextField.setText("done!");
}
}.start();
Or you can use a Runnable that you delay every time by the desired amount of time, let's say 200ms:
final static long REFRESH_RATE = 200L;
Handler mHandler = new Handler();
private final Runnable mRunnable = new Runnable() {
#Override
public void run() {
if (mStarted) {
long seconds = (System.currentTimeMillis() - t) / 1000;
statusBar.setText(String.format("%02d:%02d", seconds / 60, seconds % 60));
// cancel previous messages if they exist
handler.removeCallbacks(mRunnable);
handler.postDelayed(runnable, REFRESH_RATE);
}
}
};
start it:
mHandler.postDealyed(runnable, 0);
You can use a Timer with fixed rate:
new Timer().scheduleAtFixedRate(new TimerTask(){
#Override
public void run(){
publishProgress();
}
},0,1000);
Or you can use a ScheduledExecutorService, that will fix most of the problems you mentioned. See here and here.
ScheduledExecutorService scheduledExecutorService =
Executors.newScheduledThreadPool(1);
long lastSecondDisplayed = 0;
ScheduledFuture scheduledFuture =
scheduledExecutorService.schedule(new Callable() {
public Object call() throws Exception {
long now = System.currentTimeMillis() / 1000;
// add this optimisation, so you don't calculate and
// for sure don't refresh your UI (even slower)
// if it's not needed:
if (lastSecondDisplayed != now) {
lastSecondDisplayed = now;
// calculate whatever you want
publishProgress();
}
return "Called!";
}
}, 1, TimeUnit.SECONDS);
Optimisations:
move the 4 getResources().getIdentifier(... lines out of onProgressUpdate and prepare the 10 digits only once in onCreate.
It is always good to reuse resources in java, because when you're creating and disposing them frequently, like here, you'll finish your memory quite fast and the GC will have to free some memory for you. Both creating the objects, and especially garbage-collecting them takes a fair amount of time. By creating them only once and reusing them you keep yourself far from all this trouble.
It seems it was a problem with continuously resetting the imageViews to different drawables. The getResources().getIdentifier() function calls also somewhat contributed to extra cpu usage and GarbageCleaner(GC) problems.
Instead of creating an image resource array I created a drawable array that I can continually reference. I created it in onCreate().
final Drawable[] drawable = {ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit00),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit01),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit02),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit03),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit04),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit05),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit06),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit07),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit08),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit09),
ContextCompat.getDrawable(getApplicationContext(), R.drawable.digit10)}
Then I set the images on the imageViews using the Drawable
MainActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
centiSecondsIMG.setImageDrawable(drawable[centiseconds]);
secondsIMG.setImageDrawable(drawable[seconds]);
minutesIMG.setImageDrawable(drawable[minutes]);
hoursIMG.setImageDrawable(drawable[hours]);
}
});
Memory and cpu are now all perfectly fine and working normally.
i sitll dont know why changing the imageViews images rapidly using setImageResource() caused problems with cpu,memory and GC.
First of all, I could not even chose the method to use, i'm reading for hours now and someone says use 'Handlers', someone says use 'Timer'. Here's what I try to achieve:
At preferences, theres a setting(checkbox) which to enable / disable the repeating job. As that checkbox is checked, the timer should start to work and the thread should be executed every x seconds. As checkbox is unchecked, timer should stop.
Here's my code:
Checking whether if checkbox is checked or not, if checked 'refreshAllServers' void will be executed which does the job with timer.
boolean CheckboxPreference = prefs.getBoolean("checkboxPref", true);
if(CheckboxPreference == true) {
Main main = new Main();
main.refreshAllServers("start");
} else {
Main main = new Main();
main.refreshAllServers("stop");
}
The refreshAllServers void that does the timer job:
public void refreshAllServers(String start) {
if(start == "start") {
// Start the timer which will repeatingly execute the thread
} else {
// stop the timer
}
And here's how I execute my thread: (Works well without timer)
Thread myThread = new MyThread(-5);
myThread.start();
What I tried?
I tried any example I could see from Google (handlers, timer) none of them worked, I managed to start the timer once but stoping it did not work.
The simpliest & understandable code I saw in my research was this:
new java.util.Timer().schedule(
new java.util.TimerTask() {
#Override
public void run() {
// your code here
}
},
5000
);
Just simply use below snippet
private final Handler handler = new Handler();
private Runnable runnable = new Runnable() {
public void run() {
//
// Do the stuff
//
handler.postDelayed(this, 1000);
}
};
runnable.run();
To stop it use
handler.removeCallbacks(runnable);
Should do the trick.
Use a CountDownTimer. The way it works is it will call a method on each tick of the timer, and another method when the timer ends. At which point you can restart if needed. Also I think you should probably be kicking off AsyncTask rather than threads. Please don't try to manage your own threads in Android. Try as below. Its runs like a clock.
CountDownTimer myCountdownTimer = new CountDownTimer(30000, 1000) {
public void onTick(long millisUntilFinished) {
mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
// Kick off your AsyncTask here.
}
public void onFinish() {
mTextField.setText("done!");
// the 30 seconds is up now so do make any checks you need here.
}
}.start();
I would think to use AlarmManager http://developer.android.com/reference/android/app/AlarmManager.html
If checkbox is on call method where
AlarmManager alarmManager = (AlarmManager)SecureDocApplication.getContext()
.getSystemService(Context.ALARM_SERVICE);
PendingIntent myService = PendingIntent.getService(context, 0,
new Intent(context, MyService.class), 0);
long triggerAtTime = 1000;
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, triggerAtTime, 5000 /* 5 sec*/,
myService);
If checkbox is off cancel alarm manager
alarmManager.cancel(myService);
"[ScheduledThreadPoolExecutor] class is preferable to Timer when multiple worker threads are needed, or when the additional flexibility or capabilities of ThreadPoolExecutor (which this class extends) are required."
per...
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledThreadPoolExecutor.html
It's not much more than the handler, but has the option of running exactly every so often (vice a delay after each computation completion).
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
...
final int THREAD_POOL_SIZE = 10;
final int START_DELAY = 0;
final int TIME_PERIOD = 5;
final TimeUnit TIME_UNIT = TimeUnit.SECONDS;
ScheduledThreadPoolExecutor pool;
Runnable myPeriodicThread = new Runnable() {
#Override
public void run() {
refreshAllServers();
}
};
public void startTimer(){
pool.scheduleAtFixedRate(myPeriodicThread,
START_DELAY,
TIME_PERIOD,
TIME_UNIT);
}
public void stopTimer(){
pool.shutdownNow();
}
Thanks to everyone, I fixed this issue with using Timer.
timer = new Timer();
timer.scheduleAtFixedRate(
new java.util.TimerTask() {
#Override
public void run() {
for (int i = 0; i < server_amount; i++) {
servers[i] = "Updating...";
handler.sendEmptyMessage(0);
new MyThread(i).start();
}
}
},
2000, 5000);
I've made a simple Android music player. I want to have a TextView that shows the current time in the song in minutes:seconds format. So the first thing I tried was to make the activity Runnable and put this in run():
int position = 0;
while (MPService.getMP() != null && position<MPService.duration) {
try {
Thread.sleep(1000);
position = MPService.getSongPosition();
} catch (InterruptedException e) {
return;
}
// ... convert position to formatted minutes:seconds string ...
currentTime.setText(time); // currentTime = (TextView) findViewById(R.id.current_time);
But that fails because I can only touch a TextView in the thread where it was created. So then I tried using runOnUiThread(), but that doesn't work because then Thread.sleep(1000) is called repeatedly on the main thread, so the activity just hangs at a blank screen. So any ideas how I can solve this?
new code:
private int startTime = 0;
private Handler timeHandler = new Handler();
private Runnable updateTime = new Runnable() {
public void run() {
final int start = startTime;
int millis = appService.getSongPosition() - start;
int seconds = (int) ((millis / 1000) % 60);
int minutes = (int) ((millis / 1000) / 60);
Log.d("seconds",Integer.toString(seconds)); // no problem here
if (seconds < 10) {
// this is hit, yet the text never changes from the original value of 0:00
currentTime.setText(String.format("%d:0%d",minutes,seconds));
} else {
currentTime.setText(String.format("%d:%d",minutes,seconds));
}
timeHandler.postAtTime(this,(((minutes*60)+seconds+1)*1000));
}
};
private ServiceConnection onService = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder rawBinder) {
appService = ((MPService.LocalBinder)rawBinder).getService();
// start playing the song, etc.
if (startTime == 0) {
startTime = appService.getSongPosition();
timeHandler.removeCallbacks(updateTime);
timeHandler.postDelayed(updateTime,1000);
}
}
what about this:
int delay = 5000; // delay for 5 sec.
int period = 1000; // repeat every sec.
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask()
{
public void run()
{
//your code
}
}, delay, period);
Use a Timer for this (instead of a while loop with a Thread.Sleep in it). See this article for an example of how to use a timer to update a UI element periodically:
Updating the UI from a timer
Edit: updated way-back link, thanks to Arialdo: http://web.archive.org/web/20100126090836/http://developer.android.com/intl/zh-TW/resources/articles/timed-ui-updates.html
Edit 2: non way-back link, thanks to gatoatigrado: http://android-developers.blogspot.com/2007/11/stitch-in-time.html
You have to use a handler to handle the interaction with the GUI. Specifically a thread cannot touch ANYTHING on the main thread. You do something in a thread and if you NEED something to be changed in your main thread, then you call a handler and do it there.
Specifically it would look something like this:
Thread t = new Thread(new Runnable(){
... do stuff here
Handler.postMessage();
}
Then somewhere else in your code, you do
Handler h = new Handler(){
something something...
modify ui element here
}
Idea its like this, thread does something, notifies the handler, the handler then takes this message and does something like update a textview on the UI thread.
This is one more Timer example and I'm using this code in my project.
https://stackoverflow.com/a/18028882/1265456
I think the below blog article clearly gives a very nice solution. Especially, if you are a background service and want to regularly update your UI from this service using a timer-like functionality.
It really helped me, much more than the 2007 blog link posted by MusiGenesis above.
https://www.websmithing.com/2011/02/01/how-to-update-the-ui-in-an-android-activity-using-data-from-a-background-service/
I am trying to have a counter (count seconds and minutes) and update it on the display every second.
I have this code in the onCreate of my class, which extends Activity:
timeOnCall = (TextView) findViewById(R.id.time);
minutes = seconds = 0;
timeOnCall.setText(minutes + ":" + seconds);
// Implements the timer
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
++seconds;
if (seconds == 60) {
seconds = 0;
++minutes;
}
// Display the new time
timeOnCall.setText(minutes + ":" + seconds);
}
}, 1000, 1000);
Unfortunately, I get the following error:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
I am not sure how to fix this as it's already in the onCreate() method. Does anyone know a solution?
You can do it with a handler, something lite this:
final Handler mHandler = new Handler();
final Runnable updateText = new Runnable() {
public void run() {
timeOnCall.setText(minutes + ":" + seconds);
}
};
in onCreate you can then run:
onCreate(Bundle b) {
...
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
++seconds;
if (seconds == 60) {
seconds = 0;
++minutes;
}
// Display the new time
mHandler.post(updateText);
}
}, 1000, 1000);
}
Its because your trying to change the textview from inside a different thread. You can't do that. You need to post a message back to the thread that owns the textview.
public void run()
This starts a new thread that is separate from what is running your UI.
Edit: There are tons of examples online for the code you are looking for. Just Google something like "Android thread message handler."
Here is a complete step-by-step of what you are trying to do and doing it without a background thread. This is preferred over a timer because a timer uses a separate thread to do the update.
http://developer.android.com/resources/articles/timed-ui-updates.html