0

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;
    }

}
BitWar
  • 43
  • 1
  • 2
  • 7

3 Answers3

2

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.

Android Thread for a timer

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();   

                    }
                });

            }
 }
Community
  • 1
  • 1
Raghunandan
  • 132,755
  • 26
  • 225
  • 256
  • Moving time.setText(sequenceTimer); to onPostExecute did not work. Going to try runOnUiThread and the handler in a second. – BitWar Dec 05 '13 at 14:38
  • @BitWar it will work. coz onPostExecute is invoked on the ui thread. But i doubt whether you really need asynctask – Raghunandan Dec 05 '13 at 14:39
  • Putting the loop in runInUiThread did not work and setText in post execute doesn't accomplish what I need to: constantly updating text with timer. – BitWar Dec 11 '13 at 14:26
  • @BitWar that is vague comment. Pls check the link in my post i am sure it will work. No need for asynctask – Raghunandan Dec 11 '13 at 14:26
  • Sorry for the vagueness. I attempted putting the time code in runInUiThread and it only set the text to "0.0". After adding back in the loop, it doesn't set the text to anything. – BitWar Dec 11 '13 at 14:37
  • @BitWar you are not understanding. Ok post your updated code. i will give you a proper answer – Raghunandan Dec 11 '13 at 14:38
  • @BitWar no wonder it does not work.all you have is runOnUithread. nothing that starts a timer – Raghunandan Dec 11 '13 at 14:45
  • @BitWar check the edit. count from 100 to 0 . to stop `m_handler.removeCallbacks(m_handlerTask);`. now customize how you want – Raghunandan Dec 11 '13 at 14:56
  • You're confusing me, nothing has been edited. The while loop is suppose to start the timer. Since the boolean is set to false, the timer is suppose to start. I've gotten it to work in just a normal java class. I don't know what you want me to do. – BitWar Dec 11 '13 at 18:17
  • @BitWar i am not confusing ( you got confused). your while loop is is Asynctask. after execute of doinbackground control goes to onPostExecute. So you don't see the text set to time. Also you can't update ui in DOinbackground. What i have used is a Handler. What is the problem in my code and what is that is so confusing?. If you still don't understand read asynctask documentation yourself. – Raghunandan Dec 11 '13 at 18:20
  • @BitWar button click code is on ui thread. so if you run a while loop there it runs on ui thread. so what is the need for runOnUiThread when it is already running on ui thread. You need to understand threads. So what do you want. A count down timer. a timertask or a handler. all can be used of counting – Raghunandan Dec 11 '13 at 18:24
  • @BitWar this `while(timeStopped = false)`(assignment) should be `while(timeStopped == false)` (comparing). this is also wrong. Now what is confusing? – Raghunandan Dec 11 '13 at 18:31
  • @BitWar don't worry. just try my code and see what happens. then come back and comment – Raghunandan Dec 11 '13 at 18:32
  • Sorry I'm not trying to offend you. What I am wanting is a timer to start when a button is clicked. I guess I am not understanding how I would start the timer if the while loop is not where it currently is. – BitWar Dec 11 '13 at 18:34
  • @BitWar why don'y you try what i have posted. and read bout handlers. The count starts from 100 to 0 and it stops at 0. – Raghunandan Dec 11 '13 at 18:38
  • I just tried your code and it works. Can it modified to count upwards instead of downwards? – BitWar Dec 11 '13 at 22:47
  • I modified it to count upwards and got it to stop and start. The only problem now is that it counts up more than one after it is stopped. – BitWar Dec 12 '13 at 00:43
  • Finally modified it to start, stop, and reset. Thank you so much! I'll update my post with what I have working. – BitWar Dec 12 '13 at 01:04
2

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);
    }

}
cYrixmorten
  • 7,110
  • 3
  • 25
  • 33
1

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.     


            }
        });
    }


}
Martin O'Shea
  • 432
  • 5
  • 15