Android: TimerTask scheduled for repetition getting fired only once
Asked Answered
H

2

5

Ok this is a very weird problem I am having, and I'm pretty sure that I am messing up somewhere, but I can't quite figure out where.

What I am trying is -

  • Schedule a Timer to execute a TimerTask every five seconds
  • The TimerTask in turn executes an AsyncTask (which in this case simple sleeps for a second before returning the static count of the number of AsyncTasks).
  • Finally, the aforementioned count is updated in the UI.

And of course, the appropriate Handlers and Runnables have been used to post asynchronous messages from other threads to the UI.

This code executes only once. I expect it to fire every 5 seconds. Here's the code.

Note: I had no idea what to do with the Looper. I put it there after trial and error!

public class TimerAsyncMixActivity extends Activity {
    public static final String TAG = "TimerAsyncMix";
    static int executionCount = 0;
    Handler mHandler = new Handler();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        new Timer().schedule(new MyTimerTask(this), 0, 5000);
    }

    class MyAsyncTask extends AsyncTask<String, Void, Integer>{
        @Override
        protected Integer doInBackground(String... params) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return ++executionCount;
        }

        @Override
        protected void onPostExecute(Integer result) {

            mHandler.post(new UpdateUiThread(TimerAsyncMixActivity.this, result));
            super.onPostExecute(result);
        }
    }
}



class MyTimerTask extends TimerTask{
    private TimerAsyncMixActivity tma;

    public MyTimerTask(TimerAsyncMixActivity tma) {
        this.tma = tma;
    }

    @Override
    public void run() {
        Looper.prepare();
        Log.d(TimerAsyncMixActivity.TAG, "Timer task fired");
        tma.new MyAsyncTask().execute();
        Looper.loop();
        Looper.myLooper().quit();
    }
}

class UpdateUiThread implements Runnable{

    int displayCount;
    TimerAsyncMixActivity tma;
    public UpdateUiThread(TimerAsyncMixActivity tma, int i) {
        this.displayCount = i;
        this.tma = tma;
    }

    @Override
    public void run() {
        TextView tv = (TextView) tma.findViewById(R.id.tvDisplay);
        tv.setText("Execution count is : "+displayCount);
    }

Can anyone point me to what I'm doing wrong?

Haim answered 11/7, 2011 at 15:16 Comment(2)
I checked the code a few times and I can't see anything wrong. Do you get any on logcat? As a side note, you could simplify this a lot by just running the AsyncTask (there is no need for the extra 2 threads).Ventura
@Augusto, I was getting some errors which prompted me to put those Looper-related statements in there. Well, this code is just a skeleton of my actual app. My intention is to fetch information using REST and update the UI (I use AsyncTask for this); and to do this every N seconds (I use TimerTask for this). I tried [this approach] (#6535324) - but it is fraught with OutOfMemory issues. Is there a better way to achieve this?Haim
V
5

techie, this is how I implemented similar things. I'm won't claim that this is the best way, but it has worked for me and doesn't look too bad.

I have the following code in my activity. I create an async task when the activity starts and I stop it onPause. The AsyncTask does whatever it needs to do, and updates the UI on onProgressUpdate() (which is run on the UI thread, so there's no need to use a Handler).

private Task task;
@Override
protected void onPause() {
    task.stop();
    task = null;
}

@Override
protected void onResume() {
    task = new Task();
    task.execute();
}

private class Task extends AsyncTask<Void, String, Void> {

    private boolean running = true;
    @Override
    protected Void doInBackground(Void... params) {
        while( running ) {
            //fetch data from server;
            this.publishProgress("updated json");
            Thread.sleep(5000); // removed try/catch for readability
        }

        return null;
    }

    @Override
    protected void onProgressUpdate(String... values) {
        if( ! running ) {
            return; 
        }
        String json = values[0];
        //update views directly, as this is run on the UI thread. 
        //textView.setText(json);
    }

    public void stop() {
        running = false;
    }
}
Ventura answered 11/7, 2011 at 21:41 Comment(4)
This definitely makes sense. I was using an AsyncTask execution for just a single REST call, but now that you mention it, using it to loop and continuously fetch data makes more sense. Thanks for the pointer. I will try it out and let you know.Haim
I tried out your solution and it works like a charm. It also frees me from the hassle of maintaining several threads, Handlers, Loopers and what not. Thanks a ton! I am accepting this answer.Haim
Techie, I forgot to add a guard on onProgressUpdate to check if the task has been stopped. I've updated the method onProgressUpdate with it.Ventura
I figured out that part. Thanks :)Haim
M
1

Do not use a timer. If your phone goes to sleep, the timer is suspended too. Use AlarmManager.

Maggs answered 12/7, 2011 at 8:36 Comment(2)
My app was meant to do its work only when it is active. In other words, I do want it to "pause" when the phone goes to sleep - so also when user switches to some other app.Haim
This is a great caveat to know about and consider in your design. @Pepi, any more info on how trustable the Timer is? We have experienced multiple issues with it, from the Timer not firing at its expected period to lots of timer firings happening successively millisenconds apart (even in 'fixed-period execution'). The documentation alludes to the fact that there are no timing guarantees but doesn't actually explain what causes these and how to prevent them.Bunko

© 2022 - 2024 — McMap. All rights reserved.