Reputation: 43
I am trying to create a simple Android stopwatch application. I was having trouble with the application freezing every time I would hit the start button. I learned from reading various things online that the reason it hangs is that I ran a while loop in the UI thread and in order for the application not to crash, that while loop had to be somewhere different. A post on the XDA forums suggested that someone encountering this problem should use an AsyncTask to accomplish this. I am having trouble understanding exactly how to use AsyncTask to do this.
TL;DR: I am trying to count time and then have it update a textview with the corresponding time
Original code with while loop in UI thread
import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity
{
Button start, stop, reset;
TextView time;
boolean timeStopped = false;
long timeInNanoSeconds, startTimeInNanoSeconds;
double timer;
public double getTimeInSeconds()
{
timeInNanoSeconds = System.nanoTime() - startTimeInNanoSeconds;
double timeSeconds = (double) timeInNanoSeconds / 1000000000.0;
double roundOff = Math.round(timeSeconds * 100.0) / 100.0;
return roundOff;
}
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start = (Button) findViewById(R.id.startButton);
stop = (Button) findViewById(R.id.stopButton);
reset = (Button) findViewById(R.id.resetButton);
time = (TextView) findViewById(R.id.timeField);
start.setOnClickListener(new View.OnClickListener()
{
public void onClick(View arg0)
{
startTimeInNanoSeconds = System.nanoTime();
while(timeStopped == false)
{
double timer = getTimeInSeconds();
String stringTimer = Double.toString(timer);
CharSequence sequenceTimer = stringTimer;
time.setText(sequenceTimer);
}
}
});
stop.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
}
});
reset.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
time.setText("");
}
});
}
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
EDIT: Working version using Handler
import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
Button start, stop, reset;
TextView time;
Handler m_handler;
Runnable m_handlerTask;
int timeleft = 0;
boolean timeStopped;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start = (Button) findViewById(R.id.buttonStart);
stop = (Button) findViewById(R.id.buttonStop);
reset = (Button) findViewById(R.id.buttonReset);
time = (TextView) findViewById(R.id.textTime);
start.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
timeStopped = false;
m_handler = new Handler();
m_handlerTask = new Runnable()
{
public void run() {
if(timeStopped == false){
if(timeleft > -1) {
Log.i("timeleft","" + timeleft);
time.setText(String.valueOf(timeleft));
timeleft++;
}
else{
m_handler.removeCallbacks(m_handlerTask);
}
}
m_handler.postDelayed(m_handlerTask, 1000);
}
};
m_handlerTask.run();
}
});
stop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
timeStopped = true;
m_handler.removeCallbacks(m_handlerTask);
}
});
reset.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
timeStopped = true;
m_handler.removeCallbacks(m_handlerTask);
timeleft = 0;
time.setText(String.valueOf(timeleft));
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
Upvotes: 0
Views: 2803
Reputation: 133560
doInbackground
is invoked on the background thread. you cannot update ui from a background
time.setText(sequenceTimer);
// should be in a ui thread.
Use runOnUithread
or setText in onPostExecute
.
You can use a Handler , a timer task or a CountDowntimer depending on your requirement.
Edit:
Using Handler
public class MainActivity extends Activity
{
Button start;
TextView time;
Handler m_handler;
Runnable m_handlerTask ;
int timeleft=100;
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start = (Button) findViewById(R.id.button1);
time = (TextView)findViewById(R.id.textView1);
start.setOnClickListener(new View.OnClickListener()
{
public void onClick(View arg0)
{
m_handler = new Handler();
m_handlerTask = new Runnable()
{
@Override
public void run() {
if(timeleft>=0)
{
// do stuff
Log.i("timeleft",""+timeleft);
time.setText(String.valueOf(timeleft));
timeleft--;
}
else
{
m_handler.removeCallbacks(m_handlerTask); // cancel run
}
m_handler.postDelayed(m_handlerTask, 1000);
}
};
m_handlerTask.run();
}
});
}
}
Upvotes: 2
Reputation: 432
I had to do a similar task lately I used created a separate thread with the following code. It lets you update at set time intervals which I think would be suited to your task.
Hope it helps.
import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
public class AutomationTreadClass {
Activity refToUIActivity;
//Declare the timer
Timer t = new Timer();
//pass UI activity so you can call update on it
AutomationTreadClass( Activity callingActivity ){
refToUIActivity = callingActivity;
startTimerTread();
}
private void startTimerTread(){
//Set the schedule function and rate
t.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
//do any updates to the time you need to do here
updateLevelMeter();
}
},
//Start Time of thread
0,
//interval of updates
30);
}
private void updateLevelMeter() {
refToUIActivity.runOnUiThread(new Runnable() {
public void run() {
//access what ever UI comment you need to here. like giving you textview a value.
}
});
}
}
Upvotes: 1
Reputation: 7108
In my opinion AsyncTask is not fit for you, as this in my mind is a single shot action.
I would suggest something like this:
private ScheduledExecutorService exec;
private void startExec() {
shutDownExec();
exec = Executors.newSingleThreadScheduledExecutor();
exec.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
// this starts immediately and is run once every minute
double timer = getTimeInSeconds();
String stringTimer = Double.toString(timer);
CharSequence sequenceTimer = stringTimer;
runOnUiThread(new UpdateUI(R.id.yourtime_textview, sequenceTimer));
}
}, 0, 1, TimeUnit.MINUTES); // adjust how often run() is executed here
}
private void shutDownExec() {
if (exec != null && !exec.isTerminated()) {
exec.shutdown();
}
}
private class UpdateUI implements Runnable {
private String mText;
private TextView mTv;
public UpdateUI(int textviewid, String text) {
this.mTv = (TextView) findViewById(textviewid);
this.mText = text;
}
@Override
public void run() {
mTv.setText(mText);
}
}
Upvotes: 2