Android: Implication of using AsyncTask to make repeated Ajax Calls
Asked Answered
S

5

2

I need my Android app to periodically fetch data from a server using AJAX calls, and update the UI accordingly (just a bunch of TextViews that need to be updated with setText()). Note that this involves 2 tasks:

  1. Making an AJAX call, and updating the UI once I receive a response - I use a simple AsyncTask for this.
  2. Doing the above repeatedly, at regular intervals.

I haven't figured out an elegant way to achieve Point 2 above. Currently, I am simply executing the task itself from OnPostExecute(). I read on this thread at SO that I need not worry about garbage collection as far as the AsyncTask objects are concerned.

But I'm still unsure as to how I set up a timer that will fire my AsyncTask after it expires. Any pointers will be appreciated. Here is my code:

public class MyActivity extends Activity {


    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        new AjaxRequestTask().execute(MY_REST_API_URL);

    }

    private void updateReadings(String newReadings) {
           //Update the UI
        }

    class AjaxRequestTask extends AsyncTask<String, Integer, String> {

        @Override
        protected String doInBackground(String... restApiUrl) {
                    //Do AJAX Request
                }

        @Override
        protected void onPostExecute(String result) {
            updateReadings(result);
                     /*Is there a more elegant way to achieve this than create a new AsyncTask object every 10 seconds?  Also, How can I update the UI if I create a timer here? */
            new AjaxRequestTask().execute(MY_REST_API_URL);
        }

    }

}

Thanks in advance

EDIT: I tried posting an answer but couldn't do it since I don't have the reputation to answer within 8 hours.

Well, so I found a solution. I'm not convinced however.

protected void onPostExecute(String result) {

            updateReadings(result);
            // super.onPostExecute(result);
            new Timer().schedule(
                    new TimerTask() {
                        @Override
                        public void run() {
                            new AjaxRequestTask().execute(MY_REST_API_URL);
                        }
                    }, 
                    TIMER_ONE_TIME_EXECUTION_DELAY
            );
        }
  1. Are there any flip sides that I should be aware of when I use this? In particular, I am seeing lots of GCs happening in the LogCat. Also, I am wondering how an AsyncTask can be candidate for GC unless the onPostExecute() completes?

  2. How can I "stop" the updates? One way I thought of was to make the very first AsyncTask instance as a member variable of the Activity. That way, I can invoke cancel(true) on it and hope that this will "stop" the tasks.


SOLUTION:

In case anyone is looking for something similar - none of the solutions I mentioned here work satisfactorily. They all suffer from OutOfMemory issues. I did not debug into the details of the OOM, but I suspect it could either be because of the recursion, or because of having HTTP-related objects as member variables in the AsyncTask rather than as members of the Activity (basically because of NOT reusing HTTP and other objects).

I discarded this approach for a different one - making my Ajax Calls endlessly in the doInBackground() of my AsyncTask; and updating the UI in onProgressUpdate(). That way I also avoid the overhead of maintaining too many threads or Handlers for updating the UI (remember UI can be updated in onProgressUpdate() ).

This approach also eliminates the need for Timers and TimerTasks, favoring the use of Thread.sleep() instead. This thread on SO has more details and a code snippet too.

Simplehearted answered 30/6, 2011 at 12:35 Comment(1)
The "solution" I mentioned in my Edit above has serious pitfalls - as suspected, it leads to "infinite loop". Calling cancel(true) in onPause and onDestroy() did not do the trick - it never calls onCancelled() which I overrode in my AsyncTask. I cannot think of a way to use the isCancelled() method to influence the behavior inside of doInBackground(). I guess I'll have to go the AlarmManager way. I'll give it a try.Simplehearted
B
1

Call postDelayed() on any View to schedule a hunk of code to be run on the main application thread after a certain delay. Do this in onPostExecute() of the AsyncTask to create and execute another AsyncTask.

You could use AlarmManager, as others have cited, but I would agree with you that it feels a bit like overkill for timing that occurs purely within an activity.

That being said, if the AJAX calls should be occurring regardless of whether the activity exists, definitely consider switching to AlarmManager and an IntentService.

Bridgid answered 30/6, 2011 at 13:56 Comment(4)
Thanks for pointing out postDelayed(). It may work out better than my TimerTask solution that I posted in the edited question. I'll try it out.Simplehearted
This still means that I will have to start execution of a new AsyncTask from within the current one, doesn't it? Won't it have the same "infinite loop" pitfall that my attempted solution has? (please see the comment on the original question).Simplehearted
@techie.curious: Cancelling threads in Java has never been easy or reliable, IMHO. Simply set yourself a flag and don't call postDelayed() or execute the next AsyncTask when you're done.Bridgid
Thanks @Bridgid that did the trick. I had to ultimately use the most primitive form of inter-thread communication - setting a flag and changing its value on onPause() and onDestroy(). I am checking the value of this flag before kicking off a new AyncTask instance from within my onPostExecute().Simplehearted
P
0

I think the android way to do this is using AlarmManager. Or you can user a basic java Timer as well. I'd recommend AlarmManager.

Set it up to send some intent with a custom Action, and register a broadcastreceiver for it.

Paragon answered 30/6, 2011 at 12:43 Comment(2)
Thanks for the pointer. I don't believe AlarmManager is the right solution for me since I do not want the Ajax calls to occur from a Service. I will look at Handler, and try to avoid updating the UI from the handler. Instead, I will try to execute the AsyncTask from the handler.Simplehearted
I didn't say that you have to use services. Set up the alarm manager properly -> alarm manager sends intent at regular intervals -> you register dynamically a broadcast receiver in your activity that handles these intents -> in this receiver you start your asynctask. No services needed here. Don't forget to unregister the receiver in onStop or onPause, and disable the alarmmanager if not needed tho.Intersex
P
0

If the ajax calls are only executed in the activity you can just use a timer in the activity which starts the tasks.

Otherwise use a service which uses the AlarmManager and which connects to the gui via a broadcast.

Panamerican answered 30/6, 2011 at 12:46 Comment(1)
Right after I posted this question, I realized that performing the task at "regular" intervals is not a good idea - this is because of the nature of AJAX. I might not even have gotten a response by the time the next request is made! I am leaning towards the idea of firing the AsyncTask from the OnPostExecute, but after a fixed delay. I checked out AlarmManager, but it seems it is overkill since I need my Ajax calls to occur only when the activity is running. I'll probably look at Handler.Simplehearted
V
0

The recommended way to do a repeated task, is via AlarmManager, as alluded to by Scythe. Basically it involves setting up a broadcast listener, and having AlarmManager fire off an intent to that listener at whatever interval you choose. You then would have your broadcast listener call out to the activity to run the AsyncTask. If you need a very tight timer (less than 5s calls I'd say), then you're better off using a Timer within a Service, and using AIDL to call back to the activity.

Instead of talking directly from the broadcast intent, you could also setup an IntentService which you can poke, and use AIDL to update the activity.

Vasili answered 30/6, 2011 at 12:59 Comment(0)
S
0

This is how I achieved it finally. Note that the AsyncTask cancel(true) method is useless in my scenario because of the recursion. I used what @CommonsWare suggested - used a flag to indicate whether any more tasks should be executed.

public class MyActivity extends Activity {

    /*Flag which indicates whether the execution should be halted or not.*/
    private boolean mCancelFlag = false;

    private AjaxRequestTask mAjaxTask;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        if(mAjaxTask == null){
            mAjaxTask = new AjaxRequestTask();
        }
        mAjaxTask.execute(MY_REST_API_URL);

    }
@Override
    protected void onResume() {
        super.onResume();
        mCancelFlag = false; /*when we resume, we want the tasks to restart. Unset cancel flag*/

        /* If the main task is Finished, create a new task and execute it.*/

        if(mAjaxTask == null || mAjaxTask.getStatus().equals(AsyncTask.Status.FINISHED)){
            new AjaxRequestTask().execute(TLS_REST_API_URL);
        }

    }       
    @Override
    protected void onPause() {
        mCancelFlag = true; /*We want the execution to stop on pause. Set the cancel flag to true*/
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        mCancelFlag = true;/*We want the execution to stop on destroy. Set the cancel flag to true*/
        super.onDestroy();
    }

    private void updateReadings(String result) {
          //Update the UI using the new readings.
    }

    class AjaxRequestTask extends AsyncTask<String, Integer, String> {

        private AjaxRequestTask mChainAjaxRequest;
        private Timer mTimer;
        private TimerTask mTimerTask;


        @Override
        protected String doInBackground(String... restApiUrl) {
            //Do AJAX call and get the response
            return ajaxResponse;
        }

        @Override
        protected void onPostExecute(String result) {
            Log.d(TAG, "Updating readings");
            updateReadings(result);
            // super.onPostExecute(result);
            if(mTimer == null){
                mTimer = new Timer();
            }

            if(!mCancelFlag){/*Check if the task has been cancelled prior to creating a new TimerTask*/
                if(mTimerTask == null){
                    mTimerTask = new TimerTask() {
                        @Override
                        public void run() {
                            if(!mCancelFlag){/*One additional level of checking*/
                                if(mChainAjaxRequest == null){
                                    mChainAjaxRequest = new AjaxRequestTask();
                                }
                                    mChainAjaxRequest.execute(MY_REST_API_URL);
                            }

                        }
                    };
                }
                mTimer.schedule(mTimerTask,TIMER_ONE_TIME_EXECUTION_DELAY);

            }

        }

    }
}
Simplehearted answered 1/7, 2011 at 5:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.