BitWar
BitWar

Reputation: 43

Running things outside UI Thread

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

Answers (3)

Raghunandan
Raghunandan

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.

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

                    }
                });

            }
 }

Upvotes: 2

Martin O'Shea
Martin O'Shea

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

cYrixmorten
cYrixmorten

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

Related Questions